Пример #1
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')
Пример #2
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')
Пример #3
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')
Пример #4
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))
Пример #5
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')

    # 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}')
Пример #6
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.')
Пример #7
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')
Пример #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')

    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')
Пример #9
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')
Пример #10
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')
Пример #11
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')
Пример #12
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.')
Пример #13
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')
Пример #14
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}')
    )
Пример #15
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')
Пример #16
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')