Skip to content

pablochacin/operator-sh

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Operator-sh

A framework for implementing Kubernetes Operators as shell scripts

Goals

The main objective of operator-sh is to facilitate the development of kubernetes operators using approaches familiar to the Devops.

  • Flatten the learning curve for DevOps
  • Test k8s automation ideas easily

To achieve these goals, the operator-sh framework follows the design principles below:

  • Simplicity first.
  • Extensibility by means of hooks and extensive configuration
  • Easy to use locally, with simple drop and use setup and minimal dependencies

Usage

    Watch for events and process them using scripts

    Usage: ./operator.sh [OPTIONS...]

    Options
    -c,--changes-only: do not received ADDED events for existing objects
    -e,--log-events: log received events to log file
    -h,--hooks: path to hooks. Default is 'hooks/'
    --label-selector: watch objects that match the given label(s).
      Supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
    -l,--log-file: path to the log. Default is /var/log/operator-sh.log
    -L,--log-level: log level ("DEBUG", "INFO", "WARNING", "ERROR") 
    -k,--kubeconfig: path to kubeconfig file for accessing Kubernetes cluster
    -n,--namespace: namespace to watch (optional)
    -o,--object: type of object to watch
    -q,--queue: queue to store events
    -r,--reset-queue: reset queue to delete any pending event from previous executions
    -R,--reset-log: reset log delete messages from previous executions
    -s,--filter-spec: filter object spec from event
    -S,--filter-status: filter object status from event
    --help: display this help

Examples

This section shows different examples of using operator-sh for automating cluster management with shell scripts.

In the following examples we use kind for running a local cluster. If you don't have it already installed, please check installation instructions.

Unless explicitly stated, we assume you have created a default cluster. Please notice the exact output can vary depending on the version of kind you are using.

$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.19.1) 🖼
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Thanks for using kind! 😊

kind automatically adds the credentials for accessing the cluster to ${HOME}/.kube/config or to the path specified in the $KUBECONFIG environment variable, if it is set. You can make it the active context by using the command kubectl config use-context kind-<cluster name>.

Remember that when running the operator in any other terminal than the one on which you created the cluster, you must ensure the kubeconfig file is available to the operator or any other tool, such as kubectl and the correct context is set by default. You can also pass both parameters to operator.sh.

Logging Pod creation

In this example we will simply log each pod that is created. For doing so, we will use a simple script located in examples/echo/added which logs every pod added event received and dumps the content of the enrironment variables with the fields from the event:

#!/bin/bash

source lib/log.sh

log_info "Processing event ${EVENT_TYPE} for object ${EVENT_OBJECT_KIND}/${EVENT_OBJECT_METADATA_NAME}"
log_debug "$(env | grep 'EVENT_' | sort)"

  1. In one terminal start the operator using the example script for monitoring pod creation at examples/echo/added.sh. Redirect the log to /tmp/operator-sh.log (default is located at /var/log/operator-sh.log)
$ ./operator.sh -o pod --hooks examples/echo -R -l /tmp/operator-sh.log -L DEBUG
  1. In another terminal, start watching the log
$ tail -f /tmp/operator-sh.log
  1. In another terminal, create a deployment with one replica
$ kubectl create deployment nginx --image nginx
deployment.apps/nginx created

  1. In the terminal on which you are tailing the log, you should see these messages:
20-09-04 22:09:12 INFO Processing event "ADDED" for object "Pod"/"nginx-554b9c67f9-8m7qq"
20-09-04 22:09:12 DEBUG No event handler exits for event "MODIFIED". Ignoring.
20-09-04 22:09:12 DEBUG No event handler exits for event "MODIFIED". Ignoring.
20-09-04 22:09:12 DEBUG No event handler exits for event "MODIFIED". Ignoring.

Notice that as there is not hook for the MODIFIED event, these events are ignored by operator-sh

  1. Back in the kubectl terminal (on which you created the deployment) increase the number of replicas to create more pods:
$ kubectl scale deployment nginx --replicas 3

You should see these new messages in the log:

20-09-04 22:09:58 INFO Processing event "ADDED" for object "Pod"/"nginx-554b9c67f9-xs4zz"
20-09-04 22:09:59 INFO Processing event "ADDED" for object "Pod"/"nginx-554b9c67f9-sjrsk"
20-09-04 22:09:59 DEBUG No event handler exits for event "MODIFIED". Ignoring.
20-09-04 22:09:59 DEBUG No event handler exits for event "MODIFIED". Ignoring.
20-09-04 22:09:59 DEBUG No event handler exits for event "MODIFIED". Ignoring.
20-09-04 22:09:59 DEBUG No event handler exits for event "MODIFIED". Ignoring.
20-09-04 22:10:00 DEBUG No event handler exits for event "MODIFIED". Ignoring.
20-09-04 22:10:00 DEBUG No event handler exits for event "MODIFIED". Ignoring.
  1. Modify the script examples/echo/added and change the log level to LOG_LEVEL_DEBUG. Back in the kubectl terminal, increase the number of replicas to 4 $ kubectl scale deployment nginx --replicas 4. You should see now the following in the log file:
(click to see the complete listing)
2020-09-06 19:44:41 INFO Processing event ADDED for object Pod/nginx-554b9c67f9-v7krx
2020-09-06 19:44:41 DEBUG EVENT_OBJECT_APIVERSION=v1
EVENT_OBJECT_KIND=Pod
EVENT_OBJECT_METADATA_CREATIONTIMESTAMP=2020-09-06T17:44:41Z
EVENT_OBJECT_METADATA_GENERATENAME=nginx-554b9c67f9-
EVENT_OBJECT_METADATA_LABELS_APP=nginx
EVENT_OBJECT_METADATA_LABELS_POD_TEMPLATE_HASH=554b9c67f9
EVENT_OBJECT_METADATA_NAME=nginx-554b9c67f9-v7krx
EVENT_OBJECT_METADATA_NAMESPACE=default
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_APIVERSION=apps/v1
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_BLOCKOWNERDELETION=1
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_CONTROLLER=1
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_KIND=ReplicaSet
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_NAME=nginx-554b9c67f9
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_UID=5e891bbc-2527-436b-9b88-d5821bb0557d
EVENT_OBJECT_METADATA_RESOURCEVERSION=55933
EVENT_OBJECT_METADATA_SELFLINK=/api/v1/namespaces/default/pods/nginx-554b9c67f9-v7krx
EVENT_OBJECT_METADATA_UID=da802dcb-236a-4b35-9f80-887ddf6e4835
EVENT_OBJECT_SPEC_CONTAINERS_0_IMAGE=nginx
EVENT_OBJECT_SPEC_CONTAINERS_0_IMAGEPULLPOLICY=Always
EVENT_OBJECT_SPEC_CONTAINERS_0_NAME=nginx
EVENT_OBJECT_SPEC_CONTAINERS_0_TERMINATIONMESSAGEPATH=/dev/termination-log
EVENT_OBJECT_SPEC_CONTAINERS_0_TERMINATIONMESSAGEPOLICY=File
EVENT_OBJECT_SPEC_CONTAINERS_0_VOLUMEMOUNTS_0_MOUNTPATH=/var/run/secrets/kubernetes.io/serviceaccount
EVENT_OBJECT_SPEC_CONTAINERS_0_VOLUMEMOUNTS_0_NAME=default-token-9d96d
EVENT_OBJECT_SPEC_CONTAINERS_0_VOLUMEMOUNTS_0_READONLY=1
EVENT_OBJECT_SPEC_DNSPOLICY=ClusterFirst
EVENT_OBJECT_SPEC_ENABLESERVICELINKS=1
EVENT_OBJECT_SPEC_PRIORITY=0
EVENT_OBJECT_SPEC_RESTARTPOLICY=Always
EVENT_OBJECT_SPEC_SCHEDULERNAME=default-scheduler
EVENT_OBJECT_SPEC_SERVICEACCOUNT=default
EVENT_OBJECT_SPEC_SERVICEACCOUNTNAME=default
EVENT_OBJECT_SPEC_TERMINATIONGRACEPERIODSECONDS=30
EVENT_OBJECT_SPEC_TOLERATIONS_0_EFFECT=NoExecute
EVENT_OBJECT_SPEC_TOLERATIONS_0_KEY=node.kubernetes.io/not-ready
EVENT_OBJECT_SPEC_TOLERATIONS_0_OPERATOR=Exists
EVENT_OBJECT_SPEC_TOLERATIONS_0_TOLERATIONSECONDS=300
EVENT_OBJECT_SPEC_TOLERATIONS_1_EFFECT=NoExecute
EVENT_OBJECT_SPEC_TOLERATIONS_1_KEY=node.kubernetes.io/unreachable
EVENT_OBJECT_SPEC_TOLERATIONS_1_OPERATOR=Exists
EVENT_OBJECT_SPEC_TOLERATIONS_1_TOLERATIONSECONDS=300
EVENT_OBJECT_SPEC_VOLUMES_0_NAME=default-token-9d96d
EVENT_OBJECT_SPEC_VOLUMES_0_SECRET_DEFAULTMODE=420
EVENT_OBJECT_SPEC_VOLUMES_0_SECRET_SECRETNAME=default-token-9d96d
EVENT_OBJECT_STATUS_PHASE=Pending
EVENT_OBJECT_STATUS_QOSCLASS=BestEffort
EVENT_TYPE=ADDED

Design

The operator-sh framework architecture is described in the figure below

                       + - - - - - - - - - - - - - - - - - +        +----------+
    +--------+         ·  +-------+           +---------+  ·      +---------+  |
    |        |  events ·  |       |           |         |  ·    +-+------+  |  |
    |   K8s  |----------->| watch |           | process |--+--->|  Event |  |--+
    | Cluster|         ·  |       |           |         |  ·    | Handler|--+
    |        |         ·  +-------+           +---------+  ·    +--------+
    +--------+         ·       |                   ^  ^    ·
                       + - - - - - - - - - - - - - + -+- - +   +--------+
                               |   +-------------+ |  |        |  Json  |
                               +-->| event queue |-+  +------->| Parser |
                                   +-------------+             +--------+

The operator process consists of two sub-processes:

  • watch: connects to the K8s cluster and watches for events on an object type, sending the events to a queue as a json object.
  • process: reads events from the queue and process them sequentially. Each event is parsed and converted to a series of environment variables, as described en the section Json Parsing. These variables are used to initialize the environment for the event handling scripts.
  • event handlers: each event type (ADDED, MODIFIED, DELETED) is handled by an external script provided by the user. The script name's must match the event type but in all-lowercases (e.g. added). This scripts are executed as sub-processes and received an environment with the content of the event and other setup information (for example, the path to the kubeconfig file to connect to the cluster)

Json parsing

The events are transformed in a series of environment variables. This parser is adapted from the k8s-operator. Consider the following json corresponding to the creation of a Pod:

JSON for Pod `ADDED` event (click to display code)
{
  "type":"ADDED",
   "object": {
      "apiVersion":"v1",
      "kind":"Pod",
      "metadata": {
         "creationTimestamp":"2020-09-02T14:18:46Z",
         "generateName":"nginx-554b9c67f9-",
         "labels":{
            "app":"nginx",
            "pod-template-hash":"554b9c67f9"
         },
         "name":"nginx-554b9c67f9-psl7l",
         "namespace":"default",
         "ownerReferences": [
           {
             "apiVersion":"apps/v1",
             "blockOwnerDeletion":true,
             "controller":true,
             "kind":"ReplicaSet",
             "name":"nginx-554b9c67f9",
             "uid":"a46bbda8-073c-4bd1-a616-da9fbab4d7d6"
           }
         ],
         "resourceVersion":"11276",
         "selfLink":"/api/v1/namespaces/default/pods/nginx-554b9c67f9-psl7l",
         "uid":"bcdcd367-602e-4664-8980-da1dc8c94de3"
       },
     "spec":{
        "containers":[
           {
            "image":"nginx",
             "imagePullPolicy":"Always",
             "name":"nginx",
             "resources":{},
             "terminationMessagePath":"/dev/termination-log",
             "terminationMessagePolicy":"File",
             "volumeMounts":[
               {
                "mountPath":"/var/run/secrets/kubernetes.io/serviceaccount",
                "name":"default-token-r6ftr","readOnly":true
               }
             ]
           }
        ],
        "dnsPolicy":"ClusterFirst",
        "enableServiceLinks":true,
        "nodeName":"kind-control-plane",
        "priority":0,
        "restartPolicy":"Always",
        "schedulerName":"default-scheduler",
        "securityContext":{},
        "serviceAccount":"default",
        "serviceAccountName":"default",
        "terminationGracePeriodSeconds":30,
        "tolerations":[
          {
            "effect":"NoExecute",
            "key":"node.kubernetes.io/not-ready",
            "operator":"Exists","tolerationSeconds":300
          },
          {
            "effect":"NoExecute",
            "key":"node.kubernetes.io/unreachable",
            "operator":"Exists","tolerationSeconds":300
          }
        ],
        "volumes":[
          {
            "name":"default-token-r6ftr",
            "secret":{
              "defaultMode":420,
              "secretName":"default-token-r6ftr"
            }
          }
       ]
     },
     "status":{
       "conditions": [
         {
          "lastProbeTime":null,
          "lastTransitionTime":"2020-09-02T14:18:46Z",
          "status":"True",
          "type":"Initialized"
         },
         {
           "lastProbeTime":null,
           "lastTransitionTime":"2020-09-02T14:18:53Z",
           "status":"True","type":"Ready"
         },
         {
           "lastProbeTime":null,
           "lastTransitionTime":"2020-09-02T14:18:53Z",
           "status":"True","type":"ContainersReady"
         },
         {
           "lastProbeTime":null,
           "lastTransitionTime":"2020-09-02T14:18:46Z",
           "status":"True",
           "type":"PodScheduled"
         }
       ],
       "containerStatuses":[ 
         {
           "containerID":"containerd://da0ad052d7e4fc4019a60f916d36279c4edf657aec928335c086e67779e555ac",
           "image":"docker.io/library/nginx:latest",
           "imageID":"docker.io/library/nginx@sha256:b0ad43f7ee5edbc0effbc14645ae7055e21bc1973aee5150745632a24a752661",
           "lastState":{},
           "name":"nginx",
           "ready":true,
           "restartCount":0,
           "state":{
             "running":{
                "startedAt":"2020-09-02T14:18:52Z"
           }}
         }
       ],
       "hostIP":"172.17.0.2",
       "phase":"Running",
       "podIP":"10.244.0.4",
       "qosClass":"BestEffort","startTime":"2020-09-02T14:18:46Z"
     }
   }
 }

It will transformed to the environment variables shown bellow. Some aspects to highlight:

  • Every element is named prefixing the name of all of the elements to the root of the event
  • A prefix (by default, EVENT is added to each variable to prevent name collision with other existing variables
  • Elements of arrays are referenced by their position (index)
Environment variables for Pod ADDED event (click to display code)
EVENT_TYPE="ADDED"
EVENT_OBJECT_APIVERSION="v1"
EVENT_OBJECT_KIND="Pod"
EVENT_OBJECT_METADATA_CREATIONTIMESTAMP="2020-09-02T14:18:46Z"
EVENT_OBJECT_METADATA_GENERATENAME="nginx-554b9c67f9-"
EVENT_OBJECT_METADATA_LABELS_APP="nginx"
EVENT_OBJECT_METADATA_LABELS_POD_TEMPLATE_HASH="554b9c67f9"
EVENT_OBJECT_METADATA_NAME="nginx-554b9c67f9-psl7l"
EVENT_OBJECT_METADATA_NAMESPACE="default"
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_APIVERSION="apps/v1"
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_BLOCKOWNERDELETION="1"
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_CONTROLLER="1"
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_KIND="ReplicaSet"
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_NAME="nginx-554b9c67f9"
EVENT_OBJECT_METADATA_OWNERREFERENCES_0_UID="a46bbda8-073c-4bd1-a616-da9fbab4d7d6"
EVENT_OBJECT_METADATA_RESOURCEVERSION="11276"
EVENT_OBJECT_METADATA_SELFLINK="/api/v1/namespaces/default/pods/nginx-554b9c67f9-psl7l"
EVENT_OBJECT_METADATA_UID="bcdcd367-602e-4664-8980-da1dc8c94de3"
EVENT_OBJECT_SPEC_CONTAINERS_0_IMAGE="nginx"
EVENT_OBJECT_SPEC_CONTAINERS_0_IMAGEPULLPOLICY="Always"
EVENT_OBJECT_SPEC_CONTAINERS_0_NAME="nginx"
EVENT_OBJECT_SPEC_CONTAINERS_0_TERMINATIONMESSAGEPATH="/dev/termination-log"
EVENT_OBJECT_SPEC_CONTAINERS_0_TERMINATIONMESSAGEPOLICY="File"
EVENT_OBJECT_SPEC_CONTAINERS_0_VOLUMEMOUNTS_0_MOUNTPATH="/var/run/secrets/kubernetes.io/serviceaccount"
EVENT_OBJECT_SPEC_CONTAINERS_0_VOLUMEMOUNTS_0_NAME="default-token-r6ftr"
EVENT_OBJECT_SPEC_CONTAINERS_0_VOLUMEMOUNTS_0_READONLY="1"
EVENT_OBJECT_SPEC_DNSPOLICY="ClusterFirst"
EVENT_OBJECT_SPEC_ENABLESERVICELINKS="1"
EVENT_OBJECT_SPEC_NODENAME="kind-control-plane"
EVENT_OBJECT_SPEC_PRIORITY="0"
EVENT_OBJECT_SPEC_RESTARTPOLICY="Always"
EVENT_OBJECT_SPEC_SCHEDULERNAME="default-scheduler"
EVENT_OBJECT_SPEC_SERVICEACCOUNT="default"
EVENT_OBJECT_SPEC_SERVICEACCOUNTNAME="default"
EVENT_OBJECT_SPEC_TERMINATIONGRACEPERIODSECONDS="30"
EVENT_OBJECT_SPEC_TOLERATIONS_0_EFFECT="NoExecute"
EVENT_OBJECT_SPEC_TOLERATIONS_0_KEY="node.kubernetes.io/not-ready"
EVENT_OBJECT_SPEC_TOLERATIONS_0_OPERATOR="Exists"
EVENT_OBJECT_SPEC_TOLERATIONS_0_TOLERATIONSECONDS="300"
EVENT_OBJECT_SPEC_TOLERATIONS_1_EFFECT="NoExecute"
EVENT_OBJECT_SPEC_TOLERATIONS_1_KEY="node.kubernetes.io/unreachable"
EVENT_OBJECT_SPEC_TOLERATIONS_1_OPERATOR="Exists"
EVENT_OBJECT_SPEC_TOLERATIONS_1_TOLERATIONSECONDS="300"
EVENT_OBJECT_SPEC_VOLUMES_0_NAME="default-token-r6ftr"
EVENT_OBJECT_SPEC_VOLUMES_0_SECRET_DEFAULTMODE="420"
EVENT_OBJECT_SPEC_VOLUMES_0_SECRET_SECRETNAME="default-token-r6ftr"
EVENT_OBJECT_STATUS_CONDITIONS_0_LASTPROBETIME=""
EVENT_OBJECT_STATUS_CONDITIONS_0_LASTTRANSITIONTIME="2020-09-02T14:18:46Z"
EVENT_OBJECT_STATUS_CONDITIONS_0_STATUS="True"
EVENT_OBJECT_STATUS_CONDITIONS_0_TYPE="Initialized"
EVENT_OBJECT_STATUS_CONDITIONS_1_LASTPROBETIME=""
EVENT_OBJECT_STATUS_CONDITIONS_1_LASTTRANSITIONTIME="2020-09-02T14:18:53Z"
EVENT_OBJECT_STATUS_CONDITIONS_1_STATUS="True"
EVENT_OBJECT_STATUS_CONDITIONS_1_TYPE="Ready"
EVENT_OBJECT_STATUS_CONDITIONS_2_LASTPROBETIME=""
EVENT_OBJECT_STATUS_CONDITIONS_2_LASTTRANSITIONTIME="2020-09-02T14:18:53Z"
EVENT_OBJECT_STATUS_CONDITIONS_2_STATUS="True"
EVENT_OBJECT_STATUS_CONDITIONS_2_TYPE="ContainersReady"
EVENT_OBJECT_STATUS_CONDITIONS_3_LASTPROBETIME=""
EVENT_OBJECT_STATUS_CONDITIONS_3_LASTTRANSITIONTIME="2020-09-02T14:18:46Z"
EVENT_OBJECT_STATUS_CONDITIONS_3_STATUS="True"
EVENT_OBJECT_STATUS_CONDITIONS_3_TYPE="PodScheduled"
EVENT_OBJECT_STATUS_CONTAINERSTATUSES_0_CONTAINERID="containerd://da0ad052d7e4fc4019a60f916d36279c4edf657aec928335c086e67779e555ac"
EVENT_OBJECT_STATUS_CONTAINERSTATUSES_0_IMAGE="docker.io/library/nginx:latest"
EVENT_OBJECT_STATUS_CONTAINERSTATUSES_0_IMAGEID="docker.io/library/nginx@sha256:b0ad43f7ee5edbc0effbc14645ae7055e21bc1973aee5150745632a24a752661"
EVENT_OBJECT_STATUS_CONTAINERSTATUSES_0_NAME="nginx"
EVENT_OBJECT_STATUS_CONTAINERSTATUSES_0_READY="1"
EVENT_OBJECT_STATUS_CONTAINERSTATUSES_0_RESTARTCOUNT="0"
EVENT_OBJECT_STATUS_CONTAINERSTATUSES_0_STATE_RUNNING_STARTEDAT="2020-09-02T14:18:52Z"
EVENT_OBJECT_STATUS_HOSTIP="172.17.0.2"
EVENT_OBJECT_STATUS_PHASE="Running"
EVENT_OBJECT_STATUS_PODIP="10.244.0.4"
EVENT_OBJECT_STATUS_QOSCLASS="BestEffort"
EVENT_OBJECT_STATUS_STARTTIME="2020-09-02T14:18:46Z"

Known Issues

Labels names cannot be reconstructed from environent variable names

Labels usually have the form /. For example:

  spec:
    selector:
      matchLabels:
        node.kubernetes.io/instance-type=m3.medium

Due to the restrictions on valid variable names in bash, when flattening the object to environment variables, the labels is transformed to:

EVENT_OBJECT_SPEC_SELECTOR_MATCHLABELS_NODE_KUBERNETES_IO_INSTANCE_TYPE=m3.medium

From variable name is not possible to reconstruct the original label name unless some assumptions are made regarding the name, such as the use of all lowercase, but this is not reliable. For instance both instance-type and instance_type would be translated to INSTANCE_TYPE.

Road Map

  • Provide examples
  • Create image and k8s manifests for deploying operators
  • Implement e2e tests
  • Implement a library for managing CRDs (create, update)

Development

Testing

The operator-sh is tested using a minimalistic testing framework defined in lib/test.sh. This framework provides some functions for executing commands and assert the results. In order to use these functions in your test, you must include them into your test script:

source ../lib/test.sh

Unit tests

The example below executes the parse.py command and verifies it properly parses the event into environment variables:

#!/bin/bash

source lib/test.sh

# Test default parsing with the event.json input
function test_test_default_parsing(){ 
    unit_test "./parse.py" "event.json"
    assert_command_rc 0
    assert_output_contains "EVENT_TYPE="
    assert_equals 74 $(wc -l <<< $TEST_OUTPUT)
}

The framework can be also used to test functions in a script. For instance:

#!/bin/bash

source lib/test.sh

# include the operator source code
source ../operator.sh

# Test invalid argument option
function test_invalid_argument(){
    unit_test "parse_args --invalid-option"
    assert_command_rc 1
    assert_output_contains "Invalid parameter"
    assert_output_contains "--invalid-option"
    assert_output_contains "Usage"
}

# invoke test runner
test_runner

However, in order to prevent the script's main logic to be executed when sourcing in into your test script, you can follow the convention of wrapping your main logic as shown below:

# main logic
function main(){

}

## if sourced into another script, do not execute main logic
if [ "${BASH_SOURCE[0]}" == "$0" ]; then
    main $@
fi

E2E tests

The test library also provides means for execting e2e tests with the operator-sh. These tests are excuted by launching the operator with some parameters, executing a command and asserting some post-conditions. See the example below:

#!/bin/bash

source lib/test.sh

# Test ADDED events are received for new pods created
function test_new_pods(){
    e2e_test "kubectl create deployment nginx --image nginx"  "-o pod -L INFO"
    assert_command_rc 0
    assert_log_contains "Processing event ADDED"
}

# Test ADDED events are received for existing pods
function test_existing_pods(){
    kubectl create deployment nginx --image nginx > /dev/null
    e2e_test "kubectl get deployment nginx" "-o pod -L INFO"
    assert_command_rc 0
    assert_log_contains "Processing event ADDED"
}

test_runner $@

Executing it gives the following result:

$> tests/e2e_test.sh

Executing tests
  test_new_pods
  test_existing_pods
Finished

It is also possible to select a particular test to execute:

$> test/e2e_tests.sh -t test_existing_pods
Executing tests
  test_exising_pods
Finished

To facilitate test setup and clenup, it is posible to execute commands before and after each test.

# clean up deployments after each test
after_each "kubectl delete deployment --all --wait=true" --ignore-errors

test_runner

Inspired by

(c) 2020 Pablo Chacin.

About

A framework for developing Kubernetes operators as shell scripts

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors