Refactor release domain

merge-requests/9/head
Michael Jerger 1 year ago
parent 92081947c5
commit 749c8f2849

@ -32,8 +32,19 @@ classDiagram
ipv6 ipv6
} }
class Release {
main_branch
config_file
}
class ReleaseContext {
release_type
version
current_branch
}
C4k *-- DnsRecord C4k *-- DnsRecord
Image *-- Devops Image *-- Devops
Release *-- "0..1" ReleaseContext
``` ```

@ -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 .credential import gopass_password_from_path, gopass_field_from_path
from .release_mixin import ReleaseMixin from .release_mixin import ReleaseMixin
from .domain import Validateable, DnsRecord, Devops, Image from .domain import Validateable, DnsRecord, Devops, Image, Release, ReleaseContext
__version__ = "${version}" __version__ = "${version}"

@ -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 from src.main.python.ddadevops.domain import Version
class PrepareReleaseService(): class PrepareReleaseService():
def __init__(self, release_repo: ReleaseRepository): def __init__(self, release_repo: ReleaseContextRepository):
self.release_repo = release_repo self.release_repo = release_repo
self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func
self.git_api = GitApi() self.git_api = GitApi()
def __write_and_commit_version(self, version: Version, commit_message: str): 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.release_repo.version_repository.write_file(version.get_version_string())
self.git_api.add_file(self.release_repo.version_repository.file) self.git_api.add_file(self.release_repo.version_repository.file)
@ -24,7 +24,7 @@ class PrepareReleaseService():
class TagAndPushReleaseService(): 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.git_api = git_api
self.release_repo = release_repo self.release_repo = release_repo
self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func

@ -1,4 +1,4 @@
from .common import Validateable, DnsRecord, Devops from .common import Validateable, DnsRecord, Devops
from .image import Image from .image import Image
from .c4k import C4k from .c4k import C4k
from .release import Release, ReleaseConfig, ReleaseType, Version, EnvironmentKeys from .release import Release, ReleaseContext, ReleaseType, Version, EnvironmentKeys

@ -9,7 +9,7 @@ class Validateable:
def __validate_is_not_empty__(self, field_name: str) -> List[str]: def __validate_is_not_empty__(self, field_name: str) -> List[str]:
value = self.__dict__[field_name] value = self.__dict__[field_name]
if value is None or value == "": 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: else:
return [] return []

@ -63,39 +63,60 @@ class Version():
bump_version.increment(ReleaseType.BUMP) bump_version.increment(ReleaseType.BUMP)
return bump_version 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__( def __init__(
self, self,
devops: Devops,
main_branch: str, main_branch: str,
config_file: 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 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.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): def release_version(self):
return self.version.create_release_version(self.release_type) return self.release_context.release_version()
def bump_version(self): def bump_version(self):
return self.release_version().create_bump_version() return self.release_context.bump_version()
def validate(self): def validate(self):
result = [] result = []
if self.release_type is not None and self.main_branch != self.current_branch: result += self.__validate_is_not_empty__("main_branch")
result.append(f"Releases are allowed only on {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 return result

@ -4,7 +4,7 @@ from os import chmod
from subprocess import run from subprocess import run
from pkg_resources import resource_string from pkg_resources import resource_string
import yaml import yaml
from ..domain import Devops, Image, C4k, ReleaseConfig from ..domain import Devops, Image, C4k, Release
from ..python_util import execute from ..python_util import execute
@ -30,10 +30,10 @@ class ProjectRepository:
def set_c4k(self, project, build: C4k): def set_c4k(self, project, build: C4k):
project.set_property("c4k_build", build) project.set_property("c4k_build", build)
def get_release(self, project) -> ReleaseConfig: def get_release(self, project) -> Release:
return project.get_property("release_build") 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) project.set_property("release_build", build)

@ -1,2 +1,2 @@
from .infrastructure_api import FileHandler, EnvironmentApi, GitApi, JsonFileHandler, GradleFileHandler, PythonFileHandler, ClojureFileHandler from .infrastructure_api import FileHandler, EnvironmentApi, GitApi, JsonFileHandler, GradleFileHandler, PythonFileHandler, ClojureFileHandler
from .repo import VersionRepository, ReleaseRepository, ReleaseTypeRepository from .repo import VersionRepository, ReleaseContextRepository, ReleaseTypeRepository

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from src.main.python.ddadevops.domain import ( from src.main.python.ddadevops.domain import (
Release, ReleaseContext,
Version, Version,
ReleaseType, ReleaseType,
EnvironmentKeys, EnvironmentKeys,
@ -106,7 +106,8 @@ class ReleaseTypeRepository:
raise ValueError("No valid api passed to 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__( def __init__(
self, self,
version_repository: VersionRepository, version_repository: VersionRepository,
@ -117,9 +118,13 @@ class ReleaseRepository:
self.release_type_repository = release_type_repository self.release_type_repository = release_type_repository
self.main_branch = main_branch self.main_branch = main_branch
def get_release(self) -> Release: def get_release(self) -> ReleaseContext:
return Release( result = ReleaseContext(
self.release_type_repository.get_release_type(), self.release_type_repository.get_release_type(),
self.version_repository.get_version(), self.version_repository.get_version(),
self.main_branch, self.main_branch,
) )
if not result.is_valid():
issues = '\n'.join.result.validate()
raise ValueError(f"invalid release: {issues}")
return result

@ -1,41 +1,14 @@
from typing import Optional from typing import Optional
from pybuilder.core import Project from pybuilder.core import Project
from src.main.python.ddadevops.devops_build import DevopsBuild 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.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): class ReleaseMixin(DevopsBuild):
def __init__(self, project: Project, release_config: Optional[ReleaseConfig] = None): def __init__(self, project: Project, release: Release):
if not release_config: super().__init__(project, devops=release.devops)
if not config: self.repo.set_release(self.project, release)
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)
git_api = GitApi() git_api = GitApi()
environment_api = EnvironmentApi() environment_api = EnvironmentApi()
@ -47,8 +20,8 @@ class ReleaseMixin(DevopsBuild):
else: else:
release_type_repo = ReleaseTypeRepository.from_git(git_api) release_type_repo = ReleaseTypeRepository.from_git(git_api)
version_repo = VersionRepository(release_config.config_file) version_repo = VersionRepository(release.config_file)
release_repo = ReleaseRepository(version_repo, release_type_repo, release_config.main_branch) release_repo = ReleaseContextRepository(version_repo, release_type_repo, release.main_branch)
self.prepare_release_service = PrepareReleaseService(release_repo) self.prepare_release_service = PrepareReleaseService(release_repo)
self.tag_and_push_release_service = TagAndPushReleaseService(git_api, release_repo) self.tag_and_push_release_service = TagAndPushReleaseService(git_api, release_repo)

@ -1,9 +1,11 @@
from pybuilder.core import Project from pybuilder.core import Project
from pathlib import Path
from src.main.python.ddadevops.domain.common import ( from src.main.python.ddadevops.domain.common import (
Validateable, Validateable,
DnsRecord, DnsRecord,
Devops, 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.image import Image
from src.main.python.ddadevops.domain.c4k import C4k from src.main.python.ddadevops.domain.c4k import C4k
from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config 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(): def test_validate_with_reason():
sut = MockValidateable(None) 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(): def test_should_validate_DnsRecord():
@ -173,3 +175,52 @@ def test_c4k_build_should_calculate_command():
+ "/target/name/module/out_module.yaml" + "/target/name/module/out_module.yaml"
== sut.command(devops) == 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()

@ -1,5 +1,7 @@
from src.main.python.ddadevops.domain import ReleaseType 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(): class MockVersion():
def __init__(self, id = None, version_list = None): def __init__(self, id = None, version_list = None):
@ -33,9 +35,9 @@ class MockRelease():
def bump_version(self): def bump_version(self):
return self.release_version().create_bump_version() return self.release_version().create_bump_version()
def validate(self, main_branch): def validate(self):
self.validate_count += 1 self.validate_count += 1
return [] return []
def is_valid(self, main_branch): def is_valid(self):
return len(self.validate(main_branch)) < 1 return len(self.validate()) < 1

@ -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"

@ -1,5 +1,5 @@
from src.main.python.ddadevops.domain import ReleaseType 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 .mock_infrastructure_api import MockGitApi, MockEnvironmentApi
from .helper import Helper from .helper import Helper
@ -22,7 +22,8 @@ def test_release_repository(tmp_path):
release_type_repo = ReleaseTypeRepository.from_git(MockGitApi('MINOR test')) release_type_repo = ReleaseTypeRepository.from_git(MockGitApi('MINOR test'))
# test # test
sut = ReleaseRepository(version_repo, release_type_repo, 'main') sut = ReleaseContextRepository(version_repo, release_type_repo, 'main')
release = sut.get_release() release = sut.get_release()
assert release is not None assert release is not None

@ -4,7 +4,7 @@ from pybuilder.core import Project
from src.main.python.ddadevops.release_mixin import ReleaseMixin 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.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 from .helper import Helper
@ -23,8 +23,8 @@ class MyBuild(ReleaseMixin):
def initialize_with_object(project, CONFIG_FILE): def initialize_with_object(project, CONFIG_FILE):
project.build_depends_on('ddadevops>=3.1.2') project.build_depends_on('ddadevops>=3.1.2')
devops = Devops(STAGE, PROJECT_ROOT_PATH, MODULE, "release_test", BUILD_DIR_NAME) devops = Devops(STAGE, PROJECT_ROOT_PATH, MODULE, "release_test", BUILD_DIR_NAME)
release_config = ReleaseConfig(MAIN_BRANCH, CONFIG_FILE, devops) release = Release(devops, MAIN_BRANCH, CONFIG_FILE)
build = MyBuild(project, release_config=release_config) build = MyBuild(project, release)
return build return build
def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch): def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch):

Loading…
Cancel
Save