def resume(self): # Restore runner and action parameters since they are not provided on resume. runner_parameters, action_parameters = param_utils.render_final_params( self.runner_type.runner_parameters, self.action.parameters, self.liveaction.parameters, self.liveaction.context, ) # Assign runner parameters needed for pre-run. if runner_parameters: self.runner_parameters = runner_parameters # Restore chain holder if it is not initialized. if not self.chain_holder: self.pre_run() # Change the status of the liveaction from resuming to running. self.liveaction = action_service.update_status( self.liveaction, action_constants.LIVEACTION_STATUS_RUNNING, publish=False) # Run the action chain. return self._run_chain(action_parameters, resuming=True)
def resume(self): # Restore runner and action parameters since they are not provided on resume. runner_parameters, action_parameters = param_utils.render_final_params( self.runner_type.runner_parameters, self.action.parameters, self.liveaction.parameters, self.liveaction.context ) # Assign runner parameters needed for pre-run. if runner_parameters: self.runner_parameters = runner_parameters # Restore chain holder if it is not initialized. if not self.chain_holder: self.pre_run() # Change the status of the liveaction from resuming to running. self.liveaction = action_service.update_status( self.liveaction, action_constants.LIVEACTION_STATUS_RUNNING, publish=False ) # Run the action chain. return self._run_chain(action_parameters, resuming=True)
def request(wf_def, ac_ex_db, st2_ctx): wf_ac_ex_id = str(ac_ex_db.id) LOG.info('[%s] Processing action execution request for workflow.', wf_ac_ex_id) # Load workflow definition into workflow spec model. spec_module = specs_loader.get_spec_module('native') wf_spec = spec_module.instantiate(wf_def) # Inspect the workflow spec. inspect(wf_spec, st2_ctx, raise_exception=True) # Identify the action to execute. action_db = action_utils.get_action_by_ref(ref=ac_ex_db.action['ref']) if not action_db: error = 'Unable to find action "%s".' % ac_ex_db.action['ref'] raise ac_exc.InvalidActionReferencedException(error) # Identify the runner for the action. runner_type_db = action_utils.get_runnertype_by_name( action_db.runner_type['name']) # Render action execution parameters. runner_params, action_params = param_utils.render_final_params( runner_type_db.runner_parameters, action_db.parameters, ac_ex_db.parameters, ac_ex_db.context) # Instantiate the workflow conductor. conductor_params = {'inputs': action_params, 'context': st2_ctx} conductor = conducting.WorkflowConductor(wf_spec, **conductor_params) # Set the initial workflow state to requested. conductor.request_workflow_state(states.REQUESTED) # Serialize the conductor which initializes some internal values. data = conductor.serialize() # Create a record for workflow execution. wf_ex_db = wf_db_models.WorkflowExecutionDB(action_execution=str( ac_ex_db.id), spec=data['spec'], graph=data['graph'], flow=data['flow'], context=data['context'], input=data['input'], output=data['output'], errors=data['errors'], status=data['state']) # Insert new record into the database and publish to the message bus. wf_ex_db = wf_db_access.WorkflowExecution.insert(wf_ex_db, publish=True) LOG.info('[%s] Workflow execution "%s" created.', wf_ac_ex_id, str(wf_ex_db.id)) return wf_ex_db
def request(wf_def, ac_ex_db): # Load workflow definition into workflow spec model. spec_module = specs_loader.get_spec_module('native') wf_spec = spec_module.instantiate(wf_def) # Inspect the workflow spec. wf_spec.inspect(raise_exception=True) # Identify the action to execute. action_db = ac_db_util.get_action_by_ref(ref=ac_ex_db.action['ref']) if not action_db: error = 'Unable to find action "%s".' % ac_ex_db.action['ref'] raise ac_exc.InvalidActionReferencedException(error) # Identify the runner for the action. runner_type_db = ac_db_util.get_runnertype_by_name(action_db.runner_type['name']) # Render action execution parameters. runner_params, action_params = param_utils.render_final_params( runner_type_db.runner_parameters, action_db.parameters, ac_ex_db.parameters, ac_ex_db.context ) # Instantiate the workflow conductor. conductor = conducting.WorkflowConductor(wf_spec, **action_params) conductor.set_workflow_state(states.REQUESTED) # Serialize the conductor which initializes some internal values. data = conductor.serialize() # Create a record for workflow execution. wf_ex_db = wf_db_models.WorkflowExecutionDB( action_execution=str(ac_ex_db.id), spec=data['spec'], graph=data['graph'], flow=data['flow'], input=data['input'], output=data['output'], errors=data['errors'], status=data['state'] ) # Insert new record into the database and publish to the message bus. wf_ex_db = wf_db_access.WorkflowExecution.insert(wf_ex_db, publish=True) return wf_ex_db
def _do_run(self, runner, runnertype_db, action_db, liveaction_db): # Create a temporary auth token which will be available # for the duration of the action execution. runner.auth_token = self._create_auth_token(runner.context) updated_liveaction_db = None try: # Finalized parameters are resolved and then rendered. This process could # fail. Handle the exception and report the error correctly. try: runner_params, action_params = param_utils.render_final_params( runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters, liveaction_db.context) runner.runner_parameters = runner_params except ParamException as e: raise actionrunner.ActionRunnerException(str(e)) LOG.debug('Performing pre-run for runner: %s', runner.runner_id) runner.pre_run() # Mask secret parameters in the log context resolved_action_params = ResolvedActionParameters(action_db=action_db, runner_type_db=runnertype_db, runner_parameters=runner_params, action_parameters=action_params) extra = {'runner': runner, 'parameters': resolved_action_params} LOG.debug('Performing run for runner: %s' % (runner.runner_id), extra=extra) (status, result, context) = runner.run(action_params) try: result = json.loads(result) except: pass action_completed = status in action_constants.LIVEACTION_COMPLETED_STATES if isinstance(runner, AsyncActionRunner) and not action_completed: self._setup_async_query(liveaction_db.id, runnertype_db, context) except: LOG.exception('Failed to run action.') _, ex, tb = sys.exc_info() # mark execution as failed. status = action_constants.LIVEACTION_STATUS_FAILED # include the error message and traceback to try and provide some hints. result = {'error': str(ex), 'traceback': ''.join(traceback.format_tb(tb, 20))} context = None finally: # Log action completion extra = {'result': result, 'status': status} LOG.debug('Action "%s" completed.' % (action_db.name), extra=extra) # Always clean-up the auth_token try: LOG.debug('Setting status: %s for liveaction: %s', status, liveaction_db.id) updated_liveaction_db = self._update_live_action_db(liveaction_db.id, status, result, context) except: error = 'Cannot update LiveAction object for id: %s, status: %s, result: %s.' % ( liveaction_db.id, status, result) LOG.exception(error) raise executions.update_execution(updated_liveaction_db) extra = {'liveaction_db': updated_liveaction_db} LOG.debug('Updated liveaction after run', extra=extra) # Deletion of the runner generated auth token is delayed until the token expires. # Async actions such as Mistral workflows uses the auth token to launch other # actions in the workflow. If the auth token is deleted here, then the actions # in the workflow will fail with unauthorized exception. is_async_runner = isinstance(runner, AsyncActionRunner) action_completed = status in action_constants.LIVEACTION_COMPLETED_STATES if not is_async_runner or (is_async_runner and action_completed): try: self._delete_auth_token(runner.auth_token) except: LOG.exception('Unable to clean-up auth_token.') LOG.debug('Performing post_run for runner: %s', runner.runner_id) runner.post_run(status, result) runner.container_service = None LOG.debug('Runner do_run result', extra={'result': updated_liveaction_db.result}) LOG.audit('Liveaction completed', extra={'liveaction_db': updated_liveaction_db}) return updated_liveaction_db
def test_render_final_params_and_shell_script_action_command_strings(self): runner_parameters = {} action_db_parameters = { 'project': { 'type': 'string', 'default': 'st2', 'position': 0, }, 'version': { 'type': 'string', 'position': 1, 'required': True }, 'fork': { 'type': 'string', 'position': 2, 'default': 'StackStorm', }, 'branch': { 'type': 'string', 'position': 3, 'default': 'master', }, 'update_mistral': { 'type': 'boolean', 'position': 4, 'default': False }, 'update_changelog': { 'type': 'boolean', 'position': 5, 'default': False }, 'local_repo': { 'type': 'string', 'position': 6, } } context = {} # 1. All default values used live_action_db_parameters = { 'project': 'st2flow', 'version': '3.0.0', 'fork': 'StackStorm', 'local_repo': '/tmp/repo' } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { 'project': 'st2flow', 'version': '3.0.0', 'fork': 'StackStorm', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': False, # default value used 'local_repo': '/tmp/repo' }) # 2. Some default values used live_action_db_parameters = { 'project': 'st2web', 'version': '3.1.0', 'fork': 'StackStorm1', 'update_changelog': True, 'local_repo': '/tmp/repob' } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { 'project': 'st2web', 'version': '3.1.0', 'fork': 'StackStorm1', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': True, # default value used 'local_repo': '/tmp/repob' }) # 3. None is specified for a boolean parameter, should use a default live_action_db_parameters = { 'project': 'st2rbac', 'version': '3.2.0', 'fork': 'StackStorm2', 'update_changelog': None, 'local_repo': '/tmp/repoc' } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { 'project': 'st2rbac', 'version': '3.2.0', 'fork': 'StackStorm2', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': False, # default value used 'local_repo': '/tmp/repoc' })
def _do_run(self, runner): # Create a temporary auth token which will be available # for the duration of the action execution. runner.auth_token = self._create_auth_token( context=runner.context, action_db=runner.action, liveaction_db=runner.liveaction) try: # Finalized parameters are resolved and then rendered. This process could # fail. Handle the exception and report the error correctly. try: runner_params, action_params = param_utils.render_final_params( runner.runner_type.runner_parameters, runner.action.parameters, runner.liveaction.parameters, runner.liveaction.context) runner.runner_parameters = runner_params except ParamException as e: raise actionrunner.ActionRunnerException(six.text_type(e)) LOG.debug('Performing pre-run for runner: %s', runner.runner_id) runner.pre_run() # Mask secret parameters in the log context resolved_action_params = ResolvedActionParameters( action_db=runner.action, runner_type_db=runner.runner_type, runner_parameters=runner_params, action_parameters=action_params) extra = {'runner': runner, 'parameters': resolved_action_params} LOG.debug('Performing run for runner: %s' % (runner.runner_id), extra=extra) with CounterWithTimer(key='action.executions'): with CounterWithTimer(key='action.%s.executions' % (runner.action.ref)): (status, result, context) = runner.run(action_params) result = jsonify.try_loads(result) action_completed = status in action_constants.LIVEACTION_COMPLETED_STATES if (isinstance(runner, PollingAsyncActionRunner) and runner.is_polling_enabled() and not action_completed): queries.setup_query(runner.liveaction.id, runner.runner_type, context) except: LOG.exception('Failed to run action.') _, ex, tb = sys.exc_info() # mark execution as failed. status = action_constants.LIVEACTION_STATUS_FAILED # include the error message and traceback to try and provide some hints. result = {'error': str(ex), 'traceback': ''.join(traceback.format_tb(tb, 20))} context = None finally: # Log action completion extra = {'result': result, 'status': status} LOG.debug('Action "%s" completed.' % (runner.action.name), extra=extra) # Update the final status of liveaction and corresponding action execution. runner.liveaction = self._update_status(runner.liveaction.id, status, result, context) # Always clean-up the auth_token # This method should be called in the finally block to ensure post_run is not impacted. self._clean_up_auth_token(runner=runner, status=status) LOG.debug('Performing post_run for runner: %s', runner.runner_id) runner.post_run(status=status, result=result) LOG.debug('Runner do_run result', extra={'result': runner.liveaction.result}) LOG.audit('Liveaction completed', extra={'liveaction_db': runner.liveaction}) return runner.liveaction
def test_render_final_params_and_shell_script_action_command_strings(self): runner_parameters = {} action_db_parameters = { "project": { "type": "string", "default": "st2", "position": 0, }, "version": {"type": "string", "position": 1, "required": True}, "fork": { "type": "string", "position": 2, "default": "StackStorm", }, "branch": { "type": "string", "position": 3, "default": "master", }, "update_changelog": {"type": "boolean", "position": 4, "default": False}, "local_repo": { "type": "string", "position": 5, }, } context = {} # 1. All default values used live_action_db_parameters = { "project": "st2flow", "version": "3.0.0", "fork": "StackStorm", "local_repo": "/tmp/repo", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context ) self.assertDictEqual( action_params, { "project": "st2flow", "version": "3.0.0", "fork": "StackStorm", "branch": "master", # default value used "update_changelog": False, # default value used "local_repo": "/tmp/repo", }, ) # 2. Some default values used live_action_db_parameters = { "project": "st2web", "version": "3.1.0", "fork": "StackStorm1", "update_changelog": True, "local_repo": "/tmp/repob", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context ) self.assertDictEqual( action_params, { "project": "st2web", "version": "3.1.0", "fork": "StackStorm1", "branch": "master", # default value used "update_changelog": True, # default value used "local_repo": "/tmp/repob", }, ) # 3. None is specified for a boolean parameter, should use a default live_action_db_parameters = { "project": "st2rbac", "version": "3.2.0", "fork": "StackStorm2", "update_changelog": None, "local_repo": "/tmp/repoc", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context ) self.assertDictEqual( action_params, { "project": "st2rbac", "version": "3.2.0", "fork": "StackStorm2", "branch": "master", # default value used "update_changelog": False, # default value used "local_repo": "/tmp/repoc", }, )
def _do_run(self, runner, runnertype_db, action_db, liveaction_db): # Create a temporary auth token which will be available # for the duration of the action execution. runner.auth_token = self._create_auth_token(runner.context) updated_liveaction_db = None try: # Finalized parameters are resolved and then rendered. This process could # fail. Handle the exception and report the error correctly. try: runner_params, action_params = param_utils.render_final_params( runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters, liveaction_db.context) runner.runner_parameters = runner_params except ParamException as e: raise actionrunner.ActionRunnerException(str(e)) LOG.debug('Performing pre-run for runner: %s', runner.runner_id) runner.pre_run() # Mask secret parameters in the log context resolved_action_params = ResolvedActionParameters( action_db=action_db, runner_type_db=runnertype_db, runner_parameters=runner_params, action_parameters=action_params) extra = {'runner': runner, 'parameters': resolved_action_params} LOG.debug('Performing run for runner: %s' % (runner.runner_id), extra=extra) (status, result, context) = runner.run(action_params) try: result = json.loads(result) except: pass action_completed = status in action_constants.LIVEACTION_COMPLETED_STATES if isinstance(runner, AsyncActionRunner) and not action_completed: self._setup_async_query(liveaction_db.id, runnertype_db, context) except: LOG.exception('Failed to run action.') _, ex, tb = sys.exc_info() # mark execution as failed. status = action_constants.LIVEACTION_STATUS_FAILED # include the error message and traceback to try and provide some hints. result = { 'error': str(ex), 'traceback': ''.join(traceback.format_tb(tb, 20)) } context = None finally: # Log action completion extra = {'result': result, 'status': status} LOG.debug('Action "%s" completed.' % (action_db.name), extra=extra) # Always clean-up the auth_token try: LOG.debug('Setting status: %s for liveaction: %s', status, liveaction_db.id) updated_liveaction_db = self._update_live_action_db( liveaction_db.id, status, result, context) except: error = 'Cannot update LiveAction object for id: %s, status: %s, result: %s.' % ( liveaction_db.id, status, result) LOG.exception(error) raise executions.update_execution(updated_liveaction_db) extra = {'liveaction_db': updated_liveaction_db} LOG.debug('Updated liveaction after run', extra=extra) # Deletion of the runner generated auth token is delayed until the token expires. # Async actions such as Mistral workflows uses the auth token to launch other # actions in the workflow. If the auth token is deleted here, then the actions # in the workflow will fail with unauthorized exception. is_async_runner = isinstance(runner, AsyncActionRunner) action_completed = status in action_constants.LIVEACTION_COMPLETED_STATES if not is_async_runner or (is_async_runner and action_completed): try: self._delete_auth_token(runner.auth_token) except: LOG.exception('Unable to clean-up auth_token.') LOG.debug('Performing post_run for runner: %s', runner.runner_id) runner.post_run(status, result) runner.container_service = None LOG.debug('Runner do_run result', extra={'result': updated_liveaction_db.result}) LOG.audit('Liveaction completed', extra={'liveaction_db': updated_liveaction_db}) return updated_liveaction_db
def test_command_construction_correct_default_parameter_values_are_used( self): runner_parameters = {} action_db_parameters = { "project": { "type": "string", "default": "st2", "position": 0, }, "version": { "type": "string", "position": 1, "required": True }, "fork": { "type": "string", "position": 2, "default": "StackStorm", }, "branch": { "type": "string", "position": 3, "default": "master", }, "update_changelog": { "type": "boolean", "position": 4, "default": False }, "local_repo": { "type": "string", "position": 5, }, } context = {} action_db = ActionDB(pack="dummy", name="action") runner = LocalShellScriptRunner("id") runner.runner_parameters = {} runner.action = action_db # 1. All default values used live_action_db_parameters = { "project": "st2flow", "version": "3.0.0", "fork": "StackStorm", "local_repo": "/tmp/repo", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { "project": "st2flow", "version": "3.0.0", "fork": "StackStorm", "branch": "master", # default value used "update_changelog": False, # default value used "local_repo": "/tmp/repo", }, ) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction( name="dummy", action_exec_id="dummy", script_local_path_abs="/tmp/local.sh", named_args=named_args, positional_args=positional_args, ) command_string = shell_script_action.get_full_command_string() expected = "/tmp/local.sh st2flow 3.0.0 StackStorm master 0 /tmp/repo" self.assertEqual(command_string, expected) # 2. Some default values used live_action_db_parameters = { "project": "st2web", "version": "3.1.0", "fork": "StackStorm1", "update_changelog": True, "local_repo": "/tmp/repob", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { "project": "st2web", "version": "3.1.0", "fork": "StackStorm1", "branch": "master", # default value used "update_changelog": True, # default value used "local_repo": "/tmp/repob", }, ) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction( name="dummy", action_exec_id="dummy", script_local_path_abs="/tmp/local.sh", named_args=named_args, positional_args=positional_args, ) command_string = shell_script_action.get_full_command_string() expected = "/tmp/local.sh st2web 3.1.0 StackStorm1 master 1 /tmp/repob" self.assertEqual(command_string, expected) # 3. None is specified for a boolean parameter, should use a default live_action_db_parameters = { "project": "st2rbac", "version": "3.2.0", "fork": "StackStorm2", "update_changelog": None, "local_repo": "/tmp/repoc", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { "project": "st2rbac", "version": "3.2.0", "fork": "StackStorm2", "branch": "master", # default value used "update_changelog": False, # default value used "local_repo": "/tmp/repoc", }, ) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction( name="dummy", action_exec_id="dummy", script_local_path_abs="/tmp/local.sh", named_args=named_args, positional_args=positional_args, ) command_string = shell_script_action.get_full_command_string() expected = "/tmp/local.sh st2rbac 3.2.0 StackStorm2 master 0 /tmp/repoc" self.assertEqual(command_string, expected)
def _do_run(self, runner): # Create a temporary auth token which will be available # for the duration of the action execution. runner.auth_token = self._create_auth_token( context=runner.context, action_db=runner.action, liveaction_db=runner.liveaction, ) try: # Finalized parameters are resolved and then rendered. This process could # fail. Handle the exception and report the error correctly. try: runner_params, action_params = param_utils.render_final_params( runner.runner_type.runner_parameters, runner.action.parameters, runner.liveaction.parameters, runner.liveaction.context, ) runner.runner_parameters = runner_params except ParamException as e: raise actionrunner.ActionRunnerException(six.text_type(e)) LOG.debug("Performing pre-run for runner: %s", runner.runner_id) runner.pre_run() # Mask secret parameters in the log context resolved_action_params = ResolvedActionParameters( action_db=runner.action, runner_type_db=runner.runner_type, runner_parameters=runner_params, action_parameters=action_params, ) extra = {"runner": runner, "parameters": resolved_action_params} LOG.debug("Performing run for runner: %s" % (runner.runner_id), extra=extra) with CounterWithTimer(key="action.executions"): with CounterWithTimer(key="action.%s.executions" % (runner.action.ref)): (status, result, context) = runner.run(action_params) result = jsonify.try_loads(result) action_completed = status in action_constants.LIVEACTION_COMPLETED_STATES if (isinstance(runner, PollingAsyncActionRunner) and runner.is_polling_enabled() and not action_completed): queries.setup_query(runner.liveaction.id, runner.runner_type, context) except: LOG.exception("Failed to run action.") _, ex, tb = sys.exc_info() # mark execution as failed. status = action_constants.LIVEACTION_STATUS_FAILED # include the error message and traceback to try and provide some hints. result = { "error": str(ex), "traceback": "".join(traceback.format_tb(tb, 20)), } context = None finally: # Log action completion extra = {"result": result, "status": status} LOG.debug('Action "%s" completed.' % (runner.action.name), extra=extra) # Update the final status of liveaction and corresponding action execution. with Timer(key="action.executions.update_status"): runner.liveaction = self._update_status( runner.liveaction.id, status, result, context) # Always clean-up the auth_token # This method should be called in the finally block to ensure post_run is not impacted. self._clean_up_auth_token(runner=runner, status=status) LOG.debug("Performing post_run for runner: %s", runner.runner_id) runner.post_run(status=status, result=result) LOG.debug("Runner do_run result", extra={"result": runner.liveaction.result}) LOG.audit("Liveaction completed", extra={"liveaction_db": runner.liveaction}) return runner.liveaction
def test_command_construction_correct_default_parameter_values_are_used( self): runner_parameters = {} action_db_parameters = { "project": { "type": "string", "default": "st2", "position": 0, }, "version": { "type": "string", "position": 1, "required": True }, "fork": { "type": "string", "position": 2, "default": "StackStorm", }, "branch": { "type": "string", "position": 3, "default": "master", }, "update_changelog": { "type": "boolean", "position": 4, "default": False }, "local_repo": { "type": "string", "position": 5, }, } context = {} action_db = ActionDB(pack="dummy", name="action") runner = ParamikoRemoteScriptRunner("id") runner.runner_parameters = {} runner.action = action_db # 1. All default values used live_action_db_parameters = { "project": "st2flow", "version": "3.0.0", "fork": "StackStorm", "local_repo": "/tmp/repo", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { "project": "st2flow", "version": "3.0.0", "fork": "StackStorm", "branch": "master", # default value used "update_changelog": False, # default value used "local_repo": "/tmp/repo", }, ) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) remote_action = ParamikoRemoteScriptAction( "foo-script", "id", script_local_path_abs="/tmp/script.sh", script_local_libs_path_abs=None, named_args=named_args, positional_args=positional_args, env_vars={}, on_behalf_user="******", user="******", remote_dir="/tmp", hosts=["127.0.0.1"], cwd="/test/cwd/", ) command_string = remote_action.get_full_command_string() expected = "cd /test/cwd/ && /tmp/script.sh st2flow 3.0.0 StackStorm master 0 /tmp/repo" self.assertEqual(command_string, expected) # 2. Some default values used live_action_db_parameters = { "project": "st2web", "version": "3.1.0", "fork": "StackStorm1", "update_changelog": True, "local_repo": "/tmp/repob", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { "project": "st2web", "version": "3.1.0", "fork": "StackStorm1", "branch": "master", # default value used "update_changelog": True, # default value used "local_repo": "/tmp/repob", }, ) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) remote_action = ParamikoRemoteScriptAction( "foo-script", "id", script_local_path_abs="/tmp/script.sh", script_local_libs_path_abs=None, named_args=named_args, positional_args=positional_args, env_vars={}, on_behalf_user="******", user="******", remote_dir="/tmp", hosts=["127.0.0.1"], cwd="/test/cwd/", ) command_string = remote_action.get_full_command_string() expected = "cd /test/cwd/ && /tmp/script.sh st2web 3.1.0 StackStorm1 master 1 /tmp/repob" self.assertEqual(command_string, expected) # 3. None is specified for a boolean parameter, should use a default live_action_db_parameters = { "project": "st2rbac", "version": "3.2.0", "fork": "StackStorm2", "update_changelog": None, "local_repo": "/tmp/repoc", } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { "project": "st2rbac", "version": "3.2.0", "fork": "StackStorm2", "branch": "master", # default value used "update_changelog": False, # default value used "local_repo": "/tmp/repoc", }, ) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) remote_action = ParamikoRemoteScriptAction( "foo-script", "id", script_local_path_abs="/tmp/script.sh", script_local_libs_path_abs=None, named_args=named_args, positional_args=positional_args, env_vars={}, on_behalf_user="******", user="******", remote_dir="/tmp", hosts=["127.0.0.1"], cwd="/test/cwd/", ) command_string = remote_action.get_full_command_string() expected = "cd /test/cwd/ && /tmp/script.sh st2rbac 3.2.0 StackStorm2 master 0 /tmp/repoc" self.assertEqual(command_string, expected)
def test_command_construction_correct_default_parameter_values_are_used(self): runner_parameters = {} action_db_parameters = { 'project': { 'type': 'string', 'default': 'st2', 'position': 0, }, 'version': { 'type': 'string', 'position': 1, 'required': True }, 'fork': { 'type': 'string', 'position': 2, 'default': 'StackStorm', }, 'branch': { 'type': 'string', 'position': 3, 'default': 'master', }, 'update_changelog': { 'type': 'boolean', 'position': 4, 'default': False }, 'local_repo': { 'type': 'string', 'position': 5, } } context = {} action_db = ActionDB(pack='dummy', name='action') runner = LocalShellScriptRunner('id') runner.runner_parameters = {} runner.action = action_db # 1. All default values used live_action_db_parameters = { 'project': 'st2flow', 'version': '3.0.0', 'fork': 'StackStorm', 'local_repo': '/tmp/repo' } runner_params, action_params = param_utils.render_final_params(runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual(action_params, { 'project': 'st2flow', 'version': '3.0.0', 'fork': 'StackStorm', 'branch': 'master', # default value used 'update_changelog': False, # default value used 'local_repo': '/tmp/repo' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction(name='dummy', action_exec_id='dummy', script_local_path_abs='/tmp/local.sh', named_args=named_args, positional_args=positional_args) command_string = shell_script_action.get_full_command_string() expected = '/tmp/local.sh st2flow 3.0.0 StackStorm master 0 /tmp/repo' self.assertEqual(command_string, expected) # 2. Some default values used live_action_db_parameters = { 'project': 'st2web', 'version': '3.1.0', 'fork': 'StackStorm1', 'update_changelog': True, 'local_repo': '/tmp/repob' } runner_params, action_params = param_utils.render_final_params(runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual(action_params, { 'project': 'st2web', 'version': '3.1.0', 'fork': 'StackStorm1', 'branch': 'master', # default value used 'update_changelog': True, # default value used 'local_repo': '/tmp/repob' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction(name='dummy', action_exec_id='dummy', script_local_path_abs='/tmp/local.sh', named_args=named_args, positional_args=positional_args) command_string = shell_script_action.get_full_command_string() expected = '/tmp/local.sh st2web 3.1.0 StackStorm1 master 1 /tmp/repob' self.assertEqual(command_string, expected) # 3. None is specified for a boolean parameter, should use a default live_action_db_parameters = { 'project': 'st2rbac', 'version': '3.2.0', 'fork': 'StackStorm2', 'update_changelog': None, 'local_repo': '/tmp/repoc' } runner_params, action_params = param_utils.render_final_params(runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual(action_params, { 'project': 'st2rbac', 'version': '3.2.0', 'fork': 'StackStorm2', 'branch': 'master', # default value used 'update_changelog': False, # default value used 'local_repo': '/tmp/repoc' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction(name='dummy', action_exec_id='dummy', script_local_path_abs='/tmp/local.sh', named_args=named_args, positional_args=positional_args) command_string = shell_script_action.get_full_command_string() expected = '/tmp/local.sh st2rbac 3.2.0 StackStorm2 master 0 /tmp/repoc' self.assertEqual(command_string, expected)
def test_command_construction_correct_default_parameter_values_are_used( self): runner_parameters = {} action_db_parameters = { 'project': { 'type': 'string', 'default': 'st2', 'position': 0, }, 'version': { 'type': 'string', 'position': 1, 'required': True }, 'fork': { 'type': 'string', 'position': 2, 'default': 'StackStorm', }, 'branch': { 'type': 'string', 'position': 3, 'default': 'master', }, 'update_mistral': { 'type': 'boolean', 'position': 4, 'default': False }, 'update_changelog': { 'type': 'boolean', 'position': 5, 'default': False }, 'local_repo': { 'type': 'string', 'position': 6, } } context = {} action_db = ActionDB(pack='dummy', name='action') runner = ParamikoRemoteScriptRunner('id') runner.runner_parameters = {} runner.action = action_db # 1. All default values used live_action_db_parameters = { 'project': 'st2flow', 'version': '3.0.0', 'fork': 'StackStorm', 'local_repo': '/tmp/repo' } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { 'project': 'st2flow', 'version': '3.0.0', 'fork': 'StackStorm', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': False, # default value used 'local_repo': '/tmp/repo' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) remote_action = ParamikoRemoteScriptAction( 'foo-script', 'id', script_local_path_abs='/tmp/script.sh', script_local_libs_path_abs=None, named_args=named_args, positional_args=positional_args, env_vars={}, on_behalf_user='******', user='******', remote_dir='/tmp', hosts=['127.0.0.1'], cwd='/test/cwd/') command_string = remote_action.get_full_command_string() expected = 'cd /test/cwd/ && /tmp/script.sh st2flow 3.0.0 StackStorm master 0 0 /tmp/repo' self.assertEqual(command_string, expected) # 2. Some default values used live_action_db_parameters = { 'project': 'st2web', 'version': '3.1.0', 'fork': 'StackStorm1', 'update_changelog': True, 'local_repo': '/tmp/repob' } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { 'project': 'st2web', 'version': '3.1.0', 'fork': 'StackStorm1', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': True, # default value used 'local_repo': '/tmp/repob' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) remote_action = ParamikoRemoteScriptAction( 'foo-script', 'id', script_local_path_abs='/tmp/script.sh', script_local_libs_path_abs=None, named_args=named_args, positional_args=positional_args, env_vars={}, on_behalf_user='******', user='******', remote_dir='/tmp', hosts=['127.0.0.1'], cwd='/test/cwd/') command_string = remote_action.get_full_command_string() expected = 'cd /test/cwd/ && /tmp/script.sh st2web 3.1.0 StackStorm1 master 0 1 /tmp/repob' self.assertEqual(command_string, expected) # 3. None is specified for a boolean parameter, should use a default live_action_db_parameters = { 'project': 'st2rbac', 'version': '3.2.0', 'fork': 'StackStorm2', 'update_changelog': None, 'local_repo': '/tmp/repoc' } runner_params, action_params = param_utils.render_final_params( runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual( action_params, { 'project': 'st2rbac', 'version': '3.2.0', 'fork': 'StackStorm2', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': False, # default value used 'local_repo': '/tmp/repoc' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) remote_action = ParamikoRemoteScriptAction( 'foo-script', 'id', script_local_path_abs='/tmp/script.sh', script_local_libs_path_abs=None, named_args=named_args, positional_args=positional_args, env_vars={}, on_behalf_user='******', user='******', remote_dir='/tmp', hosts=['127.0.0.1'], cwd='/test/cwd/') command_string = remote_action.get_full_command_string() expected = 'cd /test/cwd/ && /tmp/script.sh st2rbac 3.2.0 StackStorm2 master 0 0 /tmp/repoc' self.assertEqual(command_string, expected)
def main(): # Read DEBUG value from the environment variable debug = os.environ.get('ST2_DEBUG', False) if str(debug).lower() in ['true', '1']: debug = True if debug: LOG.setLevel(logging.DEBUG) else: LOG.setLevel(logging.INFO) # Read input = os.environ.get('ST2_INPUT', {}) if isinstance(input, six.string_types): try: input = json.loads(input) except ValueError as e: LOG.error("ERROR: Can not parse `input`: '{}'\n{}".format(str(input), str(e))) raise e LOG.debug("Received input: " + json.dumps(input, indent=2)) # Read action name from environment variable action_name = os.environ['ST2_ACTION'] try: action_db = ACTIONS[action_name] except KeyError: raise ValueError('No action named "%s" has been installed.' % (action_name)) # Initialize runner manager = DriverManager(namespace='st2common.runners.runner', invoke_on_load=False, name=action_db.runner_type['name']) runnertype_db = RunnerTypeAPI.to_model(RunnerTypeAPI(**manager.driver.get_metadata())) runner = manager.driver.get_runner() runner._sandbox = False runner.runner_type_db = runnertype_db runner.action = action_db runner.action_name = action_db.name runner.entry_point = content_utils.get_entry_point_abs_path(pack=action_db.pack, entry_point=action_db.entry_point) runner.context = {} runner.libs_dir_path = content_utils.get_action_libs_abs_path(pack=action_db.pack, entry_point=action_db.entry_point) config_schema = CONFIG_SCHEMAS.get(action_db.pack, None) config_values = os.environ.get('ST2_CONFIG', None) if config_schema and config_values: runner._config = validate_config_against_schema(config_schema=config_schema, config_object=json.loads(config_values), config_path=None, pack_name=action_db.pack) param_values = os.environ.get('ST2_PARAMETERS', None) try: if param_values: live_params = param_utils.render_live_params( runner_parameters=runnertype_db.runner_parameters, action_parameters=action_db.parameters, params=json.loads(param_values), action_context={}, additional_contexts={ 'input': input }) else: live_params = input if debug and 'log_level' not in live_params: # Set log_level runner parameter live_params['log_level'] = 'DEBUG' runner_params, action_params = param_utils.render_final_params( runner_parameters=runnertype_db.runner_parameters, action_parameters=action_db.parameters, params=live_params, action_context={}) except ParamException as e: raise actionrunner.ActionRunnerException(str(e)) runner.runner_parameters = runner_params LOG.debug('Performing pre-run for runner: %s', runner.runner_id) runner.pre_run() (status, output, context) = runner.run(action_params) try: output['result'] = json.loads(output['result']) except Exception: pass output_values = os.environ.get('ST2_OUTPUT', None) if output_values: try: result = param_utils.render_live_params( runner_parameters={}, action_parameters={}, params=json.loads(output_values), action_context={}, additional_contexts={ 'input': input, 'output': output }) except ParamException as e: raise actionrunner.ActionRunnerException(str(e)) else: result = output output = output or {} if output.get('stdout', None): LOG.info('Action stdout: %s' % (output['stdout'])) if output.get('stderr', None): LOG.info('Action stderr and logs: %s' % (output['stderr'])) print(json.dumps(result))
def request(wf_def, ac_ex_db, st2_ctx, notify_cfg=None): wf_ac_ex_id = str(ac_ex_db.id) LOG.info('[%s] Processing action execution request for workflow.', wf_ac_ex_id) # Load workflow definition into workflow spec model. spec_module = specs_loader.get_spec_module('native') wf_spec = spec_module.instantiate(wf_def) # Inspect the workflow spec. inspect(wf_spec, st2_ctx, raise_exception=True) # Identify the action to execute. action_db = action_utils.get_action_by_ref(ref=ac_ex_db.action['ref']) if not action_db: error = 'Unable to find action "%s".' % ac_ex_db.action['ref'] raise ac_exc.InvalidActionReferencedException(error) # Identify the runner for the action. runner_type_db = action_utils.get_runnertype_by_name( action_db.runner_type['name']) # Render action execution parameters. runner_params, action_params = param_utils.render_final_params( runner_type_db.runner_parameters, action_db.parameters, ac_ex_db.parameters, ac_ex_db.context) # Instantiate the workflow conductor. conductor_params = {'inputs': action_params, 'context': st2_ctx} conductor = conducting.WorkflowConductor(wf_spec, **conductor_params) # Serialize the conductor which initializes some internal values. data = conductor.serialize() # Create a record for workflow execution. wf_ex_db = wf_db_models.WorkflowExecutionDB(action_execution=str( ac_ex_db.id), spec=data['spec'], graph=data['graph'], input=data['input'], context=data['context'], state=data['state'], status=data['state']['status'], output=data['output'], errors=data['errors']) # Inspect that the list of tasks in the notify parameter exist in the workflow spec. if runner_params.get('notify'): invalid_tasks = list( set(runner_params.get('notify')) - set(wf_spec.tasks.keys())) if invalid_tasks: raise wf_exc.WorkflowExecutionException( 'The following tasks in the notify parameter do not exist ' 'in the workflow definition: %s.' % ', '.join(invalid_tasks)) # Write notify instruction to record. if notify_cfg: # Set up the notify instruction in the workflow execution record. wf_ex_db.notify = { 'config': notify_cfg, 'tasks': runner_params.get('notify') } # Insert new record into the database and do not publish to the message bus yet. wf_ex_db = wf_db_access.WorkflowExecution.insert(wf_ex_db, publish=False) LOG.info('[%s] Workflow execution "%s" is created.', wf_ac_ex_id, str(wf_ex_db.id)) # Update the context with the workflow execution id created on database insert. # Publish the workflow execution requested status to the message bus. if wf_ex_db.status not in statuses.COMPLETED_STATUSES: # Set the initial workflow status to requested. conductor.request_workflow_status(statuses.REQUESTED) data = conductor.serialize() wf_ex_db.state = data['state'] wf_ex_db.status = data['state']['status'] # Put the ID of the workflow execution record in the context. wf_ex_db.context['st2']['workflow_execution_id'] = str(wf_ex_db.id) wf_ex_db.state['contexts'][0]['st2']['workflow_execution_id'] = str( wf_ex_db.id) # Update the workflow execution record. wf_ex_db = wf_db_access.WorkflowExecution.update(wf_ex_db, publish=False) wf_db_access.WorkflowExecution.publish_status(wf_ex_db) msg = '[%s] Workflow execution "%s" is published.' LOG.info(msg, wf_ac_ex_id, str(wf_ex_db.id)) else: msg = '[%s] Unable to request workflow execution. It is already in completed status "%s".' LOG.info(msg, wf_ac_ex_id, wf_ex_db.status) return wf_ex_db
def _do_run(self, runner, runnertype_db, action_db, liveaction_db): # Create a temporary auth token which will be available # for the duration of the action execution. runner.auth_token = self._create_auth_token(runner.context) updated_liveaction_db = None try: # Finalized parameters are resolved and then rendered. This process could # fail. Handle the exception and report the error correctly. try: runner_params, action_params = param_utils.render_final_params( runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters, liveaction_db.context, ) runner.runner_parameters = runner_params except ParamException as e: raise actionrunner.ActionRunnerException(str(e)) LOG.debug("Performing pre-run for runner: %s", runner.runner_id) runner.pre_run() # Mask secret parameters in the log context resolved_action_params = ResolvedActionParameters( action_db=action_db, runner_type_db=runnertype_db, runner_parameters=runner_params, action_parameters=action_params, ) extra = {"runner": runner, "parameters": resolved_action_params} LOG.debug("Performing run for runner: %s" % (runner.runner_id), extra=extra) (status, result, context) = runner.run(action_params) try: result = json.loads(result) except: pass action_completed = status in action_constants.LIVEACTION_COMPLETED_STATES if isinstance(runner, AsyncActionRunner) and not action_completed: self._setup_async_query(liveaction_db.id, runnertype_db, context) except: LOG.exception("Failed to run action.") _, ex, tb = sys.exc_info() # mark execution as failed. status = action_constants.LIVEACTION_STATUS_FAILED # include the error message and traceback to try and provide some hints. result = {"error": str(ex), "traceback": "".join(traceback.format_tb(tb, 20))} context = None finally: # Log action completion extra = {"result": result, "status": status} LOG.debug('Action "%s" completed.' % (action_db.name), extra=extra) # Always clean-up the auth_token try: LOG.debug("Setting status: %s for liveaction: %s", status, liveaction_db.id) updated_liveaction_db = self._update_live_action_db(liveaction_db.id, status, result, context) except: error = "Cannot update LiveAction object for id: %s, status: %s, result: %s." % ( liveaction_db.id, status, result, ) LOG.exception(error) raise executions.update_execution(updated_liveaction_db) extra = {"liveaction_db": updated_liveaction_db} LOG.debug("Updated liveaction after run", extra=extra) # Always clean-up the auth_token self._clean_up_auth_token(runner=runner, status=status) LOG.debug("Performing post_run for runner: %s", runner.runner_id) runner.post_run(status=status, result=result) runner.container_service = None LOG.debug("Runner do_run result", extra={"result": updated_liveaction_db.result}) LOG.audit("Liveaction completed", extra={"liveaction_db": updated_liveaction_db}) return updated_liveaction_db
def test_command_construction_correct_default_parameter_values_are_used(self): runner_parameters = {} action_db_parameters = { 'project': { 'type': 'string', 'default': 'st2', 'position': 0, }, 'version': { 'type': 'string', 'position': 1, 'required': True }, 'fork': { 'type': 'string', 'position': 2, 'default': 'StackStorm', }, 'branch': { 'type': 'string', 'position': 3, 'default': 'master', }, 'update_mistral': { 'type': 'boolean', 'position': 4, 'default': False }, 'update_changelog': { 'type': 'boolean', 'position': 5, 'default': False }, 'local_repo': { 'type': 'string', 'position': 6, } } context = {} action_db = ActionDB(pack='dummy', name='action') runner = LocalShellScriptRunner('id') runner.runner_parameters = {} runner.action = action_db # 1. All default values used live_action_db_parameters = { 'project': 'st2flow', 'version': '3.0.0', 'fork': 'StackStorm', 'local_repo': '/tmp/repo' } runner_params, action_params = param_utils.render_final_params(runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual(action_params, { 'project': 'st2flow', 'version': '3.0.0', 'fork': 'StackStorm', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': False, # default value used 'local_repo': '/tmp/repo' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction(name='dummy', action_exec_id='dummy', script_local_path_abs='/tmp/local.sh', named_args=named_args, positional_args=positional_args) command_string = shell_script_action.get_full_command_string() expected = '/tmp/local.sh st2flow 3.0.0 StackStorm master 0 0 /tmp/repo' self.assertEqual(command_string, expected) # 2. Some default values used live_action_db_parameters = { 'project': 'st2web', 'version': '3.1.0', 'fork': 'StackStorm1', 'update_changelog': True, 'local_repo': '/tmp/repob' } runner_params, action_params = param_utils.render_final_params(runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual(action_params, { 'project': 'st2web', 'version': '3.1.0', 'fork': 'StackStorm1', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': True, # default value used 'local_repo': '/tmp/repob' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction(name='dummy', action_exec_id='dummy', script_local_path_abs='/tmp/local.sh', named_args=named_args, positional_args=positional_args) command_string = shell_script_action.get_full_command_string() expected = '/tmp/local.sh st2web 3.1.0 StackStorm1 master 0 1 /tmp/repob' self.assertEqual(command_string, expected) # 3. None is specified for a boolean parameter, should use a default live_action_db_parameters = { 'project': 'st2rbac', 'version': '3.2.0', 'fork': 'StackStorm2', 'update_changelog': None, 'local_repo': '/tmp/repoc' } runner_params, action_params = param_utils.render_final_params(runner_parameters, action_db_parameters, live_action_db_parameters, context) self.assertDictEqual(action_params, { 'project': 'st2rbac', 'version': '3.2.0', 'fork': 'StackStorm2', 'branch': 'master', # default value used 'update_mistral': False, # default value used 'update_changelog': False, # default value used 'local_repo': '/tmp/repoc' }) action_db.parameters = action_db_parameters positional_args, named_args = runner._get_script_args(action_params) named_args = runner._transform_named_args(named_args) shell_script_action = ShellScriptAction(name='dummy', action_exec_id='dummy', script_local_path_abs='/tmp/local.sh', named_args=named_args, positional_args=positional_args) command_string = shell_script_action.get_full_command_string() expected = '/tmp/local.sh st2rbac 3.2.0 StackStorm2 master 0 0 /tmp/repoc' self.assertEqual(command_string, expected)
def base(event, context, passthrough=False): # Set up logging logger = logging.getLogger() # Read DEBUG value from the environment variable debug = os.environ.get('ST2_DEBUG', False) if str(debug).lower() in ['true', '1']: debug = True if debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) if isinstance(event, basestring): try: event = json.loads(event) except ValueError as e: LOG.error("ERROR: Can not parse `event`: '{}'\n{}".format( str(event), str(e))) raise e LOG.info("Received event: " + json.dumps(event, indent=2)) # Special case for Lambda function being called over HTTP via API gateway # See # https://serverless.com/framework/docs/providers/aws/events/apigateway # #example-lambda-proxy-event-default # for details is_event_body_string = (isinstance(event.get('body'), basestring) is True) content_type = event.get('headers', {}).get('content-type', '').lower() if is_event_body_string: if content_type == 'application/json': try: event['body'] = json.loads(event['body']) except Exception as e: LOG.warn('`event` has `body` which is not JSON: %s', str(e.message)) elif content_type == 'application/x-www-form-urlencoded': try: event['body'] = dict( parse_qsl(['body'], keep_blank_values=True)) except Exception as e: LOG.warn('`event` has `body` which is not `%s`: %s', content_type, str(e.message)) else: LOG.warn('Unsupported event content type: %s' % (content_type)) action_name = os.environ['ST2_ACTION'] try: action_db = ACTIONS[action_name] except KeyError: raise ValueError('No action named "%s" has been installed.' % (action_name)) manager = DriverManager(namespace='st2common.runners.runner', invoke_on_load=False, name=action_db.runner_type['name']) runnertype_db = RunnerTypeAPI.to_model( RunnerTypeAPI(**manager.driver.get_metadata()[0])) if passthrough: runner = PassthroughRunner() else: runner = manager.driver.get_runner() runner._sandbox = False runner.runner_type_db = runnertype_db runner.action = action_db runner.action_name = action_db.name # runner.liveaction = liveaction_db # runner.liveaction_id = str(liveaction_db.id) # runner.execution = ActionExecution.get(liveaction__id=runner.liveaction_id) # runner.execution_id = str(runner.execution.id) runner.entry_point = content_utils.get_entry_point_abs_path( pack=action_db.pack, entry_point=action_db.entry_point) runner.context = {} # getattr(liveaction_db, 'context', dict()) # runner.callback = getattr(liveaction_db, 'callback', dict()) runner.libs_dir_path = content_utils.get_action_libs_abs_path( pack=action_db.pack, entry_point=action_db.entry_point) # For re-run, get the ActionExecutionDB in which the re-run is based on. # rerun_ref_id = runner.context.get('re-run', {}).get('ref') # runner.rerun_ex_ref = ActionExecution.get(id=rerun_ref_id) if rerun_ref_id else None config_schema = CONFIG_SCHEMAS.get(action_db.pack, None) config_values = os.environ.get('ST2_CONFIG', None) if config_schema and config_values: runner._config = validate_config_against_schema( config_schema=config_schema, config_object=json.loads(config_values), config_path=None, pack_name=action_db.pack) param_values = os.environ.get('ST2_PARAMETERS', None) try: if param_values: live_params = param_utils.render_live_params( runner_parameters=runnertype_db.runner_parameters, action_parameters=action_db.parameters, params=json.loads(param_values), action_context={}, additional_contexts={'input': event}) else: live_params = event if debug and 'log_level' not in live_params: # Set log_level runner parameter live_params['log_level'] = 'DEBUG' runner_params, action_params = param_utils.render_final_params( runner_parameters=runnertype_db.runner_parameters, action_parameters=action_db.parameters, params=live_params, action_context={}) except ParamException as e: raise actionrunner.ActionRunnerException(str(e)) runner.runner_parameters = runner_params LOG.debug('Performing pre-run for runner: %s', runner.runner_id) runner.pre_run() (status, output, context) = runner.run(action_params) output_values = os.environ.get('ST2_OUTPUT', None) if output_values: try: result = param_utils.render_live_params( runner_parameters=runnertype_db.runner_parameters, action_parameters=action_db.parameters, params=json.loads(output_values), action_context={}, additional_contexts={ 'input': event, 'output': output }) except ParamException as e: raise actionrunner.ActionRunnerException(str(e)) else: result = output # Log the logs generated by the action. We do that so the actual action logs # (action stderr) end up in CloudWatch output = output or {} if output.get('stdout', None): LOG.info('Action stdout: %s' % (output['stdout'])) if output.get('stderr', None): LOG.info('Action stderr and logs: %s' % (output['stderr'])) return { 'event': event, 'live_params': live_params, 'output': output, 'result': result }
def _do_run(self, runner, runnertype_db, action_db, liveaction_db): # Create a temporary auth token which will be available # for the duration of the action execution. runner.auth_token = self._create_auth_token( context=runner.context, action_db=action_db, liveaction_db=liveaction_db) try: # Finalized parameters are resolved and then rendered. This process could # fail. Handle the exception and report the error correctly. try: runner_params, action_params = param_utils.render_final_params( runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters, liveaction_db.context) runner.runner_parameters = runner_params except ParamException as e: raise actionrunner.ActionRunnerException(str(e)) LOG.debug('Performing pre-run for runner: %s', runner.runner_id) runner.pre_run() # Mask secret parameters in the log context resolved_action_params = ResolvedActionParameters( action_db=action_db, runner_type_db=runnertype_db, runner_parameters=runner_params, action_parameters=action_params) extra = {'runner': runner, 'parameters': resolved_action_params} LOG.debug('Performing run for runner: %s' % (runner.runner_id), extra=extra) (status, result, context) = runner.run(action_params) try: result = json.loads(result) except: pass action_completed = status in action_constants.LIVEACTION_COMPLETED_STATES if isinstance(runner, AsyncActionRunner) and not action_completed: self._setup_async_query(liveaction_db.id, runnertype_db, context) except: LOG.exception('Failed to run action.') _, ex, tb = sys.exc_info() # mark execution as failed. status = action_constants.LIVEACTION_STATUS_FAILED # include the error message and traceback to try and provide some hints. result = { 'error': str(ex), 'traceback': ''.join(traceback.format_tb(tb, 20)) } context = None finally: # Log action completion extra = {'result': result, 'status': status} LOG.debug('Action "%s" completed.' % (action_db.name), extra=extra) # Update the final status of liveaction and corresponding action execution. liveaction_db = self._update_status(liveaction_db.id, status, result, context) # Always clean-up the auth_token # This method should be called in the finally block to ensure post_run is not impacted. self._clean_up_auth_token(runner=runner, status=status) LOG.debug('Performing post_run for runner: %s', runner.runner_id) runner.post_run(status=status, result=result) LOG.debug('Runner do_run result', extra={'result': liveaction_db.result}) LOG.audit('Liveaction completed', extra={'liveaction_db': liveaction_db}) return liveaction_db
def request(wf_def, ac_ex_db, st2_ctx, notify_cfg=None): wf_ac_ex_id = str(ac_ex_db.id) LOG.info('[%s] Processing action execution request for workflow.', wf_ac_ex_id) # Load workflow definition into workflow spec model. spec_module = specs_loader.get_spec_module('native') wf_spec = spec_module.instantiate(wf_def) # Inspect the workflow spec. inspect(wf_spec, st2_ctx, raise_exception=True) # Identify the action to execute. action_db = action_utils.get_action_by_ref(ref=ac_ex_db.action['ref']) if not action_db: error = 'Unable to find action "%s".' % ac_ex_db.action['ref'] raise ac_exc.InvalidActionReferencedException(error) # Identify the runner for the action. runner_type_db = action_utils.get_runnertype_by_name(action_db.runner_type['name']) # Render action execution parameters. runner_params, action_params = param_utils.render_final_params( runner_type_db.runner_parameters, action_db.parameters, ac_ex_db.parameters, ac_ex_db.context ) # Instantiate the workflow conductor. conductor_params = {'inputs': action_params, 'context': st2_ctx} conductor = conducting.WorkflowConductor(wf_spec, **conductor_params) # Serialize the conductor which initializes some internal values. data = conductor.serialize() # Create a record for workflow execution. wf_ex_db = wf_db_models.WorkflowExecutionDB( action_execution=str(ac_ex_db.id), spec=data['spec'], graph=data['graph'], input=data['input'], context=data['context'], state=data['state'], status=data['state']['status'], output=data['output'], errors=data['errors'] ) # Inspect that the list of tasks in the notify parameter exist in the workflow spec. if runner_params.get('notify'): invalid_tasks = list(set(runner_params.get('notify')) - set(wf_spec.tasks.keys())) if invalid_tasks: raise wf_exc.WorkflowExecutionException( 'The following tasks in the notify parameter do not exist ' 'in the workflow definition: %s.' % ', '.join(invalid_tasks) ) # Write notify instruction to record. if notify_cfg: # Set up the notify instruction in the workflow execution record. wf_ex_db.notify = { 'config': notify_cfg, 'tasks': runner_params.get('notify') } # Insert new record into the database and do not publish to the message bus yet. wf_ex_db = wf_db_access.WorkflowExecution.insert(wf_ex_db, publish=False) LOG.info('[%s] Workflow execution "%s" is created.', wf_ac_ex_id, str(wf_ex_db.id)) # Update the context with the workflow execution id created on database insert. # Publish the workflow execution requested status to the message bus. if wf_ex_db.status not in statuses.COMPLETED_STATUSES: # Set the initial workflow status to requested. conductor.request_workflow_status(statuses.REQUESTED) data = conductor.serialize() wf_ex_db.state = data['state'] wf_ex_db.status = data['state']['status'] # Put the ID of the workflow execution record in the context. wf_ex_db.context['st2']['workflow_execution_id'] = str(wf_ex_db.id) wf_ex_db.state['contexts'][0]['st2']['workflow_execution_id'] = str(wf_ex_db.id) # Update the workflow execution record. wf_ex_db = wf_db_access.WorkflowExecution.update(wf_ex_db, publish=False) wf_db_access.WorkflowExecution.publish_status(wf_ex_db) msg = '[%s] Workflow execution "%s" is published.' LOG.info(msg, wf_ac_ex_id, str(wf_ex_db.id)) else: msg = '[%s] Unable to request workflow execution. It is already in completed status "%s".' LOG.info(msg, wf_ac_ex_id, wf_ex_db.status) return wf_ex_db