def handler(system: flow_api.System, this: flow_api.Execution):
    vault_config = 'my-vaultconfig'
    secret_path = 'my-git-secret'
    secret_key_user = '******'
    secret_key_password = '******'

    my_connector_name = 'git-example-connector'

    # Create connector of type GIT and link it with vault secrets
    system.connector(my_connector_name).save(
        connector_type='GIT',
        value={
            'username': f'vault.secret({secret_key_user})',
            'password': f'vault.secret({secret_key_password})',
        },
        vault_secrets=[{
            'vault_name': vault_config,
            'engine_path': 'kv',
            'secret_path': secret_path,
        }],
    )

    # Use the connector:
    git_metadata = this.connect(
        my_connector_name,
        name='get git-metadata',
        repository_url='https://mygitrepo.com/repo/',
        command='metadata',
    ).get('output_value')
    this.log(git_metadata)

    return this.success('all done')
Esempio n. 2
0
def handler(system: flow_api.System, this: flow_api.Execution):
    # get the contents of a public github repository
    task = this.connect(
        connector_type='GIT',
        command='get',
        # The url to a public repository
        repository_url='https://github.com/starflows/library/',
        # Get the reference master, I could also specify a sha, another branch or a tag
        ref='master',
    )
    outputs = task.get('output_value')
    # Note that both the name and content of every file is part of the output
    this.log(outputs)

    task2 = this.connect(
        connector_type='GIT',
        command='get',
        repository_url='https://github.com/starflows/library/',
        files_path='flow_library',
        ref='v2',
    )
    outputs = task2.get('output_value')
    # Here there are no files in the output
    this.log(outputs)
    # But they are saved to cloudomation:
    files = system.files(dir='flow_library')
    for file in files:
        this.log(file)
    return this.success('all done')
Esempio n. 3
0
def handler(system: flow_api.System, this: flow_api.Execution):
    # this flow script will sync from the master branch.
    ref = 'master'

    # get the connection information for the github repo
    repo_info = system.setting('github_info').get('value')
    github_username = repo_info['github_username']
    github_repo_name = repo_info['github_repo_name']
    github_token = repo_info['github_token']

    repo_url = (f'https://{github_username}:{github_token}@github.com/'
                f'{github_username}/{github_repo_name}.git')

    # the git 'get' command fetches the content of the repository.
    files = this.connect(
        connector_type='GIT',
        command='get',
        repository_url=repo_url,
        ref=ref,
    ).get('output_value')['files']

    # iterate over all files
    for file_ in files:
        # split the path and filename
        path, filename = os.path.split(file_['name'])
        # split the filename and file extension
        name, ext = os.path.splitext(filename)

        # Create or update flow scripts from all .py files.
        if ext == '.py':
            # decode the base64 file content to text
            text_content = base64.b64decode(file_['content']).decode()
            # create or update Flow object
            system.flow(name).save(script=text_content)

        # Create or update settings from all .yaml files.
        elif ext == '.yaml':
            # decode the base64 file content to text
            text_content = base64.b64decode(file_['content']).decode()
            # load the yaml string in the file content
            value = yaml.safe_load(text_content)
            # create or update Setting object
            system.setting(name).save(value=value)

        # All files that have a file extension other than .py or .yaml are ignored.

    return this.success('Github sync complete')
def handler(system: flow_api.System, this: flow_api.Execution):
    secrets_input_form = system.message(
        subject='Input for a secret with username and password',
        message_type='POPUP',
        body={
            'type': 'object',
            'properties': {
                'username': {
                    'element': 'string',
                    'type': 'string',
                    'label': 'Enter the secret "username":'******'order': 1,
                },
                'password': {
                    'element': 'password',
                    'type': 'string',
                    'label': 'Enter the secret "password":'******'order': 2,
                },
                'Ok': {
                    'element': 'submit',
                    'label': 'OK',
                    'type': 'boolean',
                    'order': 3,
                },
            },
            'required': [
                'username',
                'password',
            ],
        },
    ).wait().get('response')

    target_vault = '<your-vault-configuration-name>'  # if None, then is iterating over all vaults in DB
    secret_path = '<your-secret-path-in-vault>'
    new_secrets = {
        "password": secrets_input_form['username'],
        "username": secrets_input_form['password'],
    }

    my_vault = system.vault_config(target_vault)
    my_vault.write_secret(
        secret_path,
        new_secrets,
    )
    return this.success('all done')
Esempio n. 5
0
def handler(system: flow_api.System, this: flow_api.Execution):
    """
    Clone a git repository, create Cloudomation objects from the files.
    All .py files in the flows/ subdirectory will be stored as Flows.
    All .yaml files in the settings/ subdirectory will be stored as Settings.
    All files in the files/ subdirectory will be stored as Files.
    """
    inputs = this.get('input_value')
    # this flow is registered as webhook, triggered by a commit to the
    # repository. The commit sha is passed in .json.commit_sha
    # when started manually, it will sync from master
    try:
        ref = inputs['json']['commit_sha']
    except (KeyError, TypeError):
        ref = 'master'
    # read the connection information of the private repository
    repo_info = system.setting('private git repo').get('value')
    # the git 'get' command fetches the content of the repository.
    # since no files_path is passed, the files will be returned in the
    # output_value of the task
    files = this.connect(
        connector_type='GIT',
        command='get',
        repository_url=repo_info['repository_url'],
        httpCookie=repo_info['httpCookie'],
        ref=ref,
    ).get('output_value')['files']
    # iterate over all files
    for file_ in files:
        # split the path and filename
        path, filename = os.path.split(file_['name'])
        # split the filename and file extension
        name, ext = os.path.splitext(filename)
        if path == 'flows' and ext == '.py':
            # decode the base64 file content to text
            text_content = base64.b64decode(file_['content']).decode()
            # create or update Flow object
            system.flow(name).save(script=text_content)
        elif path == 'settings' and ext == '.yaml':
            # decode the base64 file content to text
            text_content = base64.b64decode(file_['content']).decode()
            # load the yaml string in the file content
            value = yaml.safe_load(text_content)
            # create or update Setting object
            system.setting(name).save(value=value)
        elif path == 'files':
            # create or update File object
            # we use the name with the extension
            # pass the base64 encoded binary file content directly
            system.file(filename).save(content=file_['content'],
                                       convert_binary=False)

    return this.success('all done')
Esempio n. 6
0
def handler(system: flow_api.System, this: flow_api.Execution):
    inputs = this.get('input_value')
    method = inputs['method']

    store = system.setting('todo-store')
    try:
        todos = store.get('value')
    except flow_api.ResourceNotFoundError:
        todos = []
    if not todos:
        todos = []

    if method == 'POST':
        json = inputs['json']
        action = json.get('action')
        if action == 'todoNew':
            todos.append({
                'id': str(uuid.uuid4()),
                'label': json['newTodoLabel'],
            })
        elif action == 'todoDone':
            todos = [{
                **todo,
                'done': True,
            } if todo['id'] == json['id'] else todo for todo in todos]
        elif action == 'todoUndone':
            todos = [{
                **todo,
                'done': False,
            } if todo['id'] == json['id'] else todo for todo in todos]
        elif action == 'todoRemove':
            todos = [todo for todo in todos if todo['id'] != json['id']]

        store.save(value=todos)
        return this.success('all done')

    data = {
        'count': len(todos),
        'todos': todos,
    }

    return this.webhook_html_response(chevron.render(MAIN, data))
def handler(system: flow_api.System, this: flow_api.Execution):
    # retrieve the apikey and secret from the system setting
    setting = system.setting('exoscale.api').get('value')

    # endpoint to call
    compute_endpoint = "https://api.exoscale.ch/compute"

    # setup command for listing the running VMs
    command = {
        'command': 'listVirtualMachines',
        'state': 'Running',
        'apikey': setting.get('key'),
    }
    result = this.flow('acs.apicall',
                       input_value={
                           'command': command,
                           'secret': setting.get('secret'),
                           'compute_endpoint': compute_endpoint,
                       }).get('output_value').get('result')
    count = result.get("listvirtualmachinesresponse").get("count")

    # return the count of the running VMs
    return this.success(f'VMs running: {count}')
Esempio n. 8
0
def handler(system: flow_api.System, this: flow_api.Execution):
    inputs = this.get('input_value')
    gcloud_connector = inputs.get('gcloud_connector')
    project_id = system.connector(gcloud_connector).get('value').get(
        'key').get('project_id')
    if project_id is None or gcloud_connector is None:
        return this.error('Missing inputs')

    delete_task = this.connect(
        gcloud_connector,
        name='delete webpagetest-server',
        api_name='compute',
        api_version='v1',
        collection='instances',
        request='delete',
        params={
            'instance': 'webpagetest-server',
            'project': project_id,
            'zone': 'europe-west1-b',
        },
        wait=system.return_when.ALL_ENDED,
    )
    status = delete_task.get('status')
    message = delete_task.get('message')
    operation = delete_task.get('output_value').get('result')
    this.flow(
        'google_operation_wait',
        operation=operation,
    )

    system.setting('webpagetestserver').release()

    if status == 'ENDED_ERROR' and 'HttpError 404' not in message:
        return this.error('failed to delete webpagetest-server')

    webpagetestserver = system.setting('webpagetestserver').get('value')
    webpagetestserver['active'] = False
    system.setting('webpagetestserver').save(value=webpagetestserver)

    return this.success('all done')
Esempio n. 9
0
def handler(system: flow_api.System, this: flow_api.Execution):
    inputs = this.get('input_value')
    gcloud_connector = inputs.get('gcloud_connector')
    project_id = system.connector(gcloud_connector).get('value').get(
        'key').get('project_id')
    if project_id is None or gcloud_connector is None:
        return this.error('Missing inputs')

    try:
        webpagetestserver = system.setting('webpagetestserver').get('value')
        if webpagetestserver.get('active'):
            this.set_output(wait=False)
            return this.success('Server already active')
    except:
        webpagetestserver = system.setting('webpagetestserver')

    this.save(message=f'waiting for webpagetestserver lock')
    system.setting('webpagetestserver').acquire(timeout=None)
    this.save(message=f'creating webpagetest server')

    webpagetest_server_config_str = system.file(
        'webpagetest-server.json').get_text_content()
    webpagetest_server_config = json.loads(webpagetest_server_config_str)
    webpagetest_server_config['name'] = 'webpagetest-server'
    webpagetest_server_config['disks'][0]['deviceName'] = 'webpagetest-server'
    webpagetest_server_config['disks'][0]['initializeParams'][
        'sourceSnapshot'] = f'projects/{project_id}/global/snapshots/webpagetest-server-template'

    launch_task = this.connect(
        gcloud_connector,
        name='launch webpagetest-server',
        api_name='compute',
        api_version='v1',
        collection='instances',
        request='insert',
        params={
            'body': webpagetest_server_config,
            'project': project_id,
            'zone': 'europe-west1-b',
        },
        wait=system.return_when.ALL_ENDED,
    )
    status = launch_task.get('status')
    message = launch_task.get('message')
    if status == 'ENDED_ERROR' and 'HttpError 409' in message:
        return this.success('using existing webpagetestserver')
    if status == 'ENDED_ERROR':
        return this.error('failed to launch webpagetest-server')

    operation = launch_task.get('output_value')['result']

    this.flow(
        'google_operation_wait',
        operation=operation,
    )

    for _ in range(60):
        instance_list = this.connect(
            gcloud_connector,
            name='find webpagetest-server',
            api_name='compute',
            api_version='v1',
            collection='instances',
            request='list',
            params={
                'filter': 'name=webpagetest-server',
                'project': project_id,
                'zone': 'europe-west1-b',
            },
        ).get('output_value')
        if len(instance_list['result'].get('items', [])) != 0:
            break
        this.sleep(1)
    else:
        return this.error('did not find webpagetest server within 60 tries')
    external_ip = instance_list['result']['items'][0]['networkInterfaces'][0][
        'accessConfigs'][0]['natIP']

    webpagetestserver = system.setting('webpagetestserver').get('value')
    if webpagetestserver is None:
        webpagetestserver = {}
    webpagetestserver['hostname'] = external_ip
    webpagetestserver['created_at'] = datetime.timestamp(datetime.now())
    webpagetestserver['active'] = True
    system.setting('webpagetestserver').save(value=webpagetestserver)
    this.set_output(wait=True)  # wait until the docker containers are ready

    return this.success('all done')
Esempio n. 10
0
def handler(system: flow_api.System, this: flow_api.Execution):

    # (2) Set username for geonames API
    geonames_username = system.setting('geonames_username')
    if not geonames_username.exists():
        geonames_username.save(value='demo')

    username = geonames_username.get('value')

    # (3) get inputs from parent execution

    # The parent execution passed inputs to this execution, therefore we
    # don't need to specify an execution ID from which to get the inputs.
    # c.get_inputs() will capture the inputs given by the parent execution.
    countryname = this.get('input_value')['countryname']

    # (4) call the geonames API

    countrycode_response = this.connect(
        connector_type='REST',
        url=(f'http://api.geonames.org/search?'
             f'name={countryname}&'
             f'featureCode=PCLI'
             f'&type=JSON'
             f'&username={username}'
             )).run().get('output_value')['json']['geonames']

    # Check if the result contains something
    if not countrycode_response:
        # If it doesn't, we set the output to error and send back the
        # invalid country name
        this.save(output_value={'error': countryname})

    else:
        # If there is a valid response, we continue with the API calls
        countrycode = countrycode_response[0]['countryCode']
        capitalname = this.connect(
            connector_type='REST',
            url=(f'http://api.geonames.org/countryInfo?'
                 f'country={countrycode}'
                 f'&type=JSON'
                 f'&username={username}'
                 )).run().get('output_value')['json']['geonames'][0]['capital']

        capitalcoordinates_response = this.connect(
            connector_type='REST',
            url=(f'http://api.geonames.org/search?'
                 f'name={capitalname}&'
                 f'featureCode=PPLC'
                 f'&type=JSON'
                 f'&username={username}'
                 )).run().get('output_value')['json']['geonames'][0]

        # The coordinates are two values. To access them by key in the json
        # which is returned by the geonames API, we need to loop through
        # the result.
        capitalcoordinates = {
            k: float(v)
            for k, v in capitalcoordinates_response.items()
            if k in ('lat', 'lng')
        }

        # (5) Set outputs
        this.save(output_value={capitalname: capitalcoordinates})

# (6) Once we're done we end the execution.
    return this.success(message='all done')
Esempio n. 11
0
def handler(system: flow_api.System, this: flow_api.Execution):
    """ 
        In this example we setup a Virtual Machine (VM) in the google-cloud. 
        Then, Ubuntu will be installed and a small bash script is executed. 
        Bash scripts can be used to install more software on the VM or 
        change settings, anything you could do on a local machine in a terminal.  

        This flow also requires the two files:
        * create google cloud vm example.sh
        * create google cloud vm example.json
        In the future, you can take these as an example, adopt them and 
        upload them with a new name in Cloudomation > Files.
    """

    # This is a pop-up message form, it will ask for some information
    # The last element is an OK-button, after clicking it the
    # values are passed further and the pop-up message closes.
    form_response = system.message(
        subject='Input information to setup a VM in google-cloud',
        message_type='POPUP',
        body={
            'type':
            'object',
            'properties': {
                'connector_name': {
                    'element': 'string',
                    'type': 'string',
                    'label': 'Enter name for the connector: \n\
(This name can be searched for and is used to connect with your VM)',
                    'example': 'my-vm-on-gcloud',
                    'order': 1,
                },
                'project_id': {
                    'element': 'string',
                    'type': 'string',
                    'label': 'Enter your google cloud project-ID:',
                    'default': 'my-first-project-id',
                    'order': 2,
                },
                'machine_type': {
                    'element': 'string',
                    'type': 'string',
                    'label':
                    'Enter the machine type you want to use on google-cloud:',
                    'default': 'n1-standard-1',
                    'order': 3,
                },
                'server_location': {
                    'element': 'string',
                    'type': 'string',
                    'label':
                    'Enter the desired location of the hosting server from google-cloud:',
                    'default': 'europe-west1-b',
                    'order': 4,
                },
                'Ok': {
                    'element': 'submit',
                    'label': 'OK',
                    'type': 'boolean',
                    'order': 5,
                },
            },
            'required': [
                'connector_name',
                'project_id',
                'machine_type',
                'server_location',
            ],
        },
    ).wait().get('response')
    connector_name = form_response['connector_name']
    project_id = form_response['project_id']
    machine_type = form_response['machine_type']
    server_location = form_response['server_location']

    # generate a ssh key-pair for the setup-user:
    setup_user_key = RSA.generate(2048)
    setup_user_priv = setup_user_key.export_key().decode()
    setup_user_pub = setup_user_key.publickey().export_key(
        format='OpenSSH').decode()
    setup_user_key = None

    # load the startup script and pass the public ssh key to the script:
    vm_startup_script = system.file(
        'create google cloud vm example.sh').get_text_content()
    vm_startup_script = vm_startup_script.format(
        setup_user_pub=setup_user_pub, )

    # load the configuration for the VM and setup last parameters:
    vm_config_str = system.file(
        'create google cloud vm example.json').get_text_content()
    vm_config = json.loads(vm_config_str)
    vm_config['name'] = connector_name
    vm_config['machineType'] = \
        f'projects/{project_id}/zones/{server_location}/machineTypes/{machine_type}'
    vm_config['zone'] = f'projects/{project_id}/zones/{server_location}'
    vm_config['metadata']['items'][0]['value'] = vm_startup_script
    vm_config['disks'][0]['deviceName'] = connector_name
    vm_config['disks'][0]['initializeParams']['diskType'] = \
        f'projects/{project_id}/zones/{server_location}/diskTypes/pd-ssd'
    vm_config['networkInterfaces'][0]['subnetwork'] = \
        f'projects/{project_id}/regions/{server_location[:-2]}/subnetworks/default'
    # NOTE: two sources can be used for initial system installation:
    # a) from 'sourceSnapshot'
    # b) from 'sourceImage'
    # This flow script will install Ubuntu, using the parameter 'sourceImage'.
    # For installing a system from snapshot use the respective parameter.
    # This paramter is set under: disks / initializeParams

    # Connect to the VM
    operation = this.connect(
        'gcloud',
        name=f'launch {connector_name}',
        api_name='compute',
        api_version='v1',
        collection='instances',
        request='insert',
        params={
            'project': project_id,
            'zone': server_location,
            'body': vm_config,
        },
    ).get('output_value')['result']
    this.flow(
        'google_operation_wait',
        operation=operation,
    )

    # retrieve external IP and hostkey (required for creating a SSH connector):
    for _ in range(60):
        try:
            instance_list = this.connect(
                'gcloud',
                name=f'find {connector_name}',
                api_name='compute',
                api_version='v1',
                collection='instances',
                request='list',
                params={
                    'project': project_id,
                    'zone': server_location,
                    'filter': f'name={connector_name}',
                },
            ).get('output_value')
        except flow_api.DependencyFailedError:
            this.sleep(1)
        if len(instance_list['result'].get('items', [])) != 0:
            break
        this.sleep(1)
    else:
        return this.error('did not find VM within 60 tries')
    external_ip = instance_list['result']['items'][0]['networkInterfaces'][0][
        'accessConfigs'][0]['natIP']

    last_try_guest_attribute = None
    for _ in range(60):
        if last_try_guest_attribute is not None:
            last_try_guest_attribute.archive()
        try:
            last_try_guest_attribute = this.connect(
                'gcloud',
                name=f'get {connector_name} hostkey',
                api_name='compute',
                api_version='v1',
                collection='instances',
                request='getGuestAttributes',
                params={
                    'project': project_id,
                    'zone': server_location,
                    'instance': connector_name,
                    'queryPath': 'hostkeys/ssh-rsa',
                },
                wait=system.return_when.ALL_ENDED,
            )
        except Exception:
            this.sleep(1)
        else:
            if last_try_guest_attribute.load('status') == 'ENDED_SUCCESS':
                break
    else:
        return this.error('did not find hostkey within 60 tries')
    hostkey = last_try_guest_attribute.get(
        'output_value')['result']['queryValue']['items'][0]['value']

    # setup the SSH connector for the VM
    system.connector(f'{connector_name}-ssh').save(
        connector_type='SSH',
        value={
            'hostname': external_ip,
            'hostkey': hostkey,
            'username': '******',
            'key': setup_user_priv,
            'script_timeout': 600,
            'interpreter': '/usr/bin/env bash -ex',
        },
    )

    # Run a little bash script on the VM:
    last_try_script_echo = None
    for _ in range(20):
        if last_try_script_echo is not None:
            last_try_script_echo.archive()
        try:
            last_try_script_echo = this.connect(
                f'{connector_name}-ssh',
                script="""
                    echo "litte test content" >> ~/test_file.txt
                    cat ~/test_file.txt
                    rm ~/test_file.txt
                    """,
                name='run test script on new vm',
            )
        except:
            this.sleep(1)
        else:
            if last_try_script_echo.load('status') == 'ENDED_SUCCESS':
                break
    else:
        return this.error('Failed to run the little test script.')
    script_echo = last_try_script_echo.get('output_value')['report']
    this.set_output(f'Echo from test-script:\n{script_echo}')

    # Save flow state and pause, manually resume this script's execution to remove the VM
    this.save(
        message=
        'The VM was successfully deployed. Resume this execution to remove the VM'
    )
    this.pause()

    # Remove the VM
    operation = this.connect(
        'gcloud',
        name=f'remove {connector_name}',
        api_name='compute',
        api_version='v1',
        collection='instances',
        request='delete',
        params={
            'project': project_id,
            'zone': server_location,
            'instance': connector_name,
        },
    ).get('output_value')['result']
    this.flow(
        'google_operation_wait',
        operation=operation,
    )

    return this.success('all done')
Esempio n. 12
0
def handler(system: flow_api.System, this: flow_api.Execution):
    """
    Clone a git repository, create Cloudomation objects from the files.
    All .py files in the flows/ subdirectory will be stored as Flows.
    All .yaml files in the settings/ subdirectory will be stored as Settings.
    All files in the files/ subdirectory will be stored as Files.
    
    A pop-up message form will request all necessary user information for the repository.
    
    The inputs are not checked for validity!
    """

    # This is a pop-up message form, it will ask for some information
    # The last element is an OK-button, after clicking it the
    # values are passed further and the pop-up message closes.
    git_login_form_response = system.message(
        subject='Input Git repository information to connect',
        message_type='POPUP',
        body={
            'type': 'object',
            'properties': {
                'username': {
                    'element': 'string',
                    'type': 'string',
                    'label': 'Enter your username for the GIT repository:',
                    'order': 1,
                },
                'password': {
                    'element': 'password',
                    'type': 'string',
                    'label': 'Enter your password for the GIT repository:',
                    'order': 2,
                },
                # Here an example input is shown in the field, but its value is empty:
                'repository url': {
                    'element': 'string',
                    'type': 'string',
                    'label':
                    'Enter the URL for the repository you want to clone into Cloudomation:',
                    'example': 'https://repository.url/user/repository.git',
                    'order': 3,
                },
                # Here a default value is provided with the field:
                'reference': {
                    'element': 'string',
                    'type': 'string',
                    'label':
                    'Enter the "branch" you want to clone from or a "commit sha":',
                    'default': 'develop',
                    'order': 4,
                },
                'Ok': {
                    'element': 'submit',
                    'label': 'OK',
                    'type': 'boolean',
                    'order': 5,
                },
            },
            'required': [
                'username',
                'password',
                'repository url',
                'reference',
            ],
        },
    ).wait().get('response')
    git_username = git_login_form_response['username']
    git_password = git_login_form_response['password']
    git_repo_url = git_login_form_response['repository url']
    git_reference = git_login_form_response['reference']

    # Now we use a "GIT" task.
    # The git 'get' command fetches the content of the repository.
    # Since no files_path is passed, the files will be returned in the
    # output_value of the task
    files = None
    try:
        files = this.connect(
            connector_type='GIT',
            command='get',
            repository_url=git_repo_url,
            # httpCookie=repo_info.get('httpCookie'),
            ref=git_reference,
            username=git_username,
            password=git_password,
        ).get('output_value')['files']
    except flow_api.exceptions.DependencyFailedError as err:
        # send the error message to the output of this execution:
        return this.error(repr(err))

    # iterate over all files and save them on Cloudomation:
    for file_ in files:
        # split the path and filename
        path, filename = os.path.split(file_['name'])
        # split the filename and file extension
        name, ext = os.path.splitext(filename)
        if 'flows' in path and ext == '.py':
            # decode the base64 file content to text
            text_content = base64.b64decode(file_['content']).decode()
            # create or update Flow object
            system.flow(name).save(script=text_content)
        elif 'settings' in path and ext == '.yaml':
            # decode the base64 file content to text
            text_content = base64.b64decode(file_['content']).decode()
            # load the yaml string in the file content
            value = yaml.safe_load(text_content)
            # create or update Setting object
            system.setting(name).save(value=value)
        elif 'files' in path:
            # create or update File object
            # we use the name with the extension
            # pass the base64 encoded binary file content directly
            system.file(filename).save(content=file_['content'],
                                       convert_binary=False)

    return this.success('all done')
Esempio n. 13
0
def handler(system: flow_api.System, this: flow_api.Execution):
    inputs = this.get('input_value') or {}
    message_id = inputs.get('message_id')

    if message_id is None:
        defaults = {
            'interval': 60,
            'wait': True,
        }
        if 'flow_name' in inputs:
            defaults['flow_name'] = inputs['flow_name']
        if 'flow_id' in inputs:
            defaults['flow_name'] = system.flow(inputs['flow_id'],
                                                by='id').get('name')

        message = system.message(
            subject='Recurring execution',
            body={
                'type': 'object',
                'properties': {
                    'flow_name': {
                        'label':
                        'Name of the flow which should be started recurring',
                        'element': 'string',
                        'type': 'string',
                        'example': defaults.get('flow_name'),
                        'default': defaults.get('flow_name'),
                        'order': 1,
                    },
                    'interval': {
                        'label': 'Interval of recurring execution in seconds',
                        'element': 'number',
                        'type': 'number',
                        'example': defaults['interval'],
                        'default': defaults['interval'],
                        'order': 2,
                    },
                    'wait': {
                        'label': 'Wait for child executions to finish',
                        'element': 'toggle',
                        'type': 'boolean',
                        'default': defaults['wait'],
                        'order': 3,
                    },
                    'max_iterations': {
                        'label':
                        'Maximum number of iterations (unlimited if omitted)',
                        'element': 'number',
                        'type': 'number',
                        'order': 4,
                    },
                    'start': {
                        'label': 'Start recurring',
                        'element': 'submit',
                        'type': 'boolean',
                        'order': 5,
                    },
                },
                'required': [
                    'flow_name',
                    'interval',
                ],
            },
        )
        message_id = message.get('id')
        this.save(output_value={
            'message_id': message_id,
        })
        this.flow(
            'Recurring',
            name='Recurring execution',
            message_id=message_id,
            wait=False,
        )
        return this.success('requested details')

    message = system.message(message_id)
    response = message.wait().get('response')
    this.log(response=response)
    flow_name = response['flow_name']
    interval = response['interval']
    wait = response['wait']
    max_iterations = response.get('max_iterations')
    this.save(name=f'Recurring {flow_name}')

    # Loop
    iterations = 0
    start = time.time()
    while max_iterations is None or iterations < max_iterations:
        iterations += 1
        if max_iterations:
            this.save(message=f'iteration {iterations}/{max_iterations}')
        else:
            this.save(message=f'iteration {iterations}')
        # Start child execution
        inputs = {
            'start': start,
            'iterations': iterations,
            'max_iterations': max_iterations,
        }
        child = this.flow(flow_name,
                          inputs=inputs,
                          name=f'{flow_name} iteration #{iterations}',
                          run=False)
        if wait:
            try:
                child.run()
            except Exception:
                this.log(f'iteration #{iterations} failed')
        else:
            child.run_async()
        if max_iterations is not None and iterations >= max_iterations:
            break
        if wait:
            now = time.time()
            scheduled = datetime.fromtimestamp(now + interval, timezone.utc)
        else:
            scheduled = datetime.fromtimestamp(start + (iterations * interval),
                                               timezone.utc)
        scheduled_ts = scheduled.isoformat(sep=' ', timespec='minutes')
        this.save(message=scheduled_ts)
        if wait:
            this.sleep(interval)
        else:
            this.sleep_until(start + (iterations * interval))

    return this.success(f'started {iterations} iterations')
Esempio n. 14
0
def handler(system: flow_api.System, this: flow_api.Execution):
    # (1) Set up a Cloudomation webhook
    # which triggers a flow script which synchronises settings and flow scripts
    # from a github repo

    # check if the webhook exists
    if not system.setting('client.webhook.github_sync').exists():
        # If it doesn't exist, we create it.
        # First, we ask the user for a github authorisation key:
        c_webhook_key_request = system.message(
            subject='Github authorisation key',
            message_type='POPUP',
            body={
                'type': 'object',
                'properties': {
                    'key': {
                        'label':
                        (f'Please specify an authorization key for the '
                         'Cloudomation webhook. Can be any alphanumeric '
                         'string.'),
                        'element':
                        'string',
                        'type':
                        'string',
                        'order':
                        1,
                    },
                    'start': {
                        'label': 'OK',
                        'element': 'submit',
                        'type': 'boolean',
                        'order': 2,
                    },
                },
                'required': [
                    'key',
                ],
            },
        ).wait()

        c_webhook_key = c_webhook_key_request.get('response')['key']

        # Second, we read out the user's name from this execution record.
        # Note that this will set up a webhook with the user rights of
        # the user who is executing this flow script.
        cloudomation_username = system.get_own_user().get('name')

        system.setting(name='client.webhook.github_sync',
                       value={
                           "flow_name": "sync_from_github",
                           "user_name": cloudomation_username,
                           "key": c_webhook_key
                       })
    else:
        c_webhook_key = (
            system.setting('client.webhook.github_sync').load('value')['key'])

    # (2) Set up a github webhook
    # which pushes events from a repository to the Cloudomation webhook

    # check if github info setting exists
    # if it doesn't, start the flow script to request info from user
    if not system.setting('github_info').exists():
        this.flow('request_github_info')

    github_info = system.setting('github_info').load('value')
    github_username = github_info['github_username']
    github_repo_name = github_info['github_repo_name']
    github_token = github_info['github_token']

    # Check if the webhook already exists. To do that, we call the
    # github REST API to list all existing webhooks for your repository.
    github_webhook_endpoint = (f'https://api.github.com/repos/'
                               f'{github_username}/'
                               f'{github_repo_name}/'
                               f'hooks')

    list_github_webhooks = this.connect(
        connector_type='REST',
        url=github_webhook_endpoint,
        headers={'Authorization': f'token {github_token}'},
    )

    # we get the response
    github_list_webhook = list_github_webhooks.get('output_value')['json']

    # and we check if our webhook already exists
    cloudomation_client_name = system.get_own_client().get('name')
    c_webhook_url = (f'https://app.cloudomation.com/api/latest/webhook/'
                     f'{cloudomation_client_name}/'
                     f'github_sync?'
                     f'key={c_webhook_key}')

    webhook_exists = False

    for webhook in github_list_webhook:
        if webhook['config']['url'] == c_webhook_url:
            webhook_exists = True
            break

    this.set_output('webhook_exists', webhook_exists)

    # if the webhook doesn't already exist, we create it
    if not webhook_exists:
        this.connect(
            connector_type='REST',
            url=github_webhook_endpoint,
            method='POST',
            data={
                'events': ['push'],
                'config': {
                    'url': c_webhook_url,
                    'content_type': 'json'
                }
            },
            headers={'Authorization': f'token {github_token}'},
        )
        this.set_output('webhook_created', 'true')

    return this.success('All done - Cloudomation and github webhooks set up.')
Esempio n. 15
0
def handler(system: flow_api.System, this: flow_api.Execution):
    """
    Execute an ansible playbook


    Copy a playbook to an ansible host and execute it. This flow script is
    meant to be a boilerplate and should be adjusted to your needs.


    Parameters:
        ansible-host: the name of the setting which contains the connection
            information for the ansible master host. Here's a template:
            ```yaml
            hostname: TODO
            hostkey: ssh-rsa TODO
            username: TODO
            key: |
              -----BEGIN RSA PRIVATE KEY-----
              TODO
              -----END RSA PRIVATE KEY-----
            ```
        playbook: The path & name of the ansible playbook yaml file
        become: If true, execute the playbook as root
        files: A list of files which will be copied to the ansible host to
            be used by the playbook


    Returns:
        The report of the ansible-playbook invocation
    """
    inputs = this.get('input_value')
    ansible_host_setting = inputs.get('ansible-host', 'ansible-host')
    target_hosts = inputs.get('target')
    files = inputs.get('files', [])
    playbook = inputs['playbook']
    script = f'ansible-playbook {playbook}'
    become = inputs.get('become')
    if become:
        script += ' -b'
    if target_hosts:
        script += f' -i "{target_hosts},"'
    ansible_host = system.setting(ansible_host_setting).get('value')
    tasks = []
    for file in files:
        tasks.append(
            this.connect(
                connector_type='SCP',
                **ansible_host,
                src=f'cloudomation:ansible-integration-demo/{file}',
                dst=file,
                wait=False,
            ))
    tasks.append(
        this.connect(
            connector_type='SCP',
            **ansible_host,
            src=f'cloudomation:ansible-integration-demo/{playbook}',
            dst=playbook,
            wait=False,
        ))
    this.wait_for(*tasks)
    report = this.connect(
        connector_type='SSH',
        **ansible_host,
        script=script,
    ).get('output_value')['report']
    this.save(output_value={'report': report})
    return this.success('all done')
Esempio n. 16
0
def handler(system: flow_api.System, this: flow_api.Execution):

# (2) use settings
    # To use the geonames REST API, it is required to register an application,
    # which is identified via a user name that has to be provided with every
    # REST call. The best way to manage parameters like this is to store them
    # as Cloudomation settings. In this way, usernames can be changed easily in
    # the settings view without having to edit the flow scripts which use it.
    # For this example flow script, we will first check if the setting exists
    # and if it doesn't, we will create it and fill it with a demo user name.
    # This is not recommended best practice - the user name should not be hard
    # coded into the flow script. Another option would be to check if the
    # setting exists and if not, as the user to input a user name. However for
    # the purposes of this example flow script, we will create a setting that
    # contains the user name to demonstate how to use settings.
    # NOTE that settings are not intended for passwords or other sensible
    # information. For passwords, we recommend the use of Hashicorp Vault.

    # we check if there is a setting with the key geonames_username
    geonames_username = system.setting('geonames_username')
    if not geonames_username.exists():
        # if the setting doesn't exist, we set it to the demo user name.
        # if it does exist, this line will be skipped.
        geonames_username.save(value='demo')

    # then we read out the value of the geonames_username setting
    # note that this is not related to the previous check. We need to read
    # the setting in any case, independent of whether it was set by the script,
    # or before the script was run
    username = geonames_username.get('value')

    # (3) use INPUT task

    # We want to provide the user with information about a country. We ask for
    # a country and explain what we are planning to do.
    countryname_request = this.connect(
        connector_type='INPUT',
        # the INPUT function has one required parameter: the request. This is
        # what users will see and respond to.
        request=(
            'This is a country information service. If you input a country '
            'name, I will tell you a few things about this country. Please '
            'only type one country at a time. Which country would you like '
            'to learn about?'
        )
    )
    # now, the variable countryname_request contains the *execution object*
    # of the input task. From the execution object, we can get e.g. its ID,
    # its status, its runtime etc. - and most importantly, its outputs.
    # What kind of outputs you can get from an execution object depends on
    # the execution, i.e. which task you ran.

    # we get the outputs and store them in a variable 'countryname_results'
    countryname_result = countryname_request.get('output_value')

    # because we want to learn about the INPUT task, we log all the outputs to
    # see what we get back. This is not required, we do this just for learning
    # purposes. Take a look at the log to see what the INPUT task returns.
    this.log('Outputs of the INPUT task:', countryname_result)

    # It returns a JSON element and we can access the individual elements with
    # standard Python syntax: we are only interested in the user's response,
    # which should be a country name. We store it in the variable 'countryname'.
    countryname = countryname_result['response']

    # (4) use REST task
    # Now, we want to get some information about the country. To request
    # information about a country, the geonames API requires the ISO-2 country
    # code of that country, so our first request will be to get the ISO-2
    # country code for the country name. This is also an opportunity for us to
    # see if the user input a valid country name - if they didn't, this request
    # will fail.

    # Here, we use the two previously defined paramenters: the username we read
    # from a setting, and the country name from the user input. Note that you
    # can use standard python string formatting functionality for defining the
    # URL with parameters.
    countrycode_request = this.connect(
        connector_type='REST',
        url=(
            f'http://api.geonames.org/search?'
            f'name={countryname}&'
            f'featureCode=PCLI'
            f'&type=JSON'
            f'&username={username}'
        )
    )
    # again, we execute the REST task right away and store the resulting
    # execution object in a variable: countrycode_request

    # now we get the response returned from the REST call
    countrycode_response = countrycode_request.get('output_value')

    # First, we need to check if the REST call returned anything. If it didn't,
    # we will end the execution and inform the user. If it did, we continue.

    # because we want to learn about the REST task, we log the response
    # returned by the REST call. Take a look at the log to see what is returend
    # by the REST call. It is again a JSON whose elements we can access.
    this.log('Outputs of the country code REST task:', countrycode_response)

    # the geonames call returns the number of search results, which we access:
    response_count = countrycode_response['json']['totalResultsCount']

    # check if the REST call returned 0 results
    if response_count < 1:
        # if it is 0, we will end the execution and tell the user that we
        # coudn't find the country. If the REST call did return a result,
        # this line will be skipped and the script will continue.
        return this.error('We could not find the country you named.')

    # we access the country code. If you look at the response which we logged,
    # you will see that the JSON is nested so we need to go through a few
    # layers before we get to the country code.
    countrycode = countrycode_response['json']['geonames'][0]['countryCode']

    # (5) another REST task

    # now that we have the country code, we want to get some information
    # about the country
    countryinfo_request = this.connect(
        connector_type='REST',
        url=(
            f'http://api.geonames.org/countryInfo?'
            f'country={countrycode}'
            f'&type=JSON'
            f'&username={username}'
        )
    )

    # we get the ouput from the execution object
    countryinfo_result = countryinfo_request.get('output_value')['json']['geonames'][0]

    # because we want to learn about the REST task, we log the response
    # returned by the REST call. Take a look at the log to see what is returend
    # by the REST call. It is again a JSON whose elements we can access.
    this.log('Outputs of the country information REST task:', countryinfo_result)

    # (6) give the user some information about the country and ask for  feedback

    # now that we already saw how the INPUT task works, we chain it all
    # together, directly getting the output and not storing the execution
    # object in a separate variable.
    user_feedback = this.connect(
        connector_type='INPUT',
        request=(f'Here is some information about  {countryname}. It is '
                 f'located in {countryinfo_result["continentName"]}, '
                 f'its capital is {countryinfo_result["capital"]}, '
                 f'it has a population of {countryinfo_result["population"]}, '
                 f'and an area of {countryinfo_result["areaInSqKm"]} '
                 f'square kilometers. Did you like this information?')
        ).run(
    ).get('output_value')['response']

    # (7) end execution

    # we add the user feedback to the end message
    return this.success(
        message=(
            f'Country info provided. Did the user like the information? '
            f'{user_feedback}')
    )
Esempio n. 17
0
def handler(system: flow_api.System, this: flow_api.Execution):
    inputs = this.get('input_value') or {}
    message_id = inputs.get('message_id')

    if message_id is None:
        defaults = {
            'delay': '60',
        }
        if 'flow_name' in inputs:
            defaults['flow_name'] = inputs['flow_name']
        if 'flow_id' in inputs:
            defaults['flow_name'] = system.flow(inputs['flow_id'], by='id').get('name')

        message = system.message(
            subject='Delayed execution',
            body={
                'type': 'object',
                'properties': {
                    'flow_name': {
                        'label': 'Name of the flow which should be started',
                        'element': 'string',
                        'type': 'string',
                        'example': defaults.get('flow_name'),
                        'default': defaults.get('flow_name'),
                        'order': 1,
                    },
                    'label': {
                        'description': 'You can either specify a time when the execution should be started, or a delay in seconds',
                        'element': 'markdown',
                        'order': 2,
                    },
                    'time': {
                        'label': 'Time when the child execution should be started',
                        'element': 'time',
                        'type': 'string',
                        'format': 'time',
                        'order': 3,
                    },
                    'delay': {
                        'label': 'Delay in seconds after which the child execution should be started',
                        'element': 'number',
                        'type': 'number',
                        'example': defaults['delay'],
                        'default': defaults['delay'],
                        'order': 4,
                    },
                    'start': {
                        'label': 'Start delayed',
                        'element': 'submit',
                        'type': 'boolean',
                        'order': 5,
                    },
                },
                'required': [
                    'flow_name',
                ],
            },
        )
        message_id = message.get('id')
        this.save(output_value={
            'message_id': message_id,
        })
        this.flow(
            'Delayed',
            name='Delayed execution',
            message_id=message_id,
            wait=False,
        )
        return this.success('requested details')

    message = system.message(message_id)
    response = message.wait().get('response')
    this.log(response=response)
    flow_name = response['flow_name']
    scheduled_at = response.get('time')
    delay = response.get('delay')
    this.save(name=f'Delayed {flow_name}')

    if scheduled_at is not None:
        scheduled_at_t = datetime.datetime.strptime(scheduled_at, '%H:%M:%S%z').timetz()
        this.log(scheduled_at_t=scheduled_at_t)
        now = datetime.datetime.now(datetime.timezone.utc)
        this.log(now=now)
        today = now.date()
        this.log(today=today)
        scheduled = datetime.datetime.combine(today, scheduled_at_t)
        this.log(scheduled=scheduled)
        if scheduled < now:  # tomorrow
            tomorrow = today + datetime.timedelta(days=1)
            scheduled = datetime.datetime.combine(tomorrow, scheduled_at_t)
            this.log(scheduled=scheduled)
        scheduled_ts = scheduled.isoformat(sep=' ', timespec='minutes')
        this.log(scheduled_ts=scheduled_ts)
        delta_sec = (scheduled - now).total_seconds()
        this.log(delta_sec=delta_sec)
        this.save(message=scheduled_ts)
        this.sleep(delta_sec)
    elif delay is not None:
        this.save(message=f'sleeping for {delay} seconds')
        this.log(f'sleeping for {delay} seconds')
        this.sleep(delay)
    else:
        return this.error('Missing response for "time" or "delay"')

    this.flow(
        flow_name,
        inputs=inputs,
        name=f'delayed {flow_name}',
        wait=False,
    )

    return this.success(f'successfully started {flow_name}')
Esempio n. 18
0
def handler(system: flow_api.System, this: flow_api.Execution):
    inputs = this.get('input_value')
    gcloud_connector = inputs.get('gcloud_connector')
    project_id = system.connector(gcloud_connector).get('value').get(
        'key').get('project_id')
    if project_id is None or gcloud_connector is None:
        return this.error('Missing inputs')

    webpagetestserver_template_setting = system.setting(
        'webpagetest-server-template')
    webpagetestserver_template_setting.acquire(timeout=None)

    instance_list = this.connect(
        gcloud_connector,
        name='find webpagetest-server-template',
        api_name='compute',
        api_version='v1',
        collection='instances',
        request='list',
        params={
            'filter': 'name=webpagetest-server-template',
            'project': project_id,
            'zone': 'europe-west1-b',
        },
    ).get('output_value')
    if len(instance_list['result'].get('items', [])) > 0:
        this.connect(
            gcloud_connector,
            name='delete webpagetest-template',
            api_name='compute',
            api_version='v1',
            collection='instances',
            request='delete',
            params={
                'instance': 'webpagetest-server-template',
                'project': project_id,
                'zone': 'europe-west1-b',
            },
        )
        for _ in range(60):
            instance_list = this.connect(
                gcloud_connector,
                name='find webpagetest-server-template',
                api_name='compute',
                api_version='v1',
                collection='instances',
                request='list',
                params={
                    'filter': f'name=webpagetest-server-template',
                    'project': project_id,
                    'zone': 'europe-west1-b',
                },
            ).get('output_value')
            if len(instance_list['result'].get('items', [])) == 0:
                break
            this.sleep(1)
        else:
            return this.error(
                'old webpagetest-server-template still exists after 60 tries')

    images = this.connect(gcloud_connector,
                          name='find ubuntu image name',
                          api_name='compute',
                          api_version='v1',
                          collection='images',
                          request='list',
                          params={
                              'project': 'ubuntu-os-cloud'
                          }).load('output_value')['result']['items']
    image_name = None
    for image in images:
        with contextlib.suppress(KeyError):
            if image['deprecated']['state'] == 'DEPRECATED':
                continue
        if image['name'].startswith('ubuntu-1804-bionic-'):
            image_name = image['name']
            break

    setup_user_key = RSA.generate(2048)
    setup_user_priv = setup_user_key.export_key().decode()
    setup_user_pub = setup_user_key.publickey().export_key(
        format='OpenSSH').decode()
    setup_user_key = None

    webpagetest_user_key = RSA.generate(2048)
    webpagetest_user_priv = webpagetest_user_key.export_key().decode()
    webpagetest_user_pub = webpagetest_user_key.publickey().export_key(
        format='OpenSSH').decode()
    webpagetest_user_key = None

    webpagetest_server_startup_script = system.file(
        'webpagetest-server-template-startup.sh').get_text_content()
    webpagetest_server_startup_script = webpagetest_server_startup_script.format(
        setup_user_pub=setup_user_pub,
        webpagetest_user_pub=webpagetest_user_pub,
    )
    webpagetest_server_config_str = system.file(
        'webpagetest-server-template.json').get_text_content()
    webpagetest_server_config_str.replace('{PROJECT_ID}', project_id)
    webpagetest_server_config = json.loads(webpagetest_server_config_str)
    webpagetest_server_config['metadata']['items'][0][
        'value'] = webpagetest_server_startup_script
    webpagetest_server_config['name'] = 'webpagetest-server-template'
    webpagetest_server_config['disks'][0][
        'deviceName'] = 'webpagetest-server-template'
    webpagetest_server_config['disks'][0]['initializeParams'][
        'sourceImage'] = f'projects/ubuntu-os-cloud/global/images/{image_name}'

    operation = this.connect(
        gcloud_connector,
        name='launch webpagetest-server-template',
        api_name='compute',
        api_version='v1',
        collection='instances',
        request='insert',
        params={
            'body': webpagetest_server_config,
            'project': project_id,
            'zone': 'europe-west1-b',
        },
    ).get('output_value')['result']
    this.flow(
        'google_operation_wait',
        operation=operation,
    )

    try:
        for _ in range(60):
            instance_list = this.connect(
                gcloud_connector,
                name='find webpagetest-server-template',
                api_name='compute',
                api_version='v1',
                collection='instances',
                request='list',
                params={
                    'filter': 'name=webpagetest-server-template',
                    'project': project_id,
                    'zone': 'europe-west1-b',
                },
            ).get('output_value')
            if len(instance_list['result'].get('items', [])) != 0:
                break
            this.sleep(1)
        else:
            return this.error(
                'did not find webpagetest server template within 60 tries')
        external_ip = instance_list['result']['items'][0]['networkInterfaces'][
            0]['accessConfigs'][0]['natIP']

        failed_tries = []
        for _ in range(60):
            this.sleep(1)
            guest_attribute_task = this.connect(
                gcloud_connector,
                name='get webpagetest-server-template hostkey',
                api_name='compute',
                api_version='beta',
                collection='instances',
                request='getGuestAttributes',
                params={
                    'instance': 'webpagetest-server-template',
                    'queryPath': 'hostkeys/ssh-rsa',
                    'project': project_id,
                    'zone': 'europe-west1-b',
                },
                wait=system.return_when.ALL_ENDED,
            )
            if guest_attribute_task.load('status') == 'ENDED_SUCCESS':
                break
            failed_tries.append(guest_attribute_task)
        else:
            return this.error('did not find hostkey within 60 tries')
        for failed_try in failed_tries:
            failed_try.delete()
        hostkey = guest_attribute_task.get(
            'output_value')['result']['queryValue']['items'][0]['value']

        system.connector('webpagetestserver-setup').save(
            connector_type='SSH',
            value={
                'key': setup_user_priv,
                'username': '******',
                'hostname': external_ip,
                'hostkey': hostkey,
            },
        )
        system.connector('webpagetestserver').save(
            connector_type='SSH',
            value={
                'key': webpagetest_user_priv,
                'username': '******',
                'hostname': external_ip,
                'hostkey': hostkey,
            },
        )
        system.connector('webpagetestserver-copy').save(
            connector_type='SCP',
            value={
                'key': webpagetest_user_priv,
                'username': '******',
                'hostname': external_ip,
                'hostkey': hostkey,
            },
        )

        webpagetestserver_template = {
            'hostname': external_ip,
            'hostkey': hostkey,
            'setup-user': {
                'username': '******',
                'ssh-key': setup_user_priv,
                'public-key': setup_user_pub,
            },
            'webpagetest-user': {
                'username': '******',
                'ssh-key': webpagetest_user_priv,
                'public-key': webpagetest_user_pub,
            },
        }

        for _ in range(10):
            try:
                this.connect(
                    'webpagetestserver-setup',
                    script="""
                        sudo apt-get update
                        sudo apt-get install -y --no-install-recommends python-pip python3-pip
                        sudo apt-get install -y --no-install-recommends build-essential python3-dev iftop iotop zip unzip
                        # DO NOT remove python3-cryptography! or instance will not be able to setup networking on boot
                        # sudo apt-get remove -y python3-cryptography
                        """,
                    name='install packages',
                )
            except Exception:
                this.sleep(3)
            else:
                break
        else:
            return this.error(
                'failed to ssh to webpagetestserver-template within 10 tries')

        this.connect(
            'webpagetestserver',
            script="""
                pip install wheel setuptools
                pip3 install wheel setuptools
                pip3 install alembic psycopg2-binary
                """,
            name='install pip packages',
        )

        this.connect(
            'webpagetestserver-setup',
            script="""
                set +e
                ssh -V
                ret=$?
                set -e
                if [ "$ret" -ne "0" ]; then
                    sudo apt-get install -y --no-install-recommends openssh-client
                fi
                """,
            name='install openssh client',
        )

        this.connect(
            'webpagetestserver-setup',
            script="""
                set +e
                jq -V
                ret=$?
                set -e
                if [ "$ret" -ne "0" ]; then
                    sudo apt-get install -y --no-install-recommends jq
                fi
                set +e
                jq -V
                ret=$?
                set -e
                if [ "$ret" -ne "0" ]; then
                    sudo snap install jq
                fi
                """,
            name='install jq',
        )

        this.connect(
            'webpagetestserver-setup',
            script="""
                set +e
                yq -V
                ret=$?
                set -e
                if [ "$ret" -ne "0" ]; then
                    sudo snap install yq
                fi
                """,
            name='install yq',
        )

        this.connect(
            'webpagetestserver-setup',
            script="""
                set +e
                docker --version
                ret=$?
                set -e
                if [ "$ret" -ne "0" ]; then
                    curl -fsSL https://get.docker.com -o get-docker.sh
                    sh get-docker.sh
                    RELEASE=$(lsb_release -is | tr '[:upper:]' '[:lower:]')
                    sudo apt-get install -y --no-install-recommends \\
                        apt-transport-https \\
                        ca-certificates \\
                        curl \\
                        gnupg2 \\
                        software-properties-common
                    curl -fsSL https://download.docker.com/linux/${RELEASE}/gpg | sudo apt-key add -
                    sudo add-apt-repository \\
                        "deb [arch=amd64] https://download.docker.com/linux/${RELEASE} \\
                        $(lsb_release -cs) \\
                        stable"
                    sudo apt-get update
                    sudo apt-get install -y --no-install-recommends docker-ce
                fi
                sudo usermod -a -G docker webpagetest-user
                """,
            name='install docker',
        )

        this.connect(
            'webpagetestserver',
            name='create webpagetest folders',
            script="""
                mkdir -p webpagetest/server
                mkdir -p webpagetest/agent
            """,
        )

        this.connect(
            'webpagetestserver-copy',
            name='copy webpagetest server Dockerfile',
            src='cloudomation:webpagetest-server-Dockerfile',
            dst='webpagetest/server/Dockerfile',
        )

        this.connect(
            'webpagetestserver-copy',
            name='copy webpagetest server locations.ini',
            src='cloudomation:webpagetest-server-locations.ini',
            dst='webpagetest/server/locations.ini',
        )

        this.connect(
            'webpagetestserver-copy',
            name='copy webpagetest agent Dockerfile',
            src='cloudomation:webpagetest-agent-Dockerfile',
            dst='webpagetest/agent/Dockerfile',
        )

        this.connect(
            'webpagetestserver-copy',
            name='copy webpagetest agent script.sh',
            src='cloudomation:webpagetest-agent-script.sh',
            dst='webpagetest/agent/script.sh',
        )

        this.connect(
            'webpagetestserver',
            name='create webpagetest server and agent',
            input_value={'script_timeout': 600},
            script="""
                pushd webpagetest/server
                docker build -t wpt-server .
                docker run -d -p 4000:80 --restart unless-stopped wpt-server
                popd
                pushd webpagetest/agent
                chmod u+x script.sh
                docker build -t wpt-agent .
                docker run -d -p 4001:80 \\
                    --restart unless-stopped \\
                    --network="host" \\
                    -e "SERVER_URL=http://localhost:4000/work/" \\
                    -e "LOCATION=Test" \\
                    wpt-agent
            """,
        )

        this.connect(
            gcloud_connector,
            name='stop webpagetest-server-template',
            api_name='compute',
            api_version='v1',
            collection='instances',
            request='stop',
            params={
                'instance': 'webpagetest-server-template',
                'project': project_id,
                'zone': 'europe-west1-b',
            },
        )

        try:
            operation = this.connect(
                gcloud_connector,
                name='delete old webpagetest-server-template snapshot',
                api_name='compute',
                api_version='v1',
                collection='snapshots',
                request='delete',
                params={
                    'snapshot': 'webpagetest-server-template',
                    'project': project_id,
                },
            ).get('output_value')['result']
            this.flow(
                'google_operation_wait',
                operation=operation,
            )
        except Exception:
            pass

        today = date.today()
        operation = this.connect(
            gcloud_connector,
            name='create webpagetest-server-template snapshot',
            api_name='compute',
            api_version='v1',
            collection='disks',
            request='createSnapshot',
            params={
                'disk': 'webpagetest-server-template',
                'body': {
                    'name': 'webpagetest-server-template',
                    'description':
                    f'auto-generated at {today} by setup webpagetest server',
                    'storageLocations': ['europe-west1'],
                },
                'project': project_id,
                'zone': 'europe-west1-b',
            },
        ).get('output_value')['result']
        this.flow(
            'google_operation_wait',
            operation=operation,
        )

        webpagetestserver_template.pop('hostname')
        webpagetestserver_template.pop('hostkey')
        webpagetestserver_template_setting.save(
            value=webpagetestserver_template)
        webpagetestserver_template_setting.release()

    except Exception as ex:
        this.save(message=repr(ex))
        if this.get('input_value').get('interactive'):
            this.sleep(120)
            # this.pause()
        raise
    finally:
        instance_list = this.connect(
            gcloud_connector,
            name='find webpagetest-server-template',
            api_name='compute',
            api_version='v1',
            collection='instances',
            request='list',
            params={
                'filter': f'name=webpagetest-server-template',
                'project': project_id,
                'zone': 'europe-west1-b'
            },
        ).get('output_value')
        if len(instance_list['result'].get('items', [])) > 0:
            this.connect(
                gcloud_connector,
                name='delete webpagetest-server-template',
                api_name='compute',
                api_version='v1',
                collection='instances',
                request='delete',
                params={
                    'instance': 'webpagetest-server-template',
                    'project': project_id,
                    'zone': 'europe-west1-b',
                },
            )
            while True:
                instance_list = this.connect(
                    gcloud_connector,
                    name='find webpagetest-server-template',
                    api_name='compute',
                    api_version='v1',
                    collection='instances',
                    request='list',
                    params={
                        'filter': 'name=webpagetest-server-template',
                        'project': project_id,
                        'zone': 'europe-west1-b',
                    },
                ).get('output_value')
                if len(instance_list['result'].get('items', [])) == 0:
                    break
                this.sleep(1)

    return this.success('all done')
Esempio n. 19
0
def handler(system: flow_api.System, this: flow_api.Execution):
    """
    Create a message form to ask for details and schedule a flow
    """
    inputs = this.get('input_value') or {}
    message_id = inputs.get('message_id')

    if message_id is None:
        defaults = {
            'scheduled_at_day': 1,
            'scheduled_at_time': '08:30',
        }
        try:
            defaults['flow_name'] = inputs['flow_name']
        except (KeyError, TypeError):
            pass
        message = system.message(
            subject='Monthly scheduled execution',
            body={
                'type':
                'object',
                'properties': {
                    'flow_name': {
                        'label':
                        'Name of the flow which should be scheduled monthly',
                        'element': 'string',
                        'type': 'string',
                        'example': defaults.get('flow_name'),
                        'default': defaults.get('flow_name'),
                        'order': 1,
                    },
                    'scheduled_at_day': {
                        'label':
                        'Day of the month when the child execution should be started',
                        'element': 'number',
                        'type': 'number',
                        'default': defaults['scheduled_at_day'],
                        'order': 2,
                    },
                    'scheduled_at_time': {
                        'label':
                        'Time when the child execution should be started',
                        'element': 'time',
                        'type': 'string',
                        'format': 'time',
                        'default': defaults['scheduled_at_time'],
                        'order': 3,
                    },
                    'max_iterations': {
                        'label':
                        'Maximum number of iterations (unlimited if omitted)',
                        'element': 'number',
                        'type': 'number',
                        'order': 4,
                    },
                    'start': {
                        'label': 'Start monthly schedule',
                        'element': 'submit',
                        'type': 'boolean',
                        'order': 5,
                    },
                },
                'required': [
                    'flow_name',
                    'scheduled_at_day',
                    'scheduled_at_time',
                ],
            },
        )
        message_id = message.get('id')
        this.save(output_value={
            'message_id': message_id,
        })
        this.flow(
            'Scheduled monthly',
            name='Monthly scheduled execution',
            message_id=message_id,
            wait=False,
        )
        return this.success('requested details')

    message = system.message(message_id)
    response = message.wait().get('response')
    this.log(response=response)
    flow_name = response['flow_name']
    scheduled_at_day = response['scheduled_at_day']
    scheduled_at_time = response['scheduled_at_time']
    local_tz = response.get('timezone', 'Europe/Vienna')
    max_iterations = response.get('max_iterations')
    local_tz = response.get('timezone', 'Europe/Vienna')
    this.save(name=f'Scheduled {flow_name}')

    try:
        scheduled_at_time_t = datetime.datetime.strptime(
            scheduled_at_time, '%H:%M:%S%z').timetz()
    except ValueError:
        scheduled_at_time_t = datetime.datetime.strptime(
            scheduled_at_time, '%H:%M:%S').timetz()
    this.log(scheduled_at_time_t=scheduled_at_time_t)
    iterations = 0
    start = datetime.datetime.now(datetime.timezone.utc).timestamp()
    while max_iterations is None or iterations < max_iterations:
        now = datetime.datetime.now(datetime.timezone.utc)
        if scheduled_at_time_t.tzinfo is None:
            now = now.astimezone(pytz.timezone(local_tz))
        this.log(now=now)
        today = now.date()
        this.log(today=today, day=today.day)
        scheduled_at_day_t = today.replace(day=scheduled_at_day)
        scheduled_t = datetime.datetime.combine(scheduled_at_day_t,
                                                scheduled_at_time_t)
        if scheduled_t.tzinfo is None or scheduled_t.tzinfo.utcoffset(
                scheduled_t) is None:
            scheduled_t = pytz.timezone(local_tz).localize(scheduled_t)
        if scheduled_t < now:
            scheduled_t += dateutil.relativedelta.relativedelta(months=1)
        this.log(scheduled_t=scheduled_t)

        scheduled_ts = scheduled_t.isoformat(sep=' ', timespec='minutes')
        this.log(scheduled_ts=scheduled_ts)
        delta_sec = (scheduled_t - now).total_seconds()
        this.log(delta_sec=delta_sec)
        this.save(message=scheduled_ts)
        this.sleep(delta_sec)
        iterations += 1
        this.save(message=f'iteration {iterations}/{max_iterations}')
        # Start child execution
        inputs = {
            'start': start,
            'iterations': iterations,
            'max_iterations': max_iterations,
        }
        this.flow(
            flow_name,
            inputs=inputs,
            name=f'{flow_name} iteration #{iterations}',
            wait=False,
        )
        if max_iterations is not None and iterations >= max_iterations:
            break

    return this.success(f'started {iterations} iterations')
Esempio n. 20
0
def handler(system: flow_api.System, this: flow_api.Execution):
    # ======= configuration =======

    # Required: store AWS credentials in a setting
    # The setting should contain two keys:
    # 'aws_access_key_id' and 'aws_secret_access_key'
    credentials_setting = 'aws.credentials'

    # Required: a name which is used as a prefix for all AWS resources which
    # are created in this example
    name = 'Cloudomation-aws-example'

    # Optional: The ID of the VPC to use. If you do not supply a VPC ID, the
    # default VPC will be used
    vpc_id = None

    # Optional: The ID of the security group which will be applied to the
    # new instance. For this example to work, the security should should
    # allow incoming SSH connections on port 22. If you do not supply a
    # security_group_id a new security should will be created.
    security_group_id = None

    # Optional: Choose if you want the flow script to pause before cleaning up
    interactive_cleanup = False

    # ======= end of configuration =======

    # get AWS credentials from setting
    credentials = system.setting(credentials_setting).get('value')

    # create a template task we can re-use for different calls
    aws_template = this.connect(
        connector_type='AWS',
        region='eu-central-1',
        init={
            'protect_inputs': [
                'aws_access_key_id',
                'aws_secret_access_key',
            ],
        },
        **credentials,
        save=False,
    )

    # run everything in a try statement,
    # so we can clean up if anything goes wrong
    instance_id = None
    key_name = f'{name}-key'
    clean_security_group = False
    describe_vpcs = None
    try:

        if not vpc_id:
            # asyncrounously find default VPC
            describe_vpcs = aws_template.clone(
                name='find default vpc id',
                client='ec2',
                service='describe_vpcs',
                parameters={
                    'Filters': [{
                        'Name': 'isDefault',
                        'Values': ['true'],
                    }],
                },
                wait=False,
            )

        # asyncrounously find latest amazon linux image_id
        get_parameters = aws_template.clone(
            name='read image id',
            client='ssm',
            service='get_parameters',
            parameters={
                'Names': [
                    '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
                ],
            },
            wait=False,
        )

        # asyncrounously create a new key pair
        create_key_pair = aws_template.clone(
            name='create new key pair',
            client='ec2',
            service='create_key_pair',
            parameters={
                'KeyName': key_name,
            },
            init={
                'protect_outputs': [
                    'result',
                ],
            },
            wait=False,
        )

        if not security_group_id:
            if not vpc_id:
                # wait for describe_vpcs to end
                this.wait_for(describe_vpcs)
                describe_vpcs_outputs = describe_vpcs.get('output_value')
                vpc_id = describe_vpcs_outputs['result']['Vpcs'][0]['VpcId']
                this.log(vpc_id=vpc_id)

            # create a security group
            create_security_group_outputs = aws_template.clone(
                name='create security group',
                client='ec2',
                service='create_security_group',
                parameters={
                    'Description': 'Allow SSH for Cloudomation example',
                    'GroupName': f'{name}-security-group',
                    'VpcId': vpc_id,
                },
            ).get('output_value')
            security_group_id = create_security_group_outputs['result'][
                'GroupId']
            this.log(security_group_id=security_group_id)
            clean_security_group = True

            # authorize incoming SSH traffic on port 22
            aws_template.clone(
                name='authorize SSH traffic',
                client='ec2',
                service='authorize_security_group_ingress',
                parameters={
                    'CidrIp': '0.0.0.0/0',
                    'FromPort': 22,
                    'ToPort': 22,
                    'GroupId': security_group_id,
                    'IpProtocol': 'tcp',
                },
            )

        # wait for both, get_parameters and create_key_pair tasks to end
        this.wait_for(get_parameters, create_key_pair)

        get_parameters_outputs = get_parameters.get('output_value')
        image_id = get_parameters_outputs['result']['Parameters'][0]['Value']
        this.log(image_id=image_id)

        create_key_pair_outputs = create_key_pair.get('output_value')
        key_name = create_key_pair_outputs['result']['KeyName']
        private_key = create_key_pair_outputs['result']['KeyMaterial']
        this.log(key_name=key_name)

        # launch an EC2 instance
        run_instance_outputs = aws_template.clone(
            name='run instance',
            client='ec2',
            service='run_instances',
            parameters={
                'ImageId': image_id,
                'InstanceType': 't2.micro',
                'MaxCount': 1,
                'MinCount': 1,
                'KeyName': key_name,
                'SecurityGroupIds': [security_group_id],
            },
        ).get('output_value')
        instance_id = run_instance_outputs['result']['Instances'][0][
            'InstanceId']
        this.log(instance_id=instance_id)

        # wait until the instance is running
        aws_template.clone(
            name='wait for instance running',
            client='ec2',
            waiter='instance_running',
            parameters={'InstanceIds': [
                instance_id,
            ]},
        )

        # read public IP of instance
        describe_instances_outputs = aws_template.clone(
            name='describe instance',
            client='ec2',
            service='describe_instances',
            parameters={
                'InstanceIds': [
                    instance_id,
                ]
            },
        ).get('output_value')
        public_ip = describe_instances_outputs['result']['Reservations'][0][
            'Instances'][0]['PublicIpAddress']
        this.log(public_ip=public_ip)

        # read console output to find host key
        # the call might return without an actual output, we have to loop until we receive an actual output
        while True:
            get_console_output = aws_template.clone(
                name='get console output',
                client='ec2',
                service='get_console_output',
                parameters={
                    'InstanceId': instance_id,
                },
            )
            get_console_output_outputs = get_console_output.get('output_value')
            console = get_console_output_outputs['result'].get('Output')
            if console:
                in_hostkeys = False
                hostkey = None
                for line in console.splitlines():
                    if line.strip() == '-----BEGIN SSH HOST KEY KEYS-----':
                        in_hostkeys = True
                    elif line.strip() == '-----END SSH HOST KEY KEYS-----':
                        in_hostkeys = False
                    elif in_hostkeys:
                        if line.startswith('ssh-rsa '):
                            hostkey = line.strip()
                            break
                if hostkey:
                    this.log(hostkey=hostkey)
                    break
            # wait 30 seconds before requesting console output again
            this.sleep(30)

        # connect to the instance
        this.connect(
            connector_type='SSH',
            name='connect to host',
            hostname=public_ip,
            hostkey=hostkey,
            username='******',
            key=private_key,
            script=textwrap.dedent('''
                id
                uname -a
                '''),
        )

    # clean up
    finally:
        if interactive_cleanup:
            this.save(message='waiting before cleanup')
            this.pause()
        this.save(message='cleaning up')

        if instance_id:
            try:
                # remove instance
                aws_template.clone(
                    name='terminate instance',
                    client='ec2',
                    service='terminate_instances',
                    parameters={
                        'InstanceIds': [instance_id],
                    },
                )
            except Exception as ex:
                this.log(f'failed to remove instance: {str(ex)}')

            try:
                # wait for instance to be terminated
                aws_template.clone(
                    name='wait for instance terminated',
                    client='ec2',
                    waiter='instance_terminated',
                    parameters={
                        'InstanceIds': [instance_id],
                    },
                )
            except Exception as ex:
                this.log(
                    f'failed to wait for instance to be terminated: {str(ex)}')

        if key_name:
            try:
                # remove key pair
                aws_template.clone(
                    name='delete key pair',
                    client='ec2',
                    service='delete_key_pair',
                    parameters={
                        'KeyName': key_name,
                    },
                )
            except Exception as ex:
                this.log(f'failed to delete key pair: {str(ex)}')

        if clean_security_group and security_group_id:
            # remove security group
            try:
                aws_template.clone(
                    name='delete security group',
                    client='ec2',
                    service='delete_security_group',
                    parameters={
                        'GroupId': security_group_id,
                    },
                )
            except Exception as ex:
                this.log(f'failed to delete security group: {str(ex)}')

    return this.success('all done')
Esempio n. 21
0
def handler(system: flow_api.System, this: flow_api.Execution):
    DT_FORMAT = '%Y-%m-%dT%H:%M:%S.%f%z'
    number_of_steps = 12
    user_name = system.get_own_user().get('name')
    client_name = system.get_own_client().get('name')
    user_name = user_name[0].upper() + user_name[1:]
    execution_name = ''
    execution_status = ''
    output_value_str = ''
    setting_id = None
    setting_name = ''
    text = ''
    iterations = 0
    delay = 0
    file_content = ''
    message = ''
    time_out = 30 * 60
    poll_interval = 10

    def get_message(step):
        messages = [
            textwrap.dedent(  # step 1
                f'''
                ### Hi {user_name}, Welcome to Cloudomation!

                This wizard will guide you through some first steps to explore the functionality of Cloudomation.
                You will be given a task and the wizard continues once the task is done.

                If at any time you want to view the instructions again, switch to the <strong><i class="fa fa-envelope-o"></i> Messages</strong> resource.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your first task:</strong>
                    <div><i class="fa fa-check-square-o"></i> Close this message by pressing "OK".</div>
                </div>
                '''),
            textwrap.dedent(  # step 2
                f'''
                The Cloudomation user-interface uses the following layout:

                <div class="p-row p-justify-center mb-3">
                    <div class="p-col" style="border: 1px solid black; width: 300px; height: 150px">
                        <div class="p-row p-align-center p-justify-center p-1 bg-blue text-blue-contrast">Header</div>
                        <div class="p-row c-grow">
                            <div class="p-row p-align-center p-justify-center p-1 bg-green text-green-contrast" style="width: 15%; writing-mode: vertical-lr">Navigation</div>
                            <div class="p-col c-grow">
                                <div class="p-row p-align-center p-justify-center p-1 bg-yellow text-yellow-contrast">Action bar</div>
                                <div class="p-row c-grow p-align-center p-justify-center p-1 bg-light-darker text-light-darker-contrast">Main area</div>
                            </div>
                        </div>
                    </div>
                </div>

                The navigation is structured in levels:

                - Categories: e.g. **- App - <i class="fa fa-chevron-down"></i>**
                    - Resources: e.g. **<i class="fa fa-desktop"></i> Executions**
                        - Opened records: e.g. <i class="fa fa-file-code-o"></i> my first flow

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your tasks:</strong>
                    <div><i class="fa fa-check-square-o"></i> Familiarize yourself with the components of the user-interface.</div>
                    <div><i class="fa fa-check-square-o"></i> Select the <strong><i class="fa fa-code"></i> Flows</strong> resource in the <strong>- App -</strong> category.</div>
                    <div><i class="fa fa-check-square-o"></i> Create a new flow by pressing <span class="text-primary"><i class="fa fa-file-o"></i> New</span> in the action bar.</div>
                </div>
                '''),
            textwrap.dedent(  # step 3
                f'''
                A flow contains the automation logic which is processed by Cloudomation when the flow is run.

                You can learn more about flows in the [Flows](/documentation/Flows){{ext}} documentation.

                The newly created flow is opened for you.
                You can see all opened flow records below the <strong><i class="fa fa-code"></i> Flows</strong> resource in the menu on the left.

                Flows contain two important fields:
                - The flow name
                - The flow script

                Flow names must be unique among all flows.

                The flow script has to be Python code with a handler function.
                There are many Python tutorials out there, for example [The Python Tutorial](https://docs.python.org/3/tutorial/){{ext}} by the Python Software Foundation

                <div class="alert alert-info p-row p-align-center">
                    <i class="fa fa-info fa-2x text-info pr-3"></i> The rest of the wizard assumes basic understanding of Python syntax.
                </div>

                The new flow script already contains the handler function and some boilerplate.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your tasks:</strong>
                    <div><i class="fa fa-check-square-o"></i> Change the name of the flow to something more useful, like "my first flow".</div>
                    <div><i class="fa fa-check-square-o"></i> Replace the script with the following:
                        <pre class="ml-3"><code>import flow_api
                def handler(system: flow_api.System, this: flow_api.Execution):
                    this.log('Hello {user_name}!')
                    return this.success('all done')</code></pre>
                    </div>
                    <div><i class="fa fa-check-square-o"></i> Create an execution of the flow by pressing <span class="text-success"><i class="fa fa-play"></i> Try</span> in the action bar.</div>
                </div>
                '''),
            textwrap.dedent(  # step 4
                f'''
                **Congratulations!**
                
                Your execution `{execution_name}` just ended with status `{execution_status}`.
                When you pressed <span class="text-success"><i class="fa fa-play"></i> Try</span>, the user-interface switched to the executions list.

                **The difference between <span class="text-success"><i class="fa fa-play"></i> Try</span> and <span class="text-success"><i class="fa fa-play"></i> Run</span>**
                
                Both actions start the execution of your flow. For development, you can use <span class="text-success"><i class="fa fa-play"></i> Try</span> in the browser.
                <span class="text-success"><i class="fa fa-play"></i> Run</span> is meant for productive use.
                
                **Executions**

                You can see an overview of all running executions in the execution list.
                By clicking on the name of your execution, you open the execution record view.
                When scrolling down in the execution record view, you will see the "Outputs" field which contains:
                
                ```yaml
{textwrap.indent(output_value_str,'                ')}
                ```
                
                ---

                Next, let's look at the <strong><i class="fa fa-sliders"></i> Settings</strong> resource.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your tasks:</strong>
                    <div><i class="fa fa-check-square-o"></i> Select the <strong><i class="fa fa-sliders"></i> Settings</strong> resource.</div>
                    <div><i class="fa fa-check-square-o"></i> Create a new setting by pressing <span class="text-primary"><i class="fa fa-file-o"></i> New</span> in the action bar.</div>
                </div>
                '''),
            textwrap.dedent(  # step 5
                f'''
                Just like flows, settings contain two important fields:
                - The setting name
                - The setting value

                Setting names must be unique among all settings.

                The setting value must be a valid [YAML document](https://learnxinyminutes.com/docs/yaml/){{ext}}.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your tasks:</strong>
                    <div><i class="fa fa-check-square-o"></i> Change the name of the setting to something more useful, like "test setting".</div>
                    <div><i class="fa fa-check-square-o"></i> Replace the setting's value with the following:
                        <pre class="ml-3"><code>text: Hello {user_name}!
                iterations: 10
                delay: 6</code></pre>
                    </div>
                    <div><i class="fa fa-check-square-o"></i> Save the setting by pressing <span class="text-primary"><i class="fa fa-save"></i> Save</span> in the action bar.</div>
                </div>
                '''),
            textwrap.dedent(  # step 6
                f'''
                Let's create another flow which uses the newly created setting.

                This flow will read the values of `text`, `iterations`, and `delay` from the setting.

                With the values you put in the setting it will log the text "{text}" "{iterations}" times with a delay of "{delay}" seconds.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your tasks:</strong>
                    <div><i class="fa fa-check-square-o"></i> Create a new flow.</div>
                    <div><i class="fa fa-check-square-o"></i> Paste the following script:
                        <pre class="ml-3"><code>import flow_api
                def handler(system: flow_api.System, this: flow_api.Execution):
                    # read the value of the setting record
                    test_setting = system.setting('{setting_name}').get('value')
                    # loop `iterations` times
                    for i in range(test_setting['iterations']):
                        # log the text
                        this.log(test_setting['text'])
                        # sleep for `delay` seconds
                        this.sleep(test_setting['delay'])
                    return this.success('all done')</code></pre>
                    </div>
                    <div><i class="fa fa-check-square-o"></i> Create an execution of the flow by pressing <span class="text-success"><i class="fa fa-play"></i> Try</span> in the action bar.</div>
                </div>
                '''),
            textwrap.dedent(  # step 7
                f'''
                Your execution "{execution_name}" is now running. It will take about {iterations * delay} seconds to end.

                In the <strong><i class="fa fa-fw fa-desktop"></i> Executions</strong> resource you can see the list of all your executions.
                There you can monitor the current status of your executions.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your tasks:</strong>
                    <div><i class="fa fa-check-square-o"></i> Switch to the <strong><i class="fa fa-fw fa-desktop"></i> Executions</strong> resource. You'll see a list of all your executions.</div>
                    <div><i class="fa fa-check-square-o"></i> Select your execution by checking it in the list: <i class="fa fa-check-square-o"></i></div>
                    <div><i class="fa fa-check-square-o"></i> Pause the execution by pressing <span class="text-primary"><i class="fa fa-pause"></i> Pause</span> in the action bar.</div>
                </div>
                '''),
            textwrap.dedent(  # step 8
                f'''
                Let's recap what we've already seen:

                **The user-interface**

                - Layout: There are four main areas: header, navigation, main area, and action bar.
                - The **- App -** category lists resources: Executions, Flows, Settings, and others.
                - Selecting a resource shows a list of all records in the main area.
                - It is possible to check records in the list view and perform actions on _all_ checked records at once.
                - In the record view more details are seen and the record can be modified.

                **The resources**

                - Flows contain automation logic.
                - Executions are created by "running" or "trying" a flow.
                - Flows can access settings to read or write values.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your task:</strong>
                    <div><i class="fa fa-check-square-o"></i> Close this message by pressing "OK".</div>
                </div>
                '''),
            textwrap.dedent(  # step 9
                f'''
                Now, let's see how to work with files.
                
                The **Files** section is right beneath the settings section on the left.

                In this section, you can store arbitrary files like shell scripts, markup files, zip files etc.
                All these files can be loaded and manipulated through flow scripts.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your tasks:</strong>
                    <div><i class="fa fa-check-square-o"></i> Create a file by pressing <span class="text-primary"><i class="fa fa-file-o"></i> New</span> in the action bar.</div>
                    <div><i class="fa fa-check-square-o"></i> Rename the file to <code>myfile.txt</code> and write something into the text field and press <span class="text-primary"><i class="fa fa-save"></i> Save</span> in the action bar.</div>                    
                    <div><i class="fa fa-check-square-o"></i> Create a new flow and paste the following script into the code editor:
                    <pre class="ml-3"><code>import flow_api
                def handler(system: flow_api.System, this: flow_api.Execution):
                    # read the value of the file
                    read_file = system.file(
                        name='myfile.txt'
                    )
                    # print contents of the file to the log output
                    this.log(read_file.get('content'))
                    return this.success(message='all done')</code></pre>
                    </div>
                    <div><i class="fa fa-check-square-o"></i> Create an execution of the flow by pressing <span class="text-success"><i class="fa fa-play"></i> Try</span> in the action bar.</div>
                </div>
                '''),
            textwrap.dedent(  # step 10
                f'''
                You can see the content of the file in the **Outputs** section in the execution <code>{execution_name}</code>:
                <pre class="ml-3"><code>{file_content}</code></pre>

                You can learn more about files under [Files](/documentation/File%20handling%20with%20Cloudomation){{ext}} in the documentation.

                ---

                Next, let's explore tasks.

                As already covered, flows contain the automation logic.
                Tasks on the other hand provide a way to access outside systems.
                There are several task types available, each of which specializes on a certain protocol to talk with outside systems.

                The `REST` task for example can be used to communicate with any REST service. Let's try!

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your tasks:</strong>
                    <div><i class="fa fa-check-square-o"></i> Create a new flow.</div>
                    <div><i class="fa fa-check-square-o"></i> Paste the following script:
                    <pre><code>import flow_api
                def handler(system: flow_api.System, this: flow_api.Execution):
                    # create a REST task and run it
                    task = this.connect(connector_type='REST', url='https://api.icndb.com/jokes/random')
                    # access a field of the JSON response
                    joke = task.get('output_value')['json']['value']['joke']
                    # end with a joke
                    return this.success(message=joke)</code></pre>
                    </div>
                    <div><i class="fa fa-check-square-o"></i> Create an execution of the flow by pressing <span class="text-success"><i class="fa fa-play"></i> Try</span> in the action bar.</div>
                </div>
                '''),
            textwrap.dedent(  # step 11
                f'''
                Your execution `{execution_name}` ended with the message:

                    {message}

                Aside from learning something fascinating about Chuck Norris, we also used two additional important features of Cloudomation:

                - Child executions
                - Execution inputs and outputs

                Let's look at line 3 of the flow `{execution_name}` you've just created:

                ```python
                task = this.connect(connector_type='REST', url='https://api.icndb.com/jokes/random')
                ```

                There are several things to notice:

                - `this.task` instructs Cloudomation to create a task execution which is a child of the currently running execution.
                - `REST` specifies which task type to use.
                - `url=...` passes an input value named `url` to the task.

                All task types provide access to outside systems and accept a different set of inputs.
                You can learn more about the available task types in the [Tasks](/documentation/Tasks){{ext}} documentation.

                Once the child execution ends, your flow continues running.
                The child execution provides output values which can be used by your flow.
                This we can see in line 5:

                ```python
                joke = task.get('output_value')['json']['value']['joke']
                ```

                - `task.get` instructs Cloudomation to read a field of the child execution.
                - `output_value` specifies which field to read.
                - `['json']['value']['joke']` selects the part of the output value we are interested in.

                <div class="alert alert-primary">
                    <strong class="d-block pb-1">Your task:</strong>
                    <div><i class="fa fa-check-square-o"></i> Close this message by pressing "OK".</div>
                </div>
                '''),
            textwrap.dedent(  # step 12
                f'''
                We've reached the end of the first steps wizard. Here are some links you can follow to deepen your understanding of Cloudomation:

                - Learn more about using Cloudomation in the [Tutorial](https://cloudomation.com/documentation/tutorial/){{ext}}
                - [Invite users](/users){{ext}} to join your Cloudomation client "{client_name}"
                - Set up [integration with Git](https://cloudomation.com/documentation/using-flow-scripts-from-git/){{ext}}
                - Read the flow [script function reference](/documentation/Flow%20script%20function%20reference){{ext}}
                - Explore available [task types](/documentation/Tasks){{ext}}
                - Set up [webhooks](https://cloudomation.com/documentation/webhooks/){{ext}}.
                - If there are any questions or issues, please do not hesitate to [contact us](/contact){{ext}}.

                Enjoy automating!
                '''),
        ]

        return messages[step - 1]

    for step in range(1, number_of_steps + 1):
        m = system.message(subject=f'Step {step} of {number_of_steps}',
                           message_type='POPUP',
                           body={
                               'type': 'object',
                               'properties': {
                                   'content': {
                                       'element': 'markdown',
                                       'description': get_message(step),
                                       'order': 1,
                                   },
                                   'acknowledged': {
                                       'label': 'OK',
                                       'element': 'submit',
                                       'type': 'boolean',
                                       'order': 2,
                                   },
                               },
                               'required': [
                                   'acknowledged',
                               ],
                           })

        m.wait()
        dt_now = datetime.datetime.now(tz=datetime.timezone.utc)

        if step == 2:
            start_time = time.time()
            flow = None
            while flow is None and time.time() < start_time + time_out:
                for cur_flow in system.flows():
                    try:
                        if cur_flow.get(
                                'created_at'
                        ) is not None and datetime.datetime.strptime(
                                cur_flow.get('created_at'),
                                DT_FORMAT) > dt_now:
                            flow = cur_flow
                            break
                    except Exception:
                        pass
                this.sleep(poll_interval)

        elif step == 3 or step == 6:
            start_time = time.time()
            execution = None
            while execution is None and time.time() < start_time + time_out:
                for cur_execution in system.executions():
                    try:
                        if cur_execution.get(
                                'created_at'
                        ) is not None and datetime.datetime.strptime(
                                cur_execution.get('created_at'),
                                DT_FORMAT) > dt_now:
                            execution = cur_execution
                            break
                    except Exception:
                        pass
                this.sleep(poll_interval)
            if step == 3:
                execution.wait()
                execution_name, execution_status, output_value = execution.load(
                    'name', 'status', 'output_value')
                output_value_str = yaml.safe_dump(output_value,
                                                  default_flow_style=False)
            else:
                execution_name = execution.load('name')

        elif step == 4:
            start_time = time.time()
            setting = None
            while setting is None and time.time() < start_time + time_out:
                for cur_setting in system.settings():
                    try:
                        if cur_setting is not None and cur_setting.get(
                                'created_at'
                        ) is not None and datetime.datetime.strptime(
                                cur_setting.get('created_at'),
                                DT_FORMAT) > dt_now:
                            setting = cur_setting
                            break
                    except Exception:
                        pass
                this.sleep(poll_interval)
            setting_id = setting.get('id')

        elif step == 5:
            start_time = time.time()
            before_modified_at = setting.load('modified_at')
            while time.time() < start_time + time_out:
                setting = None
                for cur_setting in system.settings():
                    try:
                        if cur_setting is not None and cur_setting.load(
                                'modified_at') > before_modified_at:
                            setting = cur_setting
                            break
                    except Exception:
                        pass
                if setting is None:
                    continue
                modified_at = setting.load('modified_at')
                if modified_at is not None and modified_at > before_modified_at:
                    before_modified_at = modified_at
                    value = setting.get('value')
                    if all(k in value
                           for k in ('text', 'iterations', 'delay')):
                        break
                    else:
                        check_m = system.message(
                            subject=
                            f'Please check - Step {step} of {number_of_steps}',
                            message_type='POPUP',
                            body={
                                'type': 'object',
                                'properties': {
                                    'content': {
                                        'element':
                                        'markdown',
                                        'description':
                                        textwrap.dedent(f'''
                                                            Please make sure that the setting contains all the keys:

                                                            - text
                                                            - iterations
                                                            - delay

                                                            Like in this example:

                                                            ```yaml
                                                            text: Hello {user_name}!
                                                            iterations: 10
                                                            delay: 6
                                                            ```

                                                            The values of the keys will be used in the next step.
                                                            '''),
                                        'order':
                                        1,
                                    },
                                    'acknowledged': {
                                        'label': 'OK',
                                        'element': 'submit',
                                        'type': 'boolean',
                                        'order': 2,
                                    },
                                },
                                'required': [
                                    'acknowledged',
                                ],
                            })
                        check_m.wait()
                        continue
                this.sleep(poll_interval)
            setting_name, setting_value = setting.load('name', 'value')
            text = setting_value['text']
            iterations = setting_value['iterations']
            delay = setting_value['delay']
        elif step == 7:
            start_time = time.time()
            while time.time() < start_time + time_out:
                if execution.load('status') in ('PAUSED', 'ENDED_SUCCESS',
                                                'ENDED_ERROR',
                                                'ENDED_CANCELLED'):
                    break
                this.sleep(poll_interval)
        elif step == 9:
            start_time = time.time()
            file_content = None
            execution = None
            while time.time() < start_time + time_out:
                if file_content is None:
                    for cur_files in system.files():
                        try:
                            if cur_files.load('name') == 'myfile.txt':
                                myfile = system.file('myfile.txt')
                                file_content = myfile.get('content')
                        except Exception:
                            pass
                if execution is None:
                    for cur_execution in system.executions():
                        try:
                            if cur_execution.get(
                                    'created_at'
                            ) is not None and datetime.datetime.strptime(
                                    cur_execution.get('created_at'),
                                    DT_FORMAT) > dt_now:
                                execution = cur_execution
                        except Exception:
                            pass
                if file_content and execution:
                    break
                this.sleep(poll_interval)
            execution_name = execution.load('name')
        elif step == 10:
            start_time = time.time()
            task = None
            execution = None
            while time.time() < start_time + time_out:
                for cur_execution in system.executions():
                    try:
                        created_at = cur_execution.get('created_at')
                        if created_at is not None and datetime.datetime.strptime(
                                created_at, DT_FORMAT) > dt_now:
                            if cur_execution.get('type') == 'TASK':
                                task = cur_execution
                            else:
                                execution = cur_execution
                    except Exception:
                        pass
                if task and execution:
                    break
                this.sleep(poll_interval)
            execution.wait(return_when=system.return_when.ALL_SUCCEEDED)
            execution_name = execution.load('name')
            message = unescape(
                task.get('output_value')['json']['value']['joke'])

    return this.success('all done')
Esempio n. 22
0
def handler(system: flow_api.System, this: flow_api.Execution):

    # (2) Create a setting with country names
    # In a real-life application of this functionality, this setting would
    # probably be created by another flow script, or be defined manually once.
    # First, we check if the setting already exists. If it doesn't, we create
    # it. Feel free to change the selection of countries.
    # Note that we create a setting that contains a list. Settings can contain
    # any object that can be serialised into a yaml - lists, json etc.

    geonames_countrynames = system.setting('geonames_countrynames')
    if not geonames_countrynames.exists():
        geonames_countrynames.save(value=["Austria", "Latvia", "Azerbaijan"])

    countrynames = geonames_countrynames.get('value')

    # (3) Loop through the countries
    # In order to get the information we want, we need to do several API calls.
    # To speed this up, we parallelise the API calls by executing them in a
    # separate flow script, which we start as child executions.

    # We create an empty list to which we will append our child execution
    # objects in the for loop.
    calls = []

    # We create a separate empty list to which we will append inputs that are
    # not valid countries.
    invalids = []

    for countryname in countrynames:
        # We call the flow script that contains the REST calls with the c.flow
        # function. We store the execution object returned by the c.flow
        # function in the call variable.
        call = this.flow(
            'loop_child',
            # I can add any number of parameters here. As long as they are not
            # called the same as the defined parameters for the flow function,
            # the are appended to the input dictionary that is passed to the
            # child execution.
            # In this example, the child execution will be passed a key-value
            # pair with countryname as the key, and the first value in the
            # countryname setting as the value.
            # If I added another argument weather = nice, a second key-value
            # pair would be added to the input dictionary for the child
            # execution, with the key weather and the value nice.
            # Note that I can also pass the same input as a dictionary with the
            # inputs parameter. The below line is equivalent to
            # input_value = { 'countryname': countryname }
            countryname=countryname,
            run=False,
            # run_async() starts the child execution and then immediately returns.
            # This means that the for loop continues and the next child execution
            # is started right away - the REST calls will be executed in parallel.
        ).run_async()
        # All execution objects are appended to the calls list.
        calls.append(call)

# (4) Wait for all child executions to finish

# Here, I tell the flow script to wait for all elements in the calls list
# to finish before it continues. Remember that the calls list contains all
# execution objects that we started in the for loop.
    this.wait_for(*calls)

    # (5) Get outputs of child executions, and set outputs of parent execution

    # Now, we take all the execution objects and get their outputs. Depending
    # on whether or not there was an error, we treat the results differently.
    for call in calls:
        # Get all outputs from all child executions
        result = call.load('output_value')
        # If there was an error, append the output of the erroneous execution
        # to our list of invalid country names.
        if 'error' in result:
            invalids.append(result['error'])
        # If there was no error, we directly set the output of the flow script
        # to the output of the child executions.
        else:
            this.log(result)

    # The errors need a bit more processing: here, we set an output that
    # contains the key "warning", information about the number of failed calls,
    # and the country names for which there was an error.
    if len(invalids) > 0:
        this.log(
            f'warning: no information was found for {len(invalids)} countries')
        this.log(invalid_countries=invalids)

# (6) Once we're done we end the execution.
    return this.success(message='all done')
Esempio n. 23
0
def handler(system: flow_api.System, this: flow_api.Execution):
    github_form_response = system.message(
        subject='GitHub info',
        message_type='POPUP',
        body={
            'type': 'object',
            'properties': {
                'username': {
                    'label': 'What is your GitHub username?',
                    'element': 'string',
                    'type': 'string',
                    'order': 1,
                },
                'no_username': {
                    'element': 'submit',
                    'type': 'boolean',
                    'label': 'I don\'t have a GitHub account',
                    'order': 2,
                    'required': [],
                },
                'token_label': {
                    'element': 'markdown',
                    'description':
                    'To interact with your string account via the string REST API, you need to supply a string token for your account.\nPlease go to [https://github.com/settings/tokens](https://github.com/settings/tokens){ext} and generate a personal access token.\nCheck the following options: repo and admin:repo_hook.\nPaste the token here after you have created it.',
                    'order': 3,
                },
                'token': {
                    'element': 'string',
                    'label': 'GitHub token:',
                    'type': 'string',
                    'order': 4,
                },
                'repository name': {
                    'element': 'string',
                    'label':
                    'Which GitHub repository you would like to use for your flow scripts? If this field is left blank, we will set up a new repository for you.',
                    'type': 'string',
                    'order': 5,
                },
                'Ok': {
                    'element': 'submit',
                    'label': 'OK',
                    'type': 'boolean',
                    'order': 6,
                },
            },
            'required': [
                'username',
                'token',
            ],
        },
    ).wait().get('response')

    if github_form_response.get('no_username'):
        system.message(
            subject='Please create a GitHub account',
            message_type='POPUP',
            body={
                'type': 'object',
                'properties': {
                    'message': {
                        'element': 'markdown',
                        'description':
                        'Please go to [https://github.com/join](https://github.com/join){ext} and create an account. Restart this flow script once you have a GitHub account.',
                        'order': 3,
                    },
                    'submit': {
                        'element': 'submit',
                        'type': 'boolean',
                        'label': 'OK',
                    },
                },
            },
        )
        return this.success(
            'restart this flow when you created a GitHub account')

    github_username = github_form_response['username']
    github_token = github_form_response['token']
    github_repo_name = github_form_response.get('repository name')
    github_repo_exists = github_repo_name is not None

    if not github_repo_exists:
        github_new_repo_response = system.message(
            subject='new GitHub repository',
            message_type='POPUP',
            body={
                'type':
                'object',
                'properties': {
                    'repository name': {
                        'label':
                        'We will now set up a GitHub repository for you.\nWhat should be the name of the repository?',
                        'element': 'string',
                        'type': 'string',
                        'order': 1,
                    },
                    'description': {
                        'label':
                        'Please describe your repository briefly. This description will be published on your GitHub repository page, where you can change it later.',
                        'element': 'string',
                        'type': 'string',
                        'order': 2,
                    },
                    'private_label': {
                        'element': 'markdown',
                        'description':
                        'Do you want to create a private repository?',
                        'order': 3,
                    },
                    'private repository': {
                        'element': 'toggle',
                        'type': 'boolean',
                        'order': 4,
                    },
                    'Create repository': {
                        'element': 'submit',
                        'type': 'boolean',
                        'order': 5,
                    },
                },
                'required': [
                    'repository name',
                    'description',
                    'private repository',
                ],
            },
        ).wait().get('response')

        github_repo_name = github_new_repo_response['repository name']
        github_repo_description = github_new_repo_response['description']
        private_repo = github_new_repo_response['private repository']
        homepage = f'https://github.com/{github_username}/{github_repo_name}'

        create_repo = this.connect(
            connector_type='REST',
            url='https://api.github.com/user/repos',
            method='POST',
            data={
                'name': github_repo_name,
                'description': github_repo_description,
                'homepage': homepage,
                'private': private_repo,
                'has_issues': 'true',
                'has_projects': 'true',
                'has_wiki': 'true',
                'auto_init': 'true'
            },
            headers={'Authorization': f'token {github_token}'},
            run=False,
        )

        try:
            create_repo.run()
        except Exception:
            creation_failed = create_repo.get('output_value')
            this.log(creation_failed)
            system.message(
                subject='Repository creation failed',
                message_type='POPUP',
                body={
                    'type': 'object',
                    'properties': {
                        'error': {
                            'element':
                            'markdown',
                            'description':
                            (f'The creation of your GitHub repository failed with the following error: {creation_failed}'
                             ),
                            'order':
                            1,
                        },
                        'Ok': {
                            'element': 'submit',
                            'label': 'OK',
                            'type': 'boolean',
                            'order': 2,
                        },
                    },
                },
            )

        else:
            system.message(
                subject='Repository creation successful',
                message_type='POPUP',
                body={
                    'type': 'object',
                    'properties': {
                        'success': {
                            'element':
                            'markdown',
                            'description':
                            (f'GitHub repository created successfully. Check it out here: {homepage}'
                             ),
                            'order':
                            1,
                        },
                        'Ok': {
                            'element': 'submit',
                            'label': 'OK',
                            'type': 'boolean',
                            'order': 2,
                        },
                    },
                },
            )

    github_info = {
        'github_username': github_username,
        'github_repo_name': github_repo_name,
        'github_token': github_token
    }

    system.setting(name='github_info', value=github_info)
    this.set_output('github_info', github_info)

    return this.success('all done.')
Esempio n. 24
0
def handler(system: flow_api.System, this: flow_api.Execution):
    # retrieve the apikey and secret from the system setting
    setting = system.setting('exoscale.api').get('value')

    # default values for inputs
    service_name = 'Micro'
    template_name = 'Ubuntu 19.10'
    zone_name = 'at-vie-1'
    compute_endpoint = 'https://api.exoscale.ch/compute'

    # check if we got inputs
    inputs = this.get('input_value')
    if inputs.get('service_name'):
        service_name = inputs.get('service_name')
    if inputs.get('template_name'):
        template_name = inputs.get('template_name')
    if inputs.get('zone_name'):
        zone_name = inputs.get('zone_name')

    # retrieve the available services
    command = {
        'command': 'listServiceOfferings',
        'apikey': setting.get('key'),
    }
    result = this.flow('acs.apicall',
                       input_value={
                           'command': command,
                           'secret': setting.get('secret'),
                           'compute_endpoint': compute_endpoint,
                       }).get('output_value').get('result')

    # check if the service we want is among the available
    # services and get the id of the service
    service_offering_id = None
    for so in result.get('listserviceofferingsresponse').get(
            'serviceoffering'):
        if so.get('name') == service_name:
            service_offering_id = so.get('id')
            break
    if not service_offering_id:
        return this.error(f'could not find service name: {service_name}')

    # retrieve the available templates
    command = {
        'command': 'listTemplates',
        'templatefilter': 'featured',
        'apikey': setting.get('key'),
    }
    result = this.flow('acs.apicall',
                       input_value={
                           'command': command,
                           'secret': setting.get('secret'),
                           'compute_endpoint': compute_endpoint,
                       }).get('output_value').get('result')

    # check if the template we want is among the available
    # templates and get the id of the template
    template_id = None
    for t in result.get('listtemplatesresponse').get('template'):
        if template_name in t.get('name'):
            template_id = t.get('id')
            break
    if not template_id:
        return this.error(f'could not find template name: {template_name}')

    # retrieve the available zones
    command = {
        'command': 'listZones',
        'available': 'true',
        'apikey': setting.get('key'),
    }
    result = this.flow(
        'acs.apicall',
        input_value={
            'command': command,
            'secret': setting.get('secret'),
            'compute_endpoint': compute_endpoint,
        },
    ).get('output_value').get('result')

    # check if the zone we want is among the available
    # zones and get the id of the zone
    zone_id = None
    for z in result.get('listzonesresponse').get('zone'):
        if z.get('name') == zone_name:
            zone_id = z.get('id')
            break
    if not zone_id:
        return this.error(f'could not find zone name: {zone_name}')

    # deploy the VM with the collected ids
    command = {
        'command': 'deployVirtualMachine',
        'serviceofferingid': service_offering_id,
        'templateid': template_id,
        'zoneid': zone_id,
        'apikey': setting.get('key'),
    }

    result = this.flow('acs.apicall',
                       input_value={
                           'command': command,
                           'secret': setting.get('secret'),
                           'compute_endpoint': compute_endpoint,
                       }).get('output_value').get('result')

    # with the jobid we can query the status of the deployment
    jobid = result.get('deployvirtualmachineresponse').get('jobid')
    if not jobid:
        return this.error('could not create VM - no jobid')

    # check if the VM is running after 60 seconds
    time.sleep(60)
    instance_name = None
    command = {
        'command': 'queryAsyncJobResult',
        'jobid': jobid,
        'apikey': setting.get('key'),
    }
    result = this.flow('acs.apicall',
                       input_value={
                           'command': command,
                           'secret': setting.get('secret'),
                           'compute_endpoint': compute_endpoint,
                       }).get('output_value').get('result')

    # return success if the VM is running
    vm = result.get('queryasyncjobresultresponse',
                    {}).get('jobresult', {}).get('virtualmachine', {})
    if vm.get('state') == 'Running':
        instance_name = vm.get('displayname')
        return this.success(f'VM {instance_name} is up and running.')

    return this.error('Instance did not start in time')