integrate release as mixin
This commit is contained in:
parent
26b76a045a
commit
d670605d37
7 changed files with 133 additions and 53 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.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.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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue