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): """ 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') or {} message_id = inputs.get('message_id') if message_id is None: defaults = { 'interval': 60, 'wait': True, } if 'flow_name' in inputs: defaults['flow_name'] = inputs['flow_name'] if 'flow_id' in inputs: defaults['flow_name'] = system.flow(inputs['flow_id'], by='id').get('name') message = system.message( subject='Recurring execution', body={ 'type': 'object', 'properties': { 'flow_name': { 'label': 'Name of the flow which should be started recurring', 'element': 'string', 'type': 'string', 'example': defaults.get('flow_name'), 'default': defaults.get('flow_name'), 'order': 1, }, 'interval': { 'label': 'Interval of recurring execution in seconds', 'element': 'number', 'type': 'number', 'example': defaults['interval'], 'default': defaults['interval'], 'order': 2, }, 'wait': { 'label': 'Wait for child executions to finish', 'element': 'toggle', 'type': 'boolean', 'default': defaults['wait'], 'order': 3, }, 'max_iterations': { 'label': 'Maximum number of iterations (unlimited if omitted)', 'element': 'number', 'type': 'number', 'order': 4, }, 'start': { 'label': 'Start recurring', 'element': 'submit', 'type': 'boolean', 'order': 5, }, }, 'required': [ 'flow_name', 'interval', ], }, ) message_id = message.get('id') this.save(output_value={ 'message_id': message_id, }) this.flow( 'Recurring', name='Recurring execution', message_id=message_id, wait=False, ) return this.success('requested details') message = system.message(message_id) response = message.wait().get('response') this.log(response=response) flow_name = response['flow_name'] interval = response['interval'] wait = response['wait'] max_iterations = response.get('max_iterations') this.save(name=f'Recurring {flow_name}') # Loop iterations = 0 start = time.time() while max_iterations is None or iterations < max_iterations: iterations += 1 if max_iterations: this.save(message=f'iteration {iterations}/{max_iterations}') else: this.save(message=f'iteration {iterations}') # Start child execution inputs = { 'start': start, 'iterations': iterations, 'max_iterations': max_iterations, } child = this.flow(flow_name, inputs=inputs, name=f'{flow_name} iteration #{iterations}', run=False) if wait: try: child.run() except Exception: this.log(f'iteration #{iterations} failed') else: child.run_async() if max_iterations is not None and iterations >= max_iterations: break if wait: now = time.time() scheduled = datetime.fromtimestamp(now + interval, timezone.utc) else: scheduled = datetime.fromtimestamp(start + (iterations * interval), timezone.utc) scheduled_ts = scheduled.isoformat(sep=' ', timespec='minutes') this.save(message=scheduled_ts) if wait: this.sleep(interval) else: this.sleep_until(start + (iterations * interval)) return this.success(f'started {iterations} iterations')
def handler(system: flow_api.System, this: flow_api.Execution): inputs = this.get('input_value') or {} message_id = inputs.get('message_id') if message_id is None: defaults = { 'scheduled_at': '08:30', } if 'flow_name' in inputs: defaults['flow_name'] = inputs['flow_name'] if 'flow_id' in inputs: defaults['flow_name'] = system.flow(inputs['flow_id'], by='id').get('name') message = system.message( subject='Scheduled execution', body={ 'type': 'object', 'properties': { 'flow_name': { 'label': 'Name of the flow which should be scheduled', 'element': 'string', 'type': 'string', 'example': defaults.get('flow_name'), 'default': defaults.get('flow_name'), 'order': 1, }, 'scheduled_at': { 'label': 'Time when the child execution should be started', 'element': 'time', 'type': 'string', 'format': 'time', 'default': defaults['scheduled_at'], 'order': 2, }, 'max_iterations': { 'label': 'Maximum number of iterations (unlimited if omitted)', 'element': 'number', 'type': 'number', 'order': 3, }, 'start': { 'label': 'Start schedule', 'element': 'submit', 'type': 'boolean', 'order': 4, }, }, 'required': [ 'flow_name', 'scheduled_at', ], }, ) message_id = message.get('id') this.save(output_value={ 'message_id': message_id, }) this.flow( 'Scheduled', name='Scheduled execution', message_id=message_id, wait=False, ) return this.success('requested details') message = system.message(message_id) response = message.wait().get('response') this.log(response=response) flow_name = response['flow_name'] scheduled_at = response['scheduled_at'] max_iterations = response.get('max_iterations') local_tz = response.get('timezone', 'Europe/Vienna') this.save(name=f'Scheduled {flow_name}') try: scheduled_at_t = datetime.datetime.strptime(scheduled_at, '%H:%M:%S%z').timetz() except ValueError: scheduled_at_t = datetime.datetime.strptime(scheduled_at, '%H:%M:%S').timetz() this.log(scheduled_at_t=scheduled_at_t) iterations = 0 start = datetime.datetime.now(datetime.timezone.utc).timestamp() while max_iterations is None or iterations < max_iterations: now = datetime.datetime.now(datetime.timezone.utc) if scheduled_at_t.tzinfo is None: now = now.astimezone(pytz.timezone(local_tz)) this.log(now=now) today = now.date() this.log(today=today) scheduled = datetime.datetime.combine(today, scheduled_at_t) if scheduled.tzinfo is None or scheduled.tzinfo.utcoffset(scheduled) is None: scheduled = pytz.timezone(local_tz).localize(scheduled) if scheduled < now: # next iteration tomorrow scheduled += datetime.timedelta(days=1) this.log(scheduled=scheduled) scheduled_ts = scheduled.isoformat(sep=' ', timespec='minutes') this.log(scheduled_ts=scheduled_ts) delta_sec = (scheduled - now).total_seconds() this.log(delta_sec=delta_sec) this.save(message=scheduled_ts) this.sleep(delta_sec) iterations += 1 this.save(message=f'iteration {iterations}/{max_iterations}') # Start child execution inputs = { 'start': start, 'iterations': iterations, 'max_iterations': max_iterations, } this.flow( flow_name, inputs=inputs, name=f'{flow_name} iteration #{iterations}', wait=False, ) if max_iterations is not None and iterations >= max_iterations: break return this.success(f'started {iterations} iterations')
def handler(system: flow_api.System, this: flow_api.Execution): inputs = this.get('input_value') or {} message_id = inputs.get('message_id') if message_id is None: defaults = { 'delay': '60', } if 'flow_name' in inputs: defaults['flow_name'] = inputs['flow_name'] if 'flow_id' in inputs: defaults['flow_name'] = system.flow(inputs['flow_id'], by='id').get('name') message = system.message( subject='Delayed execution', body={ 'type': 'object', 'properties': { 'flow_name': { 'label': 'Name of the flow which should be started', 'element': 'string', 'type': 'string', 'example': defaults.get('flow_name'), 'default': defaults.get('flow_name'), 'order': 1, }, 'label': { 'description': 'You can either specify a time when the execution should be started, or a delay in seconds', 'element': 'markdown', 'order': 2, }, 'time': { 'label': 'Time when the child execution should be started', 'element': 'time', 'type': 'string', 'format': 'time', 'order': 3, }, 'delay': { 'label': 'Delay in seconds after which the child execution should be started', 'element': 'number', 'type': 'number', 'example': defaults['delay'], 'default': defaults['delay'], 'order': 4, }, 'start': { 'label': 'Start delayed', 'element': 'submit', 'type': 'boolean', 'order': 5, }, }, 'required': [ 'flow_name', ], }, ) message_id = message.get('id') this.save(output_value={ 'message_id': message_id, }) this.flow( 'Delayed', name='Delayed execution', message_id=message_id, wait=False, ) return this.success('requested details') message = system.message(message_id) response = message.wait().get('response') this.log(response=response) flow_name = response['flow_name'] scheduled_at = response.get('time') delay = response.get('delay') this.save(name=f'Delayed {flow_name}') if scheduled_at is not None: scheduled_at_t = datetime.datetime.strptime(scheduled_at, '%H:%M:%S%z').timetz() this.log(scheduled_at_t=scheduled_at_t) now = datetime.datetime.now(datetime.timezone.utc) this.log(now=now) today = now.date() this.log(today=today) scheduled = datetime.datetime.combine(today, scheduled_at_t) this.log(scheduled=scheduled) if scheduled < now: # tomorrow tomorrow = today + datetime.timedelta(days=1) scheduled = datetime.datetime.combine(tomorrow, scheduled_at_t) this.log(scheduled=scheduled) scheduled_ts = scheduled.isoformat(sep=' ', timespec='minutes') this.log(scheduled_ts=scheduled_ts) delta_sec = (scheduled - now).total_seconds() this.log(delta_sec=delta_sec) this.save(message=scheduled_ts) this.sleep(delta_sec) elif delay is not None: this.save(message=f'sleeping for {delay} seconds') this.log(f'sleeping for {delay} seconds') this.sleep(delay) else: return this.error('Missing response for "time" or "delay"') this.flow( flow_name, inputs=inputs, name=f'delayed {flow_name}', wait=False, ) return this.success(f'successfully started {flow_name}')