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')
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): 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')
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}')
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.')
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')
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')
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')
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')
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')
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.')
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')
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}') )
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')
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')