From 749c8f2849a3e38ed51b423108761d9eb87e2d65 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sat, 22 Apr 2023 12:40:40 +0000 Subject: [PATCH] Refactor release domain --- doc/architecture/Domain.md | 11 ++++ src/main/python/ddadevops/__init__.py | 2 +- .../application/release_mixin_services.py | 8 +-- src/main/python/ddadevops/domain/__init__.py | 2 +- src/main/python/ddadevops/domain/common.py | 2 +- src/main/python/ddadevops/domain/release.py | 63 ++++++++++++------- .../infrastructure/infrastructure.py | 6 +- .../infrastructure/release_mixin/__init__.py | 2 +- .../infrastructure/release_mixin/repo.py | 13 ++-- src/main/python/ddadevops/release_mixin.py | 41 +++--------- src/test/python/domain/test_domain.py | 53 +++++++++++++++- src/test/python/release_mixin/mock_domain.py | 8 ++- src/test/python/release_mixin/test_domain.py | 43 ------------- .../release_mixin/test_infrastructure.py | 5 +- .../release_mixin/test_release_mixin.py | 6 +- 15 files changed, 143 insertions(+), 122 deletions(-) delete mode 100644 src/test/python/release_mixin/test_domain.py diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md index 885362c..dd5687f 100644 --- a/doc/architecture/Domain.md +++ b/doc/architecture/Domain.md @@ -32,8 +32,19 @@ classDiagram ipv6 } + class Release { + main_branch + config_file + } + class ReleaseContext { + release_type + version + current_branch + } + C4k *-- DnsRecord Image *-- Devops + Release *-- "0..1" ReleaseContext ``` diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index 5404bac..fc4363f 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -20,6 +20,6 @@ from .devops_build import DevopsBuild, create_devops_build_config, get_devops_bu from .credential import gopass_password_from_path, gopass_field_from_path from .release_mixin import ReleaseMixin -from .domain import Validateable, DnsRecord, Devops, Image +from .domain import Validateable, DnsRecord, Devops, Image, Release, ReleaseContext __version__ = "${version}" diff --git a/src/main/python/ddadevops/application/release_mixin_services.py b/src/main/python/ddadevops/application/release_mixin_services.py index 3a4dbe2..913478a 100644 --- a/src/main/python/ddadevops/application/release_mixin_services.py +++ b/src/main/python/ddadevops/application/release_mixin_services.py @@ -1,16 +1,16 @@ -from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseRepository, GitApi +from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseContextRepository, GitApi from src.main.python.ddadevops.domain import Version class PrepareReleaseService(): - def __init__(self, release_repo: ReleaseRepository): + def __init__(self, release_repo: ReleaseContextRepository): self.release_repo = release_repo self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func self.git_api = GitApi() def __write_and_commit_version(self, version: Version, commit_message: str): - self.release.is_valid(self.release_repo.main_branch) + self.release.is_valid() self.release_repo.version_repository.write_file(version.get_version_string()) self.git_api.add_file(self.release_repo.version_repository.file) @@ -24,7 +24,7 @@ class PrepareReleaseService(): class TagAndPushReleaseService(): - def __init__(self, git_api: GitApi, release_repo: ReleaseRepository): + def __init__(self, git_api: GitApi, release_repo: ReleaseContextRepository): self.git_api = git_api self.release_repo = release_repo self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func diff --git a/src/main/python/ddadevops/domain/__init__.py b/src/main/python/ddadevops/domain/__init__.py index c584453..2e4f6b1 100644 --- a/src/main/python/ddadevops/domain/__init__.py +++ b/src/main/python/ddadevops/domain/__init__.py @@ -1,4 +1,4 @@ from .common import Validateable, DnsRecord, Devops from .image import Image from .c4k import C4k -from .release import Release, ReleaseConfig, ReleaseType, Version, EnvironmentKeys +from .release import Release, ReleaseContext, ReleaseType, Version, EnvironmentKeys diff --git a/src/main/python/ddadevops/domain/common.py b/src/main/python/ddadevops/domain/common.py index be43a01..b077512 100644 --- a/src/main/python/ddadevops/domain/common.py +++ b/src/main/python/ddadevops/domain/common.py @@ -9,7 +9,7 @@ class Validateable: def __validate_is_not_empty__(self, field_name: str) -> List[str]: value = self.__dict__[field_name] if value is None or value == "": - return [f"Field '{field_name}' may not be empty."] + return [f"Field '{field_name}' must not be empty."] else: return [] diff --git a/src/main/python/ddadevops/domain/release.py b/src/main/python/ddadevops/domain/release.py index 883bb20..c340154 100644 --- a/src/main/python/ddadevops/domain/release.py +++ b/src/main/python/ddadevops/domain/release.py @@ -63,39 +63,60 @@ class Version(): bump_version.increment(ReleaseType.BUMP) return bump_version -class ReleaseConfig(Validateable): +class ReleaseContext(Validateable): + def __init__(self, release_type: ReleaseType | None, version: Version, current_branch: str): + self.release_type = release_type + self.version = version + self.current_branch = current_branch + + def release_version(self) -> Version: + return self.version.create_release_version(self.release_type) + + def bump_version(self) -> Version: + return self.release_version().create_bump_version() + + def validate(self): + result = [] + result += self.__validate_is_not_empty__("release_type") + result += self.__validate_is_not_empty__("version") + result += self.__validate_is_not_empty__("current_branch") + return result + + def validate_branch(self, main_branch: str): + result = [] + if self.release_type is not None and main_branch != self.current_branch: + result.append(f"Releases are allowed only on {main_branch}") + return result + +class Release(Validateable): def __init__( self, + devops: Devops, main_branch: str, config_file: str, - devops: Devops, - release_version: Optional[str | None] = None, - bump_version: Optional[str | None] = None ): - self.main_branch = main_branch - self.config_file = config_file - # TODO: this information may be transient? - self.release_version = release_version - self.bump_version = bump_version - self.devops = devops - -class Release(Validateable): - def __init__(self, release_type: ReleaseType | None, version: Version, current_branch: str, main_branch: str): - self.release_type = release_type - self.version = version - self.current_branch = current_branch self.main_branch = main_branch + self.config_file = config_file + self.release_context = None + def set_release_context(self, set_release_context: ReleaseContext) -> None: + self.release_context = set_release_context + def release_version(self): - return self.version.create_release_version(self.release_type) + return self.release_context.release_version() def bump_version(self): - return self.release_version().create_bump_version() + return self.release_context.bump_version() + def validate(self): result = [] - if self.release_type is not None and self.main_branch != self.current_branch: - result.append(f"Releases are allowed only on {main_branch}") + result += self.__validate_is_not_empty__("main_branch") + result += self.__validate_is_not_empty__("config_file") + result += self.__validate_is_not_empty__("release_context") + if self.release_context is not None: + result += self.release_context.validate() + result += self.release_context.validate_branch(self.main_branch) return result - + \ No newline at end of file diff --git a/src/main/python/ddadevops/infrastructure/infrastructure.py b/src/main/python/ddadevops/infrastructure/infrastructure.py index c8bc41e..15e1e32 100644 --- a/src/main/python/ddadevops/infrastructure/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure/infrastructure.py @@ -4,7 +4,7 @@ from os import chmod from subprocess import run from pkg_resources import resource_string import yaml -from ..domain import Devops, Image, C4k, ReleaseConfig +from ..domain import Devops, Image, C4k, Release from ..python_util import execute @@ -30,10 +30,10 @@ class ProjectRepository: def set_c4k(self, project, build: C4k): project.set_property("c4k_build", build) - def get_release(self, project) -> ReleaseConfig: + def get_release(self, project) -> Release: return project.get_property("release_build") - def set_release(self, project, build: ReleaseConfig): + def set_release(self, project, build: Release): project.set_property("release_build", build) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py b/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py index ec16e32..7c50c26 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py @@ -1,2 +1,2 @@ from .infrastructure_api import FileHandler, EnvironmentApi, GitApi, JsonFileHandler, GradleFileHandler, PythonFileHandler, ClojureFileHandler -from .repo import VersionRepository, ReleaseRepository, ReleaseTypeRepository +from .repo import VersionRepository, ReleaseContextRepository, ReleaseTypeRepository diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py index c28c4cd..efbc0f6 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py @@ -1,6 +1,6 @@ from typing import Optional from src.main.python.ddadevops.domain import ( - Release, + ReleaseContext, Version, ReleaseType, EnvironmentKeys, @@ -106,7 +106,8 @@ class ReleaseTypeRepository: raise ValueError("No valid api passed to ReleaseTypeRepository") -class ReleaseRepository: +# TODO: Repo has state & repository should exist only for AggregateRoot +class ReleaseContextRepository: def __init__( self, version_repository: VersionRepository, @@ -117,9 +118,13 @@ class ReleaseRepository: self.release_type_repository = release_type_repository self.main_branch = main_branch - def get_release(self) -> Release: - return Release( + def get_release(self) -> ReleaseContext: + result = ReleaseContext( self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch, ) + if not result.is_valid(): + issues = '\n'.join.result.validate() + raise ValueError(f"invalid release: {issues}") + return result diff --git a/src/main/python/ddadevops/release_mixin.py b/src/main/python/ddadevops/release_mixin.py index 62e7249..f10f847 100644 --- a/src/main/python/ddadevops/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin.py @@ -1,41 +1,14 @@ from typing import Optional from pybuilder.core import Project from src.main.python.ddadevops.devops_build import DevopsBuild -from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseRepository, ReleaseTypeRepository, VersionRepository, GitApi, EnvironmentApi +from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseContextRepository, ReleaseTypeRepository, VersionRepository, GitApi, EnvironmentApi from src.main.python.ddadevops.application import PrepareReleaseService, TagAndPushReleaseService -from src.main.python.ddadevops.domain import ReleaseConfig, EnvironmentKeys +from src.main.python.ddadevops.domain import Release, EnvironmentKeys -# TODO: remove the config creation -def create_release_mixin_config(config_file, main_branch) -> dict: - config = {} - config.update( - {'ReleaseMixin': - {'main_branch': main_branch, - 'config_file': config_file}}) - return config -# TODO: remove the config creation -def add_versions(config, release_version, bump_version) -> dict: - config['ReleaseMixin'].update( - {'release_version': release_version, - 'bump_version': bump_version}) - return config - -# TODO: remove the config argument class ReleaseMixin(DevopsBuild): - def __init__(self, project: Project, release_config: Optional[ReleaseConfig] = None): - if not release_config: - if not config: - raise ValueError("Release parameters could not be set.") - super().__init__(project, config=config) - release_mixin_config = config['ReleaseMixin'] - release_config = ReleaseConfig( - main_branch = release_mixin_config['main_branch'], - config_file = release_mixin_config['config_file'], - devops=self.repo.get_devops(project) - ) - else: - super().__init__(project, devops=release_config.devops) - self.repo.set_release(self.project, release_config) + def __init__(self, project: Project, release: Release): + super().__init__(project, devops=release.devops) + self.repo.set_release(self.project, release) git_api = GitApi() environment_api = EnvironmentApi() @@ -47,8 +20,8 @@ class ReleaseMixin(DevopsBuild): else: release_type_repo = ReleaseTypeRepository.from_git(git_api) - version_repo = VersionRepository(release_config.config_file) - release_repo = ReleaseRepository(version_repo, release_type_repo, release_config.main_branch) + version_repo = VersionRepository(release.config_file) + release_repo = ReleaseContextRepository(version_repo, release_type_repo, release.main_branch) self.prepare_release_service = PrepareReleaseService(release_repo) self.tag_and_push_release_service = TagAndPushReleaseService(git_api, release_repo) diff --git a/src/test/python/domain/test_domain.py b/src/test/python/domain/test_domain.py index 4295612..b94b492 100644 --- a/src/test/python/domain/test_domain.py +++ b/src/test/python/domain/test_domain.py @@ -1,9 +1,11 @@ from pybuilder.core import Project +from pathlib import Path from src.main.python.ddadevops.domain.common import ( Validateable, DnsRecord, Devops, ) +from src.main.python.ddadevops.domain import Version, ReleaseType, Release, ReleaseContext from src.main.python.ddadevops.domain.image import Image from src.main.python.ddadevops.domain.c4k import C4k from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config @@ -44,7 +46,7 @@ def test_should_validate_non_empty_others(): def test_validate_with_reason(): sut = MockValidateable(None) - assert sut.validate()[0] == "Field 'field' may not be empty." + assert sut.validate()[0] == "Field 'field' must not be empty." def test_should_validate_DnsRecord(): @@ -173,3 +175,52 @@ def test_c4k_build_should_calculate_command(): + "/target/name/module/out_module.yaml" == sut.command(devops) ) + +def test_version(tmp_path: Path): + version = Version(tmp_path, [1, 2, 3]) + version.increment(ReleaseType.SNAPSHOT) + assert version.get_version_string() == "1.2.3-SNAPSHOT" + assert version.version_list == [1, 2, 3] + assert version.is_snapshot + + version = Version(tmp_path, [1, 2, 3]) + version.increment(ReleaseType.BUMP) + assert version.get_version_string() == "1.2.4-SNAPSHOT" + assert version.version_list == [1, 2, 4] + assert version.is_snapshot + + version = Version(tmp_path, [1, 2, 3]) + version.increment(ReleaseType.PATCH) + assert version.get_version_string() == "1.2.4" + assert version.version_list == [1, 2, 4] + assert not version.is_snapshot + + version = Version(tmp_path, [1, 2, 3]) + version.increment(ReleaseType.MINOR) + assert version.get_version_string() == "1.3.0" + assert version.version_list == [1, 3, 0] + assert not version.is_snapshot + + version = Version(tmp_path, [1, 2, 3]) + version.increment(ReleaseType.MAJOR) + assert version.get_version_string() == "2.0.0" + assert version.version_list == [2, 0, 0] + assert not version.is_snapshot + +def test_release_context(tmp_path): + version = Version(tmp_path, [1, 2, 3]) + release = ReleaseContext(ReleaseType.MINOR, version, "main") + + release_version = release.release_version() + assert release_version.get_version_string() in '1.3.0' + + bump_version = release.bump_version() + assert bump_version.get_version_string() in "1.3.1-SNAPSHOT" + +def test_release(tmp_path): + devops = Devops(stage="test", project_root_path="", module="module", name="name") + sut = Release(devops, "main", "config_file.json") + assert not sut.is_valid() + + sut.set_release_context(ReleaseContext(ReleaseType.MINOR, Version("id", [1,2,3]), "main")) + assert sut.is_valid() diff --git a/src/test/python/release_mixin/mock_domain.py b/src/test/python/release_mixin/mock_domain.py index e350a7b..bde9c4e 100644 --- a/src/test/python/release_mixin/mock_domain.py +++ b/src/test/python/release_mixin/mock_domain.py @@ -1,5 +1,7 @@ from src.main.python.ddadevops.domain import ReleaseType +# TODO: Domain never should be needed to mock! Why do you not yust create the needed object? + class MockVersion(): def __init__(self, id = None, version_list = None): @@ -33,9 +35,9 @@ class MockRelease(): def bump_version(self): return self.release_version().create_bump_version() - def validate(self, main_branch): + def validate(self): self.validate_count += 1 return [] - def is_valid(self, main_branch): - return len(self.validate(main_branch)) < 1 + def is_valid(self): + return len(self.validate()) < 1 diff --git a/src/test/python/release_mixin/test_domain.py b/src/test/python/release_mixin/test_domain.py deleted file mode 100644 index 47cffb4..0000000 --- a/src/test/python/release_mixin/test_domain.py +++ /dev/null @@ -1,43 +0,0 @@ -from pathlib import Path -from src.main.python.ddadevops.domain import Version, ReleaseType, Release - -def test_version(tmp_path: Path): - version = Version(tmp_path, [1, 2, 3]) - version.increment(ReleaseType.SNAPSHOT) - assert version.get_version_string() == "1.2.3-SNAPSHOT" - assert version.version_list == [1, 2, 3] - assert version.is_snapshot - - version = Version(tmp_path, [1, 2, 3]) - version.increment(ReleaseType.BUMP) - assert version.get_version_string() == "1.2.4-SNAPSHOT" - assert version.version_list == [1, 2, 4] - assert version.is_snapshot - - version = Version(tmp_path, [1, 2, 3]) - version.increment(ReleaseType.PATCH) - assert version.get_version_string() == "1.2.4" - assert version.version_list == [1, 2, 4] - assert not version.is_snapshot - - version = Version(tmp_path, [1, 2, 3]) - version.increment(ReleaseType.MINOR) - assert version.get_version_string() == "1.3.0" - assert version.version_list == [1, 3, 0] - assert not version.is_snapshot - - version = Version(tmp_path, [1, 2, 3]) - version.increment(ReleaseType.MAJOR) - assert version.get_version_string() == "2.0.0" - assert version.version_list == [2, 0, 0] - assert not version.is_snapshot - -def test_release(tmp_path): - version = Version(tmp_path, [1, 2, 3]) - release = Release(ReleaseType.MINOR, version, "main") - - release_version = release.release_version() - assert release_version.get_version_string() in '1.3.0' - - bump_version = release.bump_version() - assert bump_version.get_version_string() in "1.3.1-SNAPSHOT" diff --git a/src/test/python/release_mixin/test_infrastructure.py b/src/test/python/release_mixin/test_infrastructure.py index b008ef0..5bfe27d 100644 --- a/src/test/python/release_mixin/test_infrastructure.py +++ b/src/test/python/release_mixin/test_infrastructure.py @@ -1,5 +1,5 @@ from src.main.python.ddadevops.domain import ReleaseType -from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseTypeRepository, VersionRepository, ReleaseRepository +from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseTypeRepository, VersionRepository, ReleaseContextRepository from .mock_infrastructure_api import MockGitApi, MockEnvironmentApi from .helper import Helper @@ -22,7 +22,8 @@ def test_release_repository(tmp_path): release_type_repo = ReleaseTypeRepository.from_git(MockGitApi('MINOR test')) # test - sut = ReleaseRepository(version_repo, release_type_repo, 'main') + sut = ReleaseContextRepository(version_repo, release_type_repo, 'main') + release = sut.get_release() assert release is not None diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index d681d4a..78493aa 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -4,7 +4,7 @@ from pybuilder.core import Project from src.main.python.ddadevops.release_mixin import ReleaseMixin from src.main.python.ddadevops.infrastructure.release_mixin import GitApi, EnvironmentApi -from src.main.python.ddadevops.domain import Devops, ReleaseConfig +from src.main.python.ddadevops.domain import Devops, ReleaseContext, Release from .helper import Helper @@ -23,8 +23,8 @@ class MyBuild(ReleaseMixin): def initialize_with_object(project, CONFIG_FILE): project.build_depends_on('ddadevops>=3.1.2') devops = Devops(STAGE, PROJECT_ROOT_PATH, MODULE, "release_test", BUILD_DIR_NAME) - release_config = ReleaseConfig(MAIN_BRANCH, CONFIG_FILE, devops) - build = MyBuild(project, release_config=release_config) + release = Release(devops, MAIN_BRANCH, CONFIG_FILE) + build = MyBuild(project, release) return build def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch):