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))
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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}")
Beispiel #6
0
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",
            },
        },
    }
Beispiel #7
0
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")
Beispiel #8
0
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")
Beispiel #9
0
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",
                    },
                },
            },
        )