def advance_workflow(current_step): """Advance the workflow for all applications able to be advanced.""" Logger.info( {"current_step": current_step}, "[/api/workflow/progress/<current_step>] Workflow advance started", ) try: # Creating a copy of the working configuration directory config_dir = config_lib.create_temporary_config_copy() config_lib.change_environment( Configuration.get_config_default_branch(), config_dir) project_config = config_lib.get_project_config(config_dir) report_status, report = workflow_lib.advance_workflow( config_dir, project_config, current_step) status, message = _get_status_and_message(report_status) # Clean up non_blocking_clean( config_dir, message_prefix="[/api/workflow/progress/<current_step>]") # HTTP Response Logger.info( { "current_step": current_step, "report": report }, f"[/api/workflow/progress/<current_step>] {message}", ) return ( { "current_step": current_step, "report": report, "message": message }, status, ) # pylint: disable=broad-except except Exception as err: Logger.error( { "current_step": current_step, "err": str(err) }, "[/api/workflow/progress/<current_step>] Workflow advance failed", ) # HTTP Response return ( { "current_step": current_step, "err": str(err), "message": "Workflow advance failed", }, HTTPStatus.INTERNAL_SERVER_ERROR, )
def test_non_blocking_clean_with_error_and_message_prefix( self, io_mock, logger_mock): """Should append message prefix to log.""" io_mock.remove.side_effect = Exception("some error during removal") non_blocking_clean("some/path", message_prefix="[prefix]") logger_mock.warn.assert_called_with( ANY, "[prefix] Error trying to clean temporary directory / file")
def test_non_blocking_clean_with_error(self, io_mock, _logger_mock): """Should not raise an error.""" io_mock.remove.side_effect = Exception("some error during removal") try: non_blocking_clean("some/path") # pylint: disable=broad-except except Exception: self.fail( "_non_blocking_clean should not raise an error when cleaning fails" )
def advance_workflow( config_dir: str, project_config: Dict, current_step: str ) -> Tuple[WorkflowAdvanceStatus, List[AdvanceWorkflowAppReport]]: """Advance the application workflow to the next step""" progress_report: List[AdvanceWorkflowAppReport] = [] next_step = get_next_step(project_config, current_step) if next_step is None: raise WorkflowError("Workflow is already in final step.") status = WorkflowAdvanceStatus.SUCCESS # List applications try: apps = config.list_apps_config(config_dir) except Exception as err: raise AppListingError(err) for (app_name, app_config) in apps.items(): should_app_progress = False tag = None app_dir = None try: # Determine if app is ready to progress or not app_dir = git.create_working_repository( app_name, app_config["git"]["origin"]) should_app_progress, tag = get_app_progress_report( app_dir, current_step, next_step) # If app is ready to progress, make it advance to the next step in the workflow Logger.info( { "app": app_name, "tag": tag, "current_step": current_step, "next_step": next_step, }, "Advancing to the next workflow step" if should_app_progress else "App is already up-to-date. Skipping.", ) if should_app_progress: git.branch(app_dir, next_step) git.rebase(app_dir, current_step, onto=tag) git.push(app_dir) processes = config.get_processes(app_config) cron_jobs = config.get_cronjobs(app_config) progress_report.append({ "name": app_name, "tag": tag, "step": next_step, "processes": processes, "cron_jobs": cron_jobs, }) # pylint: disable=broad-except except Exception as err: Logger.error( { "app_name": app_name, "config_dir": config_dir, "should_app_progress": should_app_progress, "tag": tag, "current_step": current_step, "err": str(err), }, "Error while advancing the workflow", ) status = WorkflowAdvanceStatus.FAIL if app_dir is not None: non_blocking_clean(app_dir) return status, progress_report
def init_workflow( organization: str, app_name: str, git_provider: AbstractGitProvider ) -> Tuple[WorkflowInitStatus, Report]: """Initialize the workflow of an application's repository by creating all workflow branches. This function is idempotent which means it will not try to recreate a branch that already exists. However if a branch already exists but is not protected, it will be set to protected.""" # Create temporary copy for avoiding concurrency problems config_path = config.create_temporary_config_copy() # Switch to staging environment in order to get application configuration config.change_environment("staging", config_path) # Get application configuration to get the list of workflow branches app_config = config.get_app_config(app_name, config_path) master_tag = GitConfiguration.get_master_tag() workflow_branches = _get_workflow_branches(app_config, master_tag) branches = {} status = WorkflowInitStatus.SUCCESS if len(workflow_branches) != 0: status = WorkflowInitStatus.FAIL try: # Get user_login linked to the GITHUB_TOKEN user_info = git_provider.get_user_info() user_login = user_info.login if user_info else None Logger.info({"user_login": user_login}, "User login retrieved") # Get the last commit's sha on master branch branch = git_provider.get_branch(organization, app_name, master_tag) master_head_sha = branch and branch.commit and branch.commit.sha Logger.info({"sha": master_head_sha}, "master last commit sha retrieved") # Sync all workflow branches with master's head and # protect them by limiting push rights to user_login for branch_name in workflow_branches: branches[branch_name] = _create_and_protect_branch( organization, app_name, branch_name, master_head_sha, user_login, git_provider) except GitResourceNotFoundError as err: Logger.error( { "master_branch_name": master_tag, "err": err }, "master last commit sha failed to be retrieved.", ) except GitProviderError as err: Logger.error( { "organization": organization, "app_name": app_name, "err": err }, "Fail to initialize workflow", ) else: status = WorkflowInitStatus.SUCCESS # Clean temporary copy non_blocking_clean(config_path) return status, branches
def test_non_blocking_clean(self, io_mock, _logger_mock): """Should clean the folder/file provided.""" non_blocking_clean("some/path") io_mock.remove.assert_called_with("some/path")