Control Flows: Conditions and Iterations

Conditions

The main conditional statement is if. Within this parameter, all the available placeholders and their objective JavaScript mappings can be used.

For example

if ('${env.protocol}' == 'http')
or
if (env.protocol == 'http')

The main iterable object is ForEach. Both if and ForEach can be of any nesting level.

  • If condition is specified incorrectly, the actions inside if statement are not executed. Herewith, the Cloud Scripting Console returns the ‘invalid condition’ message with the root cause explanation. The application installer proceeds to the next action.

  • If condition is valid, but is not executed, the 'condition is not met' message is logged.

Examples

  • Comparing global variables

      type: update
      name: Comparing global variables
      
      globals:
        p1: 1
        p2: 2
      
      onInstall:
        - if (globals.p1 < globals.p2):
            if (user.uid > 1):
              log: "## p1 < p2 and ${user.email} is not a platform owner"
      
      {
        "type": "update",
        "name": "Comparing global variables",
        "globals": {
          "p1": 1,
          "p2": 2
        },
        "onInstall": [
          {
            "if (globals.p1 < globals.p2)": {
              "if (user.uid > 1)": {
                "log": "## p1 < p2 and ${user.email} is not a platform owner"
              }
            }
          }
        ]
      }
      

  • Checking environment status

      onInstall:
        if (env.status == 1):
          log: "## Environment is running"
      
      {
        "onInstall": {
          "if (env.status == 1)": {
            "log": "## Environment is running"
          }
        }
      }
      

  • Checking Jelastic SSL status

      onInstall:
        if(!${env.ssl}):
          log: "## SSL Disabled"
      
      {
        "onInstall": {
          "if(!${env.ssl})": {
            "log": "## SSL Disabled"
          }
        }
      }
      

  • Validating environment domain

      onInstall:
        if (/^env-/.test(env.domain)):
          log: "## Env domain begins with env-: ${env.domain}"
      
      {
        "onInstall": {
          "if (/^env-/.test(env.domain))": {
            "log": "## Env domain begins with env-: ${env.domain}"
          }
        }
      }
      

  • Checking compute node OS type and balancer presence

      onInstall:
        if (nodes.cp && nodes.cp[0].osType == 'LINUX'):
          log: "## Environment has compute node based on Linux"
          if (nodes.bl && nodes.bl[0].nodeType == 'nginx' && nodes.cp.length > 1):
            log: "## Environment has Nginx balancer and more than one compute node"
      
      {
        "onInstall": {
          "if (nodes.cp && nodes.cp[0].osType == 'LINUX')": [
            {
              "log": "## Environment has compute node based on Linux"
            },
            {
              "if (nodes.bl && nodes.bl[0].nodeType == 'nginx' && nodes.cp.length > 1)": {
                "log": "## Environment has Nginx balancer and more than one compute node"
              }
            }
          ]
        }
      }
      

Nested Conditions

Nesting of two if conditional statements - the first one is checking an environment for two compute nodes presence. If the nodes are available, the second one is checking the presence of the external IP address on the first balancer node and is logging the correspondent messages.

    type: update
    name: Nesting example
    
    onInstall:
      if (${nodes.cp[1].id}):
        - cmd [${nodes.cp[1].id}]: echo "Environment consists of two compute nodes" >> /tmp/result.txt
        - if (/^[0-9]{2,3}.[0-9]{2,3}.[0-9]{2,3}.[0-9]{2,3}/.test(\"${nodes.bl[0].extips}\")):
            cmd [${nodes.cp[0].id}]: echo "Balancer node with external IP address!" >> /tmp/result.txt
    
    {
      "type": "update",
      "name": "Nesting example",
      "onInstall": {
        "if (${nodes.cp[1].id})": [
          {
            "cmd [${nodes.cp[1].id}]": "echo \"Environment consists of two compute nodes\" >> /tmp/result.txt"
          },
          {
            "if (/^[0-9]{2,3}.[0-9]{2,3}.[0-9]{2,3}.[0-9]{2,3}/.test(\"${nodes.bl[0].extips}\"))": {
              "cmd [${nodes.cp[0].id}]": "echo \"Balancer node with external IP address!\" >> /tmp/result.txt"
            }
          }
        ]
      }
    }
    

The operation result can be located within a result.txt file that is automatically created in the master node (i.e. the first cp node) tmp directory.

Environment consists of two compute nodes
Balancer node with external IP address!

  • Checking balancer stack type
      type: update
      name: Nginx stack
      
      onInstall:
        if (nodes.bl[0].nodeType == 'nginx'):
          - script: |
              return { result: 0, error: "Environment balancer node is NGINX stack"};
      
      {
        "type": "update",
        "name": "Nginx stack",
        "onInstall": {
          "if (nodes.bl[0].nodeType == 'nginx')": [
            {
              "script": "return { result: 0, error: \"Environment balancer node is NGINX stack\"};"
            }
          ]
        }
      }
      

Iterations

ForEach

The main iterable object is ForEach with the following map.

    env:
      nodes: []
      contexts: []
      extdomains: []
    
    nodes: {}
    settings: {}
    license: {}
    
    event:
      params: {}
      response: {}
    
    this: {}
    
    {
      "env": {
        "nodes": [],
        "contexts": [],
        "extdomains": []
      },
      "nodes": {},
      "settings": {},
      "license": {},
      "event": {
        "params": {},
        "response": {}
      },
      "this": {}
    }
    
where:

  • settings [optional] - values of the fields that are predefined within a user settings form
  • license [optional] - link to fetch parameters that are specified within the prepopulate custom script. It enables to customize default field values and can be further initialized through the $(license.{any_name} placeholder within a manifest.
  • event [optional] - object with events that can be of two types, triggering a particular action before or after the event execution
  • this [optional] - object with parameters that are transmitted within the procedure body. See the full list of available placeholders on this parameter.

Iteration can be executed by env.nodes, nodes, env.contexts, and env.extdomains objects.

Iteration set by env.extdomains.

    forEach(env.extdomains):
      - writeFile:
          nodeGroup: cp
          path: /var/lib/jelastic/keys/${@i}.txt
          body: hello
    
    {
      "forEach(env.extdomains)": [
        {
          "writeFile": {
            "nodeGroup": "cp",
            "path": "/var/lib/jelastic/keys/${@i}.txt",
            "body": "hello"
          }
        }
      ]
    }
    
where:

  • @i - default iterator name
  • env.extdomains - bound external domains

Iteration set by env.contexts.

    forEach(env.contexts):
      writeFile [cp]:
        path: /var/lib/jelastic/keys/${@i.context}.txt
        body: 1
    
    {
      "forEach(env.contexts)": {
        "writeFile [cp]": {
          "path": "/var/lib/jelastic/keys/${@i.context}.txt",
          "body": "1"
        }
      }
    }
    
where:

  • env.contexts - list of contexts (applications) deployed to an environment

The example of scaling nodes.

    type: update
    name: Scaling Example
    
    onAfterScaleIn [nodeGroup:cp]: ScaleNodes
    
    onAfterScaleOut [nodeGroup:cp]: ScaleNodes
    
    actions:
      ScaleNodes:
        forEach(nodes.cp):
          cmd [bl]:
            - {commands to rewrite all Compute nodes internal IP addresses in balancer configs. Here balancer node is NGINX}
            - /etc/init.d/nginx reload
    
    {
      "type": "update",
      "name": "Scaling Example",
      "onAfterScaleIn[nodeGroup:cp]": "ScaleNodes",
      "onAfterScaleOut[nodeGroup:cp]": "ScaleNodes",
      "actions": {
        "ScaleNodes": {
          "forEach(nodes.cp)": {
            "cmd [bl]": [
              "{commands to rewrite all Compute nodes internal IP addresses in balancer configs. Here balancer node is NGINX}",
              "/etc/init.d/nginx reload"
            ]
          }
        }
      }
    }
    
As a result of the cmd action, the compute nodes internal IP addresses are rewritten within balancer configs and NGINX balancer node is reloaded. The onAfterScaleIn and onAfterScaleOut events are executed immediately after adding or removing a compute node.

By All Nodes

Iteration by all nodes in an environment.

Iteration set by env.nodes.

    forEach(env.nodes):
      cmd [${@i.id}]: echo ${@i.address} > /tmp/example.txt
    
    {
      "forEach(env.nodes)": {
        "cmd [${@i.id}]": "echo ${@i.address} > /tmp/example.txt"
      }
    }
    

By Compute Nodes

Iteration by compute nodes with a custom iterator name.

    forEach(cp:nodes.cp):
      cmd [${@cp.id}]: echo ${@cp.address} > /tmp/example.txt
    
    {
      "forEach(cp:nodes.cp)": {
        "cmd [${@cp.id}]": "echo ${@cp.address} > /tmp/example.txt"
      }
    }
    
where:
- @cp [optional] - custom iterator name. Also, target nodes can be set by type -\({@cp.nodeType}*, or group - *\){@cp.nodeGroup}.

Custom iterator name can be used for nesting cycles one into another.

    type: update
    name: execution actions
    
    onInstall:
      forEach(item:env.nodes):
        forEach(secondItem:env.nodes):
          log: ${@@item} - ${@@secondItem} - ${@}
    
    {
      "type": "update",
      "name": "execution actions",
      "onInstall": {
        "forEach(item:env.nodes)": {
          "forEach(secondItem:env.nodes)": {
            "log": "${@@item} - ${@@secondItem} - ${@}"
          }
        }
      }
    }
    
where:

  • ${@} - iterator number current loop
  • ${@@item} - iterator number of the first loop
  • ${@@secondItem} - iterator number of the second loop

In this case, every environment node will have only one conjunction by Node ID.

The ForEach execution is recorded in the user console log file for convenient code debugging.

forEachCount

What's next?