def test_get_app_config_when_not_found(self, configuration_mock, io_mock): io_mock.exists.return_value = False configuration_mock.get_config_app_folder.return_value = "apps" with self.assertRaises(AppConfigurationNotFoundError) as context: config.get_app_config("some-app", "/some/path") self.assertEqual("Configuration file not found for app: some-app", str(context.exception))
def update_pristine_repository(app_name): """Update the pristine repository of an application""" app_config = config.get_app_config(app_name) repository_dir = io.get_pristine_path(app_name) update_repository(repository_dir, app_config.get("git").get("origin")) Logger.debug( {app_name, repository_dir}, "[git#update_pristine_repository] Updated a pristine repository", ) return repository_dir
def tag(repository_dir, app_name, tag_name): """Add a tag to the repository""" app_config = config.get_app_config(app_name) # Move to the corresponding environment branch branch(repository_dir, app_config.get("workflow")[0]) # Extract the last commit hash: commit_hash = get_last_commit_hash(repository_dir) final_tag = f"{tag_name}-sha-{commit_hash}" if not semver.VersionInfo.isvalid(final_tag): raise RuntimeError(f'Invalid version tag: "{final_tag}".') io.execute(f"git tag -a {final_tag} {commit_hash}", repository_dir) return final_tag
def build(app_name, context): """Build the docker image of the last version of the app Example: > app_name = "my-app" > context = {"repository": "/path_to/my-app-doc", "commit_hash": "a2b3c4"} > build(app_name, context) """ repository = context["repository"] image_tag = git.get_last_tag(repository) Logger.info( {"tag": f"{app_name}@{image_tag}"}, "Application name and version", ) if has_docker_image(app_name, image_tag): Logger.info({}, "Docker image already built (skipped)") return image_tag app_config = config.get_app_config(app_name) commit_hash = context["commit_hash"] build_variables = app_config["build"]["variables"] # Application build environment variables: builds_args = [] for key, value in build_variables.items(): builds_args.append(f"--build-arg {key}={value}") builds_args.append(f"--build-arg COMMIT_HASH={commit_hash}") builds_args_str = " ".join(builds_args) command = f"docker build --tag {app_name}:{image_tag} {builds_args_str} {repository}" Logger.debug({"command": command}, "Docker build command") try: io.execute(command) except Exception as err: Logger.error({"err": err}, "Error while building Docker image") raise err return image_tag
def push(app_name, repository): """Push an image to the configured docker registry""" # This will need to be done a bit differently to work with GCP registry app_config = config.get_app_config(app_name) image_tag = git.get_last_tag(repository) if not has_docker_image(app_name, image_tag): raise RuntimeError("Docker image not available") registry = app_config["docker"]["registry"] # Create the tag image = get_registry_image_tag(app_name, image_tag, registry) io.execute(f"docker tag {app_name}:{image_tag} {image}") io.execute(f"docker push {image}")
def test_get_app_config(mocker): mocker.patch.object(Configuration, "get_config_path", return_value="tests/__fixtures__/config") app_config = config.get_app_config("backoffice") assert app_config == { "domain": "website.com", "sub_domain": "backoffice", "variables": { "ope": { "VARIABLE_OPE_1": "ope_1", "VARIABLE_OPE_2": "ope_2_override", "VARIABLE_OPE_3": "ope_3", }, "app": { "VARIABLE_APP_1": "app_1", "VARIABLE_APP_2": "app_2_override", "VARIABLE_APP_3": "app_3", }, }, }
def _differed_build(app_name: str): config_dir = None app_dir = None try: # Retrieve app's configuration config_dir = config.create_temporary_config_copy() config.change_environment(Configuration.get_config_default_branch(), config_dir) app_config = config.get_app_config(app_name, config_dir) Logger.debug( {"app": app_name, "config_directory": config_dir}, "[/api/builds/:app] Application's configuration retrieved", ) # Retrieve app's repository app_dir = git.create_working_repository(app_name, app_config["git"]["origin"]) git.branch(app_dir, app_config["workflow"][0]) Logger.debug( {"app": app_name, "working_directory": app_dir}, "[/api/builds/:app] Application's repository retrieved", ) try: # Create a new tag version = app.get_version(app_dir) git_tag = git.tag(app_dir, version) Logger.debug( {"app": app_name, "tag": git_tag}, "[/api/builds/:app] New tag created", ) except Exception as err: # The tag may already exist Logger.warn( {"app": app_name, "err": err}, "[/api/builds/:app] Error while tagging the app" ) # Build and publish the new docker image image_tag = docker.build(app_name, app_dir, app_config) Logger.debug( {"app": app_name, "image": image_tag}, "[/api/builds/:app] Docker image created" ) docker.push(app_name, image_tag, app_config) Logger.debug( {"app": app_name, "image": image_tag}, "[/api/builds/:app] Docker image published on registry", ) # Send the new tag to git git.push(app_dir) Logger.debug({"app": app_name}, "[/api/builds/:app] Tag pushed to Git") except Exception as err: Logger.error( {"app": app_name, "err": err}, "[/api/builds/:app] Error while tagging and building the app", ) # Clean up temporary directories try: if config_dir is not None: io.remove(config_dir) if app_dir is not None: io.remove(app_dir) except Exception as err: Logger.error({"app": app_name, "err": err}, "[/api/builds/:app] Error during cleanup")
def test_get_app_config_when_not_found(mocker): mocker.patch.object(io, "exists", return_value=False) with pytest.raises(AppConfigurationNotFoundError): config.get_app_config("app_not_here")
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_get_app_config(self, configuration_mock, get_project_config_mock, read_yaml_mock, io_mock): # Mocks configuration_mock.get_config_app_folder.return_value = "apps" io_mock.exists.return_value = True read_yaml_mock.return_value = { "sub_domain": "backoffice", "variables": { "ope": { "VARIABLE_OPE_2": "ope_2_override", "VARIABLE_OPE_3": "ope_3" }, "app": { "VARIABLE_APP_2": "app_2_override", "VARIABLE_APP_3": "app_3" }, }, } get_project_config_mock.return_value = { "domain": "website.com", "variables": { "ope": { "VARIABLE_OPE_1": "ope_1", "VARIABLE_OPE_2": "ope_2" }, "app": { "VARIABLE_APP_1": "app_1", "VARIABLE_APP_2": "app_2" }, }, } # Test app_config = config.get_app_config("backoffice", "tests/__fixtures__/config") # Assertions io_mock.exists.assert_called_once_with( "tests/__fixtures__/config/apps/backoffice.yaml") read_yaml_mock.assert_called_once_with( "tests/__fixtures__/config/apps/backoffice.yaml") get_project_config_mock.assert_called_once() self.assertEqual( app_config, { "domain": "website.com", "sub_domain": "backoffice", "variables": { "ope": { "VARIABLE_OPE_1": "ope_1", "VARIABLE_OPE_2": "ope_2_override", "VARIABLE_OPE_3": "ope_3", }, "app": { "VARIABLE_APP_1": "app_1", "VARIABLE_APP_2": "app_2_override", "VARIABLE_APP_3": "app_3", }, }, }, )