def __create_preview_info_file(self, gitops_config: GitOpsConfig) -> None: preview_id = self.__args.preview_id yaml_file_dump( { "previewId": preview_id, "previewIdHash": gitops_config.create_preview_id_hash(preview_id), "routeHost": gitops_config.get_preview_host(preview_id), "namespace": gitops_config.get_preview_namespace(preview_id), }, "/tmp/gitopscli-preview-info.yaml", )
def test_replacements(self): config = self.load() self.assertEqual( config.replacements, [ GitOpsConfig.Replacement( path="a.b", variable=GitOpsConfig.Replacement.Variable.ROUTE_HOST), GitOpsConfig.Replacement( path="c.d", variable=GitOpsConfig.Replacement.Variable.GIT_COMMIT), ], )
def setUp(self): self.init_mock_manager(CreatePreviewCommand) self.os_mock = self.monkey_patch(os) self.os_mock.path.isdir.return_value = True self.shutil_mock = self.monkey_patch(shutil) self.shutil_mock.copytree.return_value = None self.logging_mock = self.monkey_patch(logging) self.logging_mock.info.return_value = None self.update_yaml_file_mock = self.monkey_patch(update_yaml_file) self.update_yaml_file_mock.return_value = True self.yaml_file_dump_mock = self.monkey_patch(yaml_file_dump) self.yaml_file_dump_mock.return_value = None self.load_gitops_config_mock = self.monkey_patch(load_gitops_config) self.load_gitops_config_mock.return_value = GitOpsConfig( team_config_org="TEAM_CONFIG_ORG", team_config_repo="TEAM_CONFIG_REPO", application_name="my-app", route_host_template="app.xy-{SHA256_8CHAR_BRANCH_HASH}.example.tld", replacements=[ GitOpsConfig.Replacement( path="image.tag", variable=GitOpsConfig.Replacement.Variable.GIT_COMMIT), GitOpsConfig.Replacement( path="route.host", variable=GitOpsConfig.Replacement.Variable.ROUTE_HOST), ], ) self.git_repo_api_mock = self.create_mock(GitRepoApi) self.git_repo_api_factory_mock = self.monkey_patch(GitRepoApiFactory) self.git_repo_api_factory_mock.create.return_value = self.git_repo_api_mock self.git_repo_mock = self.monkey_patch(GitRepo) self.git_repo_mock.return_value = self.git_repo_mock self.git_repo_mock.__enter__.return_value = self.git_repo_mock self.git_repo_mock.__exit__.return_value = False self.git_repo_mock.get_full_file_path.side_effect = lambda x: f"/tmp/created-tmp-dir/{x}" self.git_repo_mock.clone.return_value = None self.git_repo_mock.commit.return_value = None self.git_repo_mock.push.return_value = None self.seal_mocks()
def __replace_values(self, git_repo: GitRepo, gitops_config: GitOpsConfig) -> bool: preview_id = self.__args.preview_id preview_folder_name = gitops_config.get_preview_namespace( self.__args.preview_id) context = GitOpsConfig.Replacement.PreviewContext( gitops_config, preview_id, self.__args.git_hash) any_value_replaced = False for file, replacements in gitops_config.replacements.items(): for replacement in replacements: replacement_value = replacement.get_value(context) value_replaced = self.__update_yaml_file( git_repo, f"{preview_folder_name}/{file}", replacement.path, replacement_value, ) if value_replaced: any_value_replaced = True logging.info( "Replaced property '%s' in '%s' with value: %s", replacement.path, file, replacement_value) else: logging.info("Keep property '%s' in '%s' value: %s", replacement.path, file, replacement_value) return any_value_replaced
def __create_preview_from_template_if_not_existing( self, template_git_repo: GitRepo, target_git_repo: GitRepo, gitops_config: GitOpsConfig) -> bool: preview_namespace = gitops_config.get_preview_namespace( self.__args.preview_id) full_preview_folder_path = target_git_repo.get_full_file_path( preview_namespace) preview_env_already_exist = os.path.isdir(full_preview_folder_path) if preview_env_already_exist: logging.info("Use existing folder for preview: %s", preview_namespace) return False logging.info("Create new folder for preview: %s", preview_namespace) full_preview_template_folder_path = template_git_repo.get_full_file_path( gitops_config.preview_template_path) if not os.path.isdir(full_preview_template_folder_path): raise GitOpsException( f"The preview template folder does not exist: {gitops_config.preview_template_path}" ) logging.info("Using the preview template folder: %s", gitops_config.preview_template_path) shutil.copytree( full_preview_template_folder_path, full_preview_folder_path, ) return True
def __create_preview_from_template_if_not_existing( self, git_repo: GitRepo, gitops_config: GitOpsConfig) -> bool: preview_namespace = gitops_config.get_preview_namespace( self.__args.preview_id) full_preview_folder_path = git_repo.get_full_file_path( preview_namespace) preview_env_already_exist = os.path.isdir(full_preview_folder_path) if preview_env_already_exist: logging.info("Use existing folder for preview: %s", preview_namespace) return False logging.info("Create new folder for preview: %s", preview_namespace) preview_template_folder_name = f".preview-templates/{gitops_config.application_name}" full_preview_template_folder_path = git_repo.get_full_file_path( preview_template_folder_name) if not os.path.isdir(full_preview_template_folder_path): raise GitOpsException( f"The preview template folder does not exist: {preview_template_folder_name}" ) logging.info("Using the preview template folder: %s", preview_template_folder_name) shutil.copytree( full_preview_template_folder_path, full_preview_folder_path, ) self.__update_yaml_file(git_repo, f"{preview_namespace}/Chart.yaml", "name", preview_namespace) return True
def load_gitops_config(git_api_config: GitApiConfig, organisation: str, repository_name: str) -> GitOpsConfig: git_repo_api = GitRepoApiFactory.create(git_api_config, organisation, repository_name) with GitRepo(git_repo_api) as git_repo: git_repo.clone() gitops_config_file_path = git_repo.get_full_file_path( ".gitops.config.yaml") try: gitops_config_yaml = yaml_file_load(gitops_config_file_path) except FileNotFoundError as ex: raise GitOpsException("No such file: .gitops.config.yaml") from ex return GitOpsConfig.from_yaml(gitops_config_yaml)
def __get_value_for_variable( self, gitops_config: GitOpsConfig, variable: GitOpsConfig.Replacement.Variable) -> str: mapping: Dict[GitOpsConfig.Replacement.Variable, Callable[[], str]] = { GitOpsConfig.Replacement.Variable.ROUTE_HOST: lambda: gitops_config.get_route_host(self.__args.preview_id), GitOpsConfig.Replacement.Variable.GIT_COMMIT: lambda: self.__args.git_hash, } assert set( mapping.keys()) == set(GitOpsConfig.Replacement.Variable ), "variable to value mapping not complete" return mapping[variable]()
def setUp(self): self.init_mock_manager(DeletePreviewCommand) self.os_mock = self.monkey_patch(os) self.os_mock.path.exists.return_value = True self.shutil_mock = self.monkey_patch(shutil) self.shutil_mock.rmtree.return_value = None self.logging_mock = self.monkey_patch(logging) self.logging_mock.info.return_value = None self.load_gitops_config_mock = self.monkey_patch(load_gitops_config) self.load_gitops_config_mock.return_value = GitOpsConfig( api_version=0, application_name="APP", preview_host_template="www.foo.bar", preview_template_organisation="PREVIEW_TEMPLATE_ORG", preview_template_repository="PREVIEW_TEMPLATE_REPO", preview_template_path_template=".preview-templates/my-app", preview_template_branch="template-branch", preview_target_organisation="PREVIEW_TARGET_ORG", preview_target_repository="PREVIEW_TARGET_REPO", preview_target_branch="target-branch", preview_target_namespace_template="APP-${PREVIEW_ID_HASH}-preview", preview_target_max_namespace_length=50, replacements={}, ) self.git_repo_api_mock = self.create_mock(GitRepoApi) self.git_repo_api_mock.create_pull_request.return_value = GitRepoApi.PullRequestIdAndUrl( 42, "<url of dummy pr>") self.git_repo_api_factory_mock = self.monkey_patch(GitRepoApiFactory) self.git_repo_api_factory_mock.create.return_value = self.git_repo_api_mock self.git_repo_mock = self.monkey_patch(GitRepo) self.git_repo_mock.return_value = self.git_repo_mock self.git_repo_mock.__enter__.return_value = self.git_repo_mock self.git_repo_mock.__exit__.return_value = False self.git_repo_mock.get_full_file_path.side_effect = lambda x: f"/tmp/created-tmp-dir/{x}" self.git_repo_mock.clone.return_value = None self.git_repo_mock.commit.return_value = None self.git_repo_mock.push.return_value = None self.seal_mocks()
def __replace_values(self, git_repo: GitRepo, gitops_config: GitOpsConfig) -> bool: preview_folder_name = gitops_config.get_preview_namespace( self.__args.preview_id) any_value_replaced = False for replacement in gitops_config.replacements: replacement_value = self.__get_value_for_variable( gitops_config, replacement.variable) value_replaced = self.__update_yaml_file( git_repo, f"{preview_folder_name}/values.yaml", replacement.path, replacement_value, ) if value_replaced: any_value_replaced = True logging.info("Replaced property '%s' with value: %s", replacement.path, replacement_value) else: logging.info("Keep property '%s' value: %s", replacement.path, replacement_value) return any_value_replaced
def setUp(self): self.init_mock_manager(DeletePreviewCommand) self.os_mock = self.monkey_patch(os) self.os_mock.path.exists.return_value = True self.shutil_mock = self.monkey_patch(shutil) self.shutil_mock.rmtree.return_value = None self.logging_mock = self.monkey_patch(logging) self.logging_mock.info.return_value = None self.load_gitops_config_mock = self.monkey_patch(load_gitops_config) self.load_gitops_config_mock.return_value = GitOpsConfig( application_name="APP", team_config_org="TEAM_CONFIG_ORG", team_config_repo="TEAM_CONFIG_REPO", route_host_template="www.foo.bar", replacements=[], ) self.git_repo_api_mock = self.create_mock(GitRepoApi) self.git_repo_api_mock.create_pull_request.return_value = GitRepoApi.PullRequestIdAndUrl( 42, "<url of dummy pr>") self.git_repo_api_factory_mock = self.monkey_patch(GitRepoApiFactory) self.git_repo_api_factory_mock.create.return_value = self.git_repo_api_mock self.git_repo_mock = self.monkey_patch(GitRepo) self.git_repo_mock.return_value = self.git_repo_mock self.git_repo_mock.__enter__.return_value = self.git_repo_mock self.git_repo_mock.__exit__.return_value = False self.git_repo_mock.get_full_file_path.side_effect = lambda x: f"/tmp/created-tmp-dir/{x}" self.git_repo_mock.clone.return_value = None self.git_repo_mock.commit.return_value = None self.git_repo_mock.push.return_value = None self.seal_mocks()
def load(self) -> GitOpsConfig: return GitOpsConfig.from_yaml(self.yaml)
def setUp(self): self.init_mock_manager(CreatePreviewCommand) self.os_mock = self.monkey_patch(os) self.os_mock.path.isdir.return_value = True self.shutil_mock = self.monkey_patch(shutil) self.shutil_mock.copytree.return_value = None self.logging_mock = self.monkey_patch(logging) self.logging_mock.info.return_value = None self.update_yaml_file_mock = self.monkey_patch(update_yaml_file) self.update_yaml_file_mock.return_value = True self.yaml_file_dump_mock = self.monkey_patch(yaml_file_dump) self.yaml_file_dump_mock.return_value = None self.load_gitops_config_mock = self.monkey_patch(load_gitops_config) self.load_gitops_config_mock.return_value = GitOpsConfig( api_version=2, application_name="my-app", preview_host_template="app.xy-${PREVIEW_ID_HASH}.example.tld", preview_template_organisation="PREVIEW_TEMPLATE_ORG", preview_template_repository="PREVIEW_TEMPLATE_REPO", preview_template_path_template=".preview-templates/my-app", preview_template_branch="template-branch", preview_target_organisation="PREVIEW_TARGET_ORG", preview_target_repository="PREVIEW_TARGET_REPO", preview_target_branch=None, preview_target_namespace_template= "my-app-${PREVIEW_ID_HASH}-preview", preview_target_max_namespace_length=50, replacements={ "Chart.yaml": [ GitOpsConfig.Replacement( path="name", value_template="${PREVIEW_NAMESPACE}"), ], "values.yaml": [ GitOpsConfig.Replacement(path="image.tag", value_template="${GIT_HASH}"), GitOpsConfig.Replacement(path="route.host", value_template="${PREVIEW_HOST}"), ], }, ) self.template_git_repo_api_mock = self.create_mock(GitRepoApi) self.target_git_repo_api_mock = self.create_mock(GitRepoApi) self.git_repo_api_factory_mock = self.monkey_patch(GitRepoApiFactory) def git_repo_api_factory_create_mock( _: GitApiConfig, organisation: str, repository_name: str) -> GitRepoApi: if "TEMPLATE" in organisation and "TEMPLATE" in repository_name: return self.template_git_repo_api_mock if "TARGET" in organisation and "TARGET" in repository_name: return self.target_git_repo_api_mock raise Exception(f"no mock for {organisation}/{repository_name}") self.git_repo_api_factory_mock.create.side_effect = git_repo_api_factory_create_mock self.template_git_repo_mock = self.create_mock(GitRepo) self.template_git_repo_mock.__enter__.return_value = self.template_git_repo_mock self.template_git_repo_mock.__exit__.return_value = False self.template_git_repo_mock.get_full_file_path.side_effect = lambda x: f"/tmp/template-repo/{x}" self.template_git_repo_mock.clone.return_value = None self.template_git_repo_mock.commit.return_value = None self.template_git_repo_mock.push.return_value = None self.target_git_repo_mock = self.create_mock(GitRepo) self.target_git_repo_mock.__enter__.return_value = self.target_git_repo_mock self.target_git_repo_mock.__exit__.return_value = False self.target_git_repo_mock.get_full_file_path.side_effect = lambda x: f"/tmp/target-repo/{x}" self.target_git_repo_mock.clone.return_value = None def git_repo_constructor_mock(git_repo_api: GitRepoApi) -> GitRepo: if git_repo_api == self.template_git_repo_api_mock: return self.template_git_repo_mock if git_repo_api == self.target_git_repo_api_mock: return self.target_git_repo_mock raise Exception(f"no mock for {git_repo_api}") self.monkey_patch(GitRepo).side_effect = git_repo_constructor_mock self.seal_mocks()
def test_create_new_preview_from_same_template_target_repo(self): gitops_config: GitOpsConfig = self.load_gitops_config_mock.return_value self.load_gitops_config_mock.return_value = GitOpsConfig( api_version=gitops_config.api_version, application_name=gitops_config.application_name, preview_host_template=gitops_config.preview_host_template, preview_template_organisation=gitops_config. preview_target_organisation, # template = target preview_template_repository=gitops_config. preview_target_repository, # template = target preview_template_path_template=gitops_config. preview_template_path_template, preview_template_branch=gitops_config. preview_target_branch, # template = target preview_target_organisation=gitops_config. preview_target_organisation, preview_target_repository=gitops_config.preview_target_repository, preview_target_branch=gitops_config.preview_target_branch, preview_target_namespace_template=gitops_config. preview_target_namespace_template, preview_target_max_namespace_length=gitops_config. preview_target_max_namespace_length, replacements=gitops_config.replacements, ) self.os_mock.path.isdir.side_effect = lambda path: { "/tmp/target-repo/my-app-685912d3-preview": False, # doesn't exist yet -> expect create "/tmp/target-repo/.preview-templates/my-app": True, }[path] deployment_created_callback = Mock(return_value=None) command = CreatePreviewCommand(ARGS) command.register_callbacks( deployment_already_up_to_date_callback=lambda route_host: self. fail("should not be called"), deployment_updated_callback=lambda route_host: self.fail( "should not be called"), deployment_created_callback=deployment_created_callback, ) command.execute() deployment_created_callback.assert_called_once_with( "app.xy-685912d3.example.tld") assert self.mock_manager.method_calls == [ call.load_gitops_config( ARGS, "ORGA", "REPO", ), call.yaml_file_dump( { "previewId": "PREVIEW_ID", "previewIdHash": "685912d3", "routeHost": "app.xy-685912d3.example.tld", "namespace": "my-app-685912d3-preview", }, "/tmp/gitopscli-preview-info.yaml", ), call.GitRepoApiFactory.create( ARGS, "PREVIEW_TARGET_ORG", "PREVIEW_TARGET_REPO", ), # only clone once for template and target call.GitRepo(self.target_git_repo_api_mock), call.GitRepo.clone(None), call.GitRepo.get_full_file_path("my-app-685912d3-preview"), call.os.path.isdir("/tmp/target-repo/my-app-685912d3-preview"), call.logging.info("Create new folder for preview: %s", "my-app-685912d3-preview"), call.GitRepo.get_full_file_path(".preview-templates/my-app"), call.os.path.isdir("/tmp/target-repo/.preview-templates/my-app"), call.logging.info("Using the preview template folder: %s", ".preview-templates/my-app"), call.shutil.copytree("/tmp/target-repo/.preview-templates/my-app", "/tmp/target-repo/my-app-685912d3-preview"), call.GitRepo.get_full_file_path( "my-app-685912d3-preview/Chart.yaml"), call.update_yaml_file( "/tmp/target-repo/my-app-685912d3-preview/Chart.yaml", "name", "my-app-685912d3-preview"), call.logging.info("Replaced property '%s' in '%s' with value: %s", "name", "Chart.yaml", "my-app-685912d3-preview"), call.GitRepo.get_full_file_path( "my-app-685912d3-preview/values.yaml"), call.update_yaml_file( "/tmp/target-repo/my-app-685912d3-preview/values.yaml", "image.tag", "3361723dbd91fcfae7b5b8b8b7d462fbc14187a9", ), call.logging.info( "Replaced property '%s' in '%s' with value: %s", "image.tag", "values.yaml", "3361723dbd91fcfae7b5b8b8b7d462fbc14187a9", ), call.GitRepo.get_full_file_path( "my-app-685912d3-preview/values.yaml"), call.update_yaml_file( "/tmp/target-repo/my-app-685912d3-preview/values.yaml", "route.host", "app.xy-685912d3.example.tld"), call.logging.info( "Replaced property '%s' in '%s' with value: %s", "route.host", "values.yaml", "app.xy-685912d3.example.tld", ), call.GitRepo.commit( "GIT_USER", "GIT_EMAIL", "Create new preview environment for 'my-app' and git hash '3361723dbd91fcfae7b5b8b8b7d462fbc14187a9'.", ), call.GitRepo.push(), ]