integrate release as mixin

This commit is contained in:
Michael Jerger 2023-04-29 22:03:44 +02:00
parent 26b76a045a
commit d670605d37
7 changed files with 133 additions and 53 deletions

View file

@ -45,7 +45,7 @@ classDiagram
Devops *-- "0..1" Image: spcialized_builds Devops *-- "0..1" Image: spcialized_builds
Devops *-- "0..1" C4k: spcialized_builds Devops *-- "0..1" C4k: spcialized_builds
Devops *-- Release: release Devops *-- "0..1" Release: mixins
C4k *-- DnsRecord C4k *-- DnsRecord
Release *-- "0..1" ReleaseContext Release *-- "0..1" ReleaseContext

View file

@ -4,13 +4,20 @@ from typing import List, TypedDict
import logging import logging
import deprecation import deprecation
def filter_none(list_to_filter): def filter_none(list_to_filter):
return [x for x in list_to_filter if x is not None] return [x for x in list_to_filter if x is not None]
class BuildType(Enum): class BuildType(Enum):
IMAGE = 0 IMAGE = 0
C4K = 1 C4K = 1
class MixinType(Enum):
RELEASE = 0
class Validateable: class Validateable:
def __validate_is_not_none__(self, field_name: str) -> List[str]: def __validate_is_not_none__(self, field_name: str) -> List[str]:
value = self.__dict__[field_name] value = self.__dict__[field_name]
@ -32,9 +39,10 @@ class Validateable:
def throw_if_invalid(self): def throw_if_invalid(self):
if not self.is_valid(): if not self.is_valid():
issues = '\n'.join(self.validate()) issues = "\n".join(self.validate())
raise ValueError(f"Invalid Validateable: {issues}") raise ValueError(f"Invalid Validateable: {issues}")
class DnsRecord(Validateable): class DnsRecord(Validateable):
def __init__(self, fqdn, ipv4=None, ipv6=None): def __init__(self, fqdn, ipv4=None, ipv6=None):
self.fqdn = fqdn self.fqdn = fqdn
@ -50,13 +58,19 @@ class DnsRecord(Validateable):
class Devops(Validateable): class Devops(Validateable):
def __init__(self, input: dict, specialized_builds: dict[BuildType, Validateable]): def __init__(
self.stage = input.get('stage') self,
self.project_root_path = input.get('project_root_path') input: dict,
self.module = input.get('module') specialized_builds: dict[BuildType, Validateable],
self.name = input.get('name', self.module) mixins: dict[MixinType, Validateable],
self.build_dir_name = input.get('build_dir_name', 'target') ):
self.specialized_builds=specialized_builds self.stage = input.get("stage")
self.project_root_path = input.get("project_root_path")
self.module = input.get("module")
self.name = input.get("name", self.module)
self.build_dir_name = input.get("build_dir_name", "target")
self.specialized_builds = specialized_builds
self.mixins = mixins
def build_path(self): def build_path(self):
path = [self.project_root_path, self.build_dir_name, self.name, self.module] path = [self.project_root_path, self.build_dir_name, self.name, self.module]
@ -71,6 +85,9 @@ class Devops(Validateable):
if self.specialized_builds: if self.specialized_builds:
for build in self.specialized_builds: for build in self.specialized_builds:
result += self.specialized_builds[build].validate() result += self.specialized_builds[build].validate()
if self.mixins:
for mixin in self.mixins:
result += self.mixins[mixin].validate()
return result return result
def __put__(self, key, value): def __put__(self, key, value):

View file

@ -1,9 +1,11 @@
import deprecation import deprecation
from enum import Enum from enum import Enum
from typing import List from typing import List
from .common import Devops, BuildType from .common import Devops, BuildType, MixinType
from .image import Image from .image import Image
from .c4k import C4k from .c4k import C4k
from .release import Release, ReleaseContext
class DevopsFactory: class DevopsFactory:
def __init__(self): def __init__(self):
@ -11,13 +13,20 @@ class DevopsFactory:
def build_devops(self, input) -> Devops: def build_devops(self, input) -> Devops:
build_types = self.__parse_build_types__(input["build_types"]) build_types = self.__parse_build_types__(input["build_types"])
mixin_types = self.__parse_mixin_types__(input["mixin_types"])
specialized_builds = {} specialized_builds = {}
if BuildType.IMAGE in build_types: if BuildType.IMAGE in build_types:
specialized_builds[BuildType.IMAGE] = Image(input) specialized_builds[BuildType.IMAGE] = Image(input)
if BuildType.C4K in build_types: if BuildType.C4K in build_types:
specialized_builds[BuildType.C4K] = C4k(input) specialized_builds[BuildType.C4K] = C4k(input)
devops = Devops(input, specialized_builds=specialized_builds) mixins = {}
if MixinType.RELEASE in mixin_types:
release_context = ReleaseContext(input)
mixins[MixinType.RELEASE] = Release(input, release_context=release_context)
devops = Devops(input, specialized_builds=specialized_builds, mixins=mixins)
devops.throw_if_invalid() devops.throw_if_invalid()
@ -31,3 +40,9 @@ class DevopsFactory:
for build_type in build_types: for build_type in build_types:
result += [BuildType[build_type]] result += [BuildType[build_type]]
return result return result
def __parse_mixin_types__(self, mixin_types: List[str]) -> List[MixinType]:
result = []
for mixin_type in mixin_types:
result += [MixinType[mixin_type]]
return result

View file

@ -6,18 +6,21 @@ from .common import (
Devops, Devops,
) )
class ReleaseType(Enum): class ReleaseType(Enum):
MAJOR = 0 MAJOR = 0
MINOR = 1 MINOR = 1
PATCH = 2 PATCH = 2
SNAPSHOT = 3 SNAPSHOT = 3
BUMP = None BUMP = None
NONE = 15
class EnvironmentKeys(Enum): class EnvironmentKeys(Enum):
DDADEVOPS_RELEASE_TYPE = 0 DDADEVOPS_RELEASE_TYPE = 0
class Version():
class Version(Validateable):
def __init__(self, path: Path, version_list: list): def __init__(self, path: Path, version_list: list):
self.path = path self.path = path
self.version_list = version_list self.version_list = version_list
@ -62,11 +65,25 @@ class Version():
bump_version.increment(ReleaseType.BUMP) bump_version.increment(ReleaseType.BUMP)
return bump_version return bump_version
class ReleaseContext(Validateable): class ReleaseContext(Validateable):
def __init__(self, release_type: ReleaseType | None, version: Version, current_branch: str): def __init__(
self.release_type = release_type self,
self.version = version input: dict,
self.current_branch = current_branch ):
self.release_type = ReleaseType[input.get("release_type", "SNAPSHOT")]
self.release_current_version = input.get("release_current_version")
self.release_current_branch = input.get("release_current_branch")
self.version = self.__version_from_str__()
# TODO: mv version parsing to version
def __version_from_str__(self):
if not self.release_current_version:
return
version_parsed = []
for x in self.release_current_version.split("."):
version_parsed += [int(x)]
return Version("unused", version_parsed)
def release_version(self) -> Version: def release_version(self) -> Version:
return self.version.create_release_version(self.release_type) return self.version.create_release_version(self.release_type)
@ -77,30 +94,32 @@ class ReleaseContext(Validateable):
def validate(self): def validate(self):
result = [] result = []
result += self.__validate_is_not_empty__("release_type") result += self.__validate_is_not_empty__("release_type")
result += self.__validate_is_not_empty__("version") result += self.__validate_is_not_empty__("release_current_version")
result += self.__validate_is_not_empty__("current_branch") result += self.__validate_is_not_empty__("release_current_branch")
if self.version:
result += self.version.validate()
return result return result
def validate_branch(self, main_branch: str): def validate_branch(self, main_branch: str):
result = [] result = []
if self.release_type is not None and main_branch != self.current_branch: if (
self.release_type is not None
and self.release_type != ReleaseType.NONE
and main_branch != self.release_current_branch
):
result.append(f"Releases are allowed only on {main_branch}") result.append(f"Releases are allowed only on {main_branch}")
return result return result
class Release(Validateable): class Release(Validateable):
def __init__( def __init__(
self, self,
devops: Devops, input: dict,
main_branch: str, release_context: ReleaseContext,
config_file: str,
): ):
self.devops = devops self.release_main_branch = input.get("release_main_branch", "main")
self.main_branch = main_branch self.release_config_file = input.get("release_config_file", "project.clj")
self.config_file = config_file self.release_context = release_context
self.release_context: ReleaseContext | None = None
def set_release_context(self, set_release_context: ReleaseContext):
self.release_context = set_release_context
def release_version(self): def release_version(self):
return self.release_context.release_version() return self.release_context.release_version()
@ -108,14 +127,12 @@ class Release(Validateable):
def bump_version(self): def bump_version(self):
return self.release_context.bump_version() return self.release_context.bump_version()
def validate(self): def validate(self):
result = [] result = []
result += self.__validate_is_not_empty__("main_branch") result += self.__validate_is_not_empty__("release_main_branch")
result += self.__validate_is_not_empty__("config_file") result += self.__validate_is_not_empty__("release_config_file")
result += self.__validate_is_not_empty__("release_context") result += self.__validate_is_not_empty__("release_context")
if self.release_context is not None: if self.release_context is not None:
result += self.release_context.validate() result += self.release_context.validate()
result += self.release_context.validate_branch(self.main_branch) result += self.release_context.validate_branch(self.release_main_branch)
return result return result

View file

@ -22,12 +22,13 @@ def test_devops_factory():
"module": "test_image", "module": "test_image",
"project_root_path": "../../..", "project_root_path": "../../..",
"build_types": ["IMAGE"], "build_types": ["IMAGE"],
"mixin_types": [],
"image_dockerhub_user": "dockerhub_user", "image_dockerhub_user": "dockerhub_user",
"image_dockerhub_password": "dockerhub_password", "image_dockerhub_password": "dockerhub_password",
"image_tag": "docker_image_tag", "image_tag": "docker_image_tag",
} }
) )
assert sut != None assert sut is not None
sut = DevopsFactory().build_devops( sut = DevopsFactory().build_devops(
{ {
@ -36,8 +37,26 @@ def test_devops_factory():
"module": "test_image", "module": "test_image",
"project_root_path": "../../..", "project_root_path": "../../..",
"build_types": ["C4K"], "build_types": ["C4K"],
"mixin_types": [],
"c4k_grafana_cloud_user": "user", "c4k_grafana_cloud_user": "user",
"c4k_grafana_cloud_password": "password", "c4k_grafana_cloud_password": "password",
} }
) )
assert sut != None assert sut is not None
sut = DevopsFactory().build_devops(
{
"stage": "test",
"name": "mybuild",
"module": "test_image",
"project_root_path": "../../..",
"build_types": [],
"mixin_types": ["RELEASE"],
"release_main_branch": "main",
"release_config_file": "project.clj",
"release_type": "NONE",
"release_current_version": "1.0.0",
"release_current_branch": "my_feature",
}
)
assert sut is not None

View file

@ -9,14 +9,20 @@ def devops_config(overrides: dict) -> dict:
"project_root_path": "../../..", "project_root_path": "../../..",
"build_dir_name": "target", "build_dir_name": "target",
"build_types": ["IMAGE", "C4K"], "build_types": ["IMAGE", "C4K"],
"mixin_types": ["RELEASE"],
"image_dockerhub_user": "dockerhub_user", "image_dockerhub_user": "dockerhub_user",
"image_dockerhub_password": "dockerhub_password", "image_dockerhub_password": "dockerhub_password",
"image_tag": "image_tag", "image_tag": "image_tag",
'c4k_config': {}, "c4k_config": {},
"c4k_grafana_cloud_user": "user", "c4k_grafana_cloud_user": "user",
"c4k_grafana_cloud_password": "password", "c4k_grafana_cloud_password": "password",
"c4k_grafana_cloud_url": "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push", "c4k_grafana_cloud_url": "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push",
'c4k_auth': {}, "c4k_auth": {},
"release_main_branch": "main",
"release_config_file": "project.clj",
"release_type": "NONE",
"release_current_version": "1.0.0",
"release_current_branch": "my_feature",
} }
input = default.copy() input = default.copy()
input.update(overrides) input.update(overrides)

View file

@ -5,6 +5,7 @@ from src.main.python.ddadevops.domain.common import (
DnsRecord, DnsRecord,
Devops, Devops,
BuildType, BuildType,
MixinType,
) )
from src.main.python.ddadevops.domain import ( from src.main.python.ddadevops.domain import (
Version, Version,
@ -13,7 +14,8 @@ from src.main.python.ddadevops.domain import (
ReleaseContext, ReleaseContext,
) )
from src.main.python.ddadevops.domain.image import Image from src.main.python.ddadevops.domain.image import Image
from .test_helper import build_devops from .test_helper import build_devops, devops_config
def test_version(tmp_path: Path): def test_version(tmp_path: Path):
version = Version(tmp_path, [1, 2, 3]) version = Version(tmp_path, [1, 2, 3])
@ -48,22 +50,26 @@ def test_version(tmp_path: Path):
def test_release_context(tmp_path): def test_release_context(tmp_path):
version = Version(tmp_path, [1, 2, 3]) sut = ReleaseContext(
release = ReleaseContext(ReleaseType.MINOR, version, "main") devops_config(
{
release_version = release.release_version() "release_type": "MINOR",
assert release_version.get_version_string() in "1.3.0" "release_current_version": "1.2.3",
"release_current_branch": "main",
bump_version = release.bump_version() }
assert bump_version.get_version_string() in "1.3.1-SNAPSHOT" )
)
assert sut.release_version().get_version_string() == "1.3.0"
assert sut.bump_version().get_version_string() == "1.3.1-SNAPSHOT"
def test_release(tmp_path): def test_release(tmp_path):
devops = build_devops({}) sut = build_devops(
sut = Release(devops, "main", "config_file.json") {
assert not sut.is_valid() "release_type": "MINOR",
"release_current_version": "1.2.3",
sut.set_release_context( "release_current_branch": "main",
ReleaseContext(ReleaseType.MINOR, Version("id", [1, 2, 3]), "main") }
) )
assert sut.mixins[MixinType.RELEASE]
assert sut.is_valid() assert sut.is_valid()