From decd36b67e9db8073c9d079529d970f7b173be99 Mon Sep 17 00:00:00 2001 From: see Date: Wed, 1 Feb 2023 14:46:18 +0000 Subject: [PATCH 001/243] Initial commit --- .gitignore | 23 +++++++++++++++++++++++ LICENSE | 9 +++++++++ README.md | 2 ++ 3 files changed, 34 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..adf8f72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# ---> Go +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e8c70ca --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# terraformDummyRepo + From 0bd416a63f5327f0bb10990af05e293496e132cb Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 8 Feb 2023 10:33:39 +0100 Subject: [PATCH 002/243] WIP prepare build.py --- build.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 build.py diff --git a/build.py b/build.py new file mode 100644 index 0000000..9bc2066 --- /dev/null +++ b/build.py @@ -0,0 +1,21 @@ +from pybuilder.core import task, init +from ddadevops import * + +@init +def initialize(project): + pass + +@task +def release(project): + build = get_devops_build(project) + build.init() + build.prepareRelease() # mit config file + # build + build.publish + build.releaseInGit + + +@task +def build(project): + build = get_devops_build(project) + # ToDo: Implement for project From 68397ed0e4ca94ed1509f375362477ee863c185e Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 8 Feb 2023 10:57:40 +0100 Subject: [PATCH 003/243] Add skeleton for testing Version --- .gitignore | 1 + build.py | 25 ++++++++++++++++++------- devops_test.py | 34 ++++++++++++++++++++++++++++++++++ package.json | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 devops_test.py create mode 100644 package.json diff --git a/.gitignore b/.gitignore index adf8f72..a9c5fbc 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ # Go workspace file go.work +__pycache__ \ No newline at end of file diff --git a/build.py b/build.py index 9bc2066..af9430f 100644 --- a/build.py +++ b/build.py @@ -1,21 +1,32 @@ from pybuilder.core import task, init from ddadevops import * +from devops_test import * + + +def main(): + init_project() @init def initialize(project): - pass + project.build_depends_on('ddadevops>=3.1.2') + # build = get_devops_build() + # build.init() + init_project() @task def release(project): - build = get_devops_build(project) - build.init() - build.prepareRelease() # mit config file - # build - build.publish - build.releaseInGit + # build = get_devops_build(project) + # build.prepare_release() # mit config file + prepare_release() + # build() + # build.publish() + # build.release_in_git() + # release_in_git() @task def build(project): build = get_devops_build(project) # ToDo: Implement for project + +main() \ No newline at end of file diff --git a/devops_test.py b/devops_test.py new file mode 100644 index 0000000..8d87964 --- /dev/null +++ b/devops_test.py @@ -0,0 +1,34 @@ +import json + +def init_project(): + # validate_values() + version = Version('package.json') + version.parse() + +def prepare_release(): + pass + +def release_in_git(): + pass + +class Version(): + + def __init__(self, config_file_path): + self.version = "0.0.0" + self.config_file_path = config_file_path + print('init project') + + def parse(self): + if self.config_file_path.split('.').last() == 'json': + self.__parse_json() + + def __parse_json(self): + with open(self.config_file_path, 'r') as json_file: + json_data = json.load(json_file) + print(json_data['version']) + + def increment(self, level): + pass + + def get(): + pass \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..03f0a3e --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "dummy", + "description": "Generate c4k yaml for a jitsi deployment.", + "author": "meissa GmbH", + "version": "1.3.2", + "homepage": "https://gitlab.com/domaindrivenarchitecture/c4k-jitsi#readme", + "repository": "https://www.npmjs.com/package/c4k-jitsi", + "license": "APACHE2", + "main": "c4k-jitsi.js", + "bin": { + "c4k-jitsi": "./c4k-jitsi.js" + }, + "keywords": [ + "cljs", + "jitsi", + "k8s", + "c4k", + "deployment", + "yaml", + "convention4kubernetes" + ], + "bugs": { + "url": "https://gitlab.com/domaindrivenarchitecture/c4k-jitsi/issues" + }, + "dependencies": { + "js-base64": "^3.6.1", + "js-yaml": "^4.0.0" + }, + "devDependencies": { + "shadow-cljs": "^2.11.18", + "source-map-support": "^0.5.19" + } + } \ No newline at end of file From fbcd9ee3981534308ca958074073ce3a46239ffa Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 8 Feb 2023 11:22:55 +0100 Subject: [PATCH 004/243] WIP Implement ReleaseLevel --- build.py | 3 +-- devops_test.py | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/build.py b/build.py index af9430f..3efe26d 100644 --- a/build.py +++ b/build.py @@ -21,8 +21,7 @@ def release(project): # build() # build.publish() # build.release_in_git() - # release_in_git() - + # release_in_git() @task def build(project): diff --git a/devops_test.py b/devops_test.py index 8d87964..59b1e71 100644 --- a/devops_test.py +++ b/devops_test.py @@ -1,4 +1,5 @@ import json +from enum import Enum def init_project(): # validate_values() @@ -13,22 +14,37 @@ def release_in_git(): class Version(): + class ReleaseLevel(Enum): + SNAPSHOT = 0 + PATCH = 1 + MINOR = 2 + MAJOR = 3 + def __init__(self, config_file_path): self.version = "0.0.0" self.config_file_path = config_file_path print('init project') def parse(self): - if self.config_file_path.split('.').last() == 'json': + if self.config_file_path.split('.')[-1] == 'json': self.__parse_json() def __parse_json(self): with open(self.config_file_path, 'r') as json_file: json_data = json.load(json_file) - print(json_data['version']) + self.version = json_data['version'] - def increment(self, level): - pass + def increment(self, level: ReleaseLevel): + match level: + case ReleaseLevel.SNAPSHOT: + if "-SNAPSHOT" not in self.version + self.version = self.version + "-SNAPSHOT" + case ReleaseLevel.PATCH: + pass + case ReleaseLevel.MINOR: + pass + case ReleaseLevel.MAJOR: + pass def get(): pass \ No newline at end of file From 691b17dc5664cfc8325be71827103e343fd0ac4b Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 8 Feb 2023 11:37:41 +0100 Subject: [PATCH 005/243] Move some functions around for testing --- devops_test.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/devops_test.py b/devops_test.py index 59b1e71..1695307 100644 --- a/devops_test.py +++ b/devops_test.py @@ -5,6 +5,8 @@ def init_project(): # validate_values() version = Version('package.json') version.parse() + version.increment(ReleaseLevel.SNAPSHOT) + print(version.get()) def prepare_release(): pass @@ -12,13 +14,13 @@ def prepare_release(): def release_in_git(): pass -class Version(): +class ReleaseLevel(Enum): + SNAPSHOT = 0 + PATCH = 1 + MINOR = 2 + MAJOR = 3 - class ReleaseLevel(Enum): - SNAPSHOT = 0 - PATCH = 1 - MINOR = 2 - MAJOR = 3 +class Version(): def __init__(self, config_file_path): self.version = "0.0.0" @@ -46,5 +48,5 @@ class Version(): case ReleaseLevel.MAJOR: pass - def get(): - pass \ No newline at end of file + def get(self) -> str: + return self.version \ No newline at end of file From 09d08d6e431ccddac202a2c860238672b1a36b3f Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 8 Feb 2023 11:42:46 +0100 Subject: [PATCH 006/243] WIP --- devops_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devops_test.py b/devops_test.py index 1695307..9ac7180 100644 --- a/devops_test.py +++ b/devops_test.py @@ -39,7 +39,7 @@ class Version(): def increment(self, level: ReleaseLevel): match level: case ReleaseLevel.SNAPSHOT: - if "-SNAPSHOT" not in self.version + if "-SNAPSHOT" not in self.version: self.version = self.version + "-SNAPSHOT" case ReleaseLevel.PATCH: pass From c1928b8c440671ec02c02e459b7529c879113eb8 Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 8 Feb 2023 11:52:15 +0100 Subject: [PATCH 007/243] WIP implement patch --- devops_test.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/devops_test.py b/devops_test.py index 9ac7180..a0aa37f 100644 --- a/devops_test.py +++ b/devops_test.py @@ -5,7 +5,7 @@ def init_project(): # validate_values() version = Version('package.json') version.parse() - version.increment(ReleaseLevel.SNAPSHOT) + version.increment(ReleaseLevel.PATCH) print(version.get()) def prepare_release(): @@ -15,17 +15,17 @@ def release_in_git(): pass class ReleaseLevel(Enum): - SNAPSHOT = 0 - PATCH = 1 - MINOR = 2 - MAJOR = 3 + MAJOR = 0 + MINOR = 1 + PATCH = 2 + SNAPSHOT = 3 + class Version(): def __init__(self, config_file_path): self.version = "0.0.0" self.config_file_path = config_file_path - print('init project') def parse(self): if self.config_file_path.split('.')[-1] == 'json': @@ -42,7 +42,10 @@ class Version(): if "-SNAPSHOT" not in self.version: self.version = self.version + "-SNAPSHOT" case ReleaseLevel.PATCH: - pass + split_version = self.version.split('.') + patch_version = int(split_version[ReleaseLevel.PATCH.value]) + self.version = split_version[:ReleaseLevel.PATCH.value] + str(patch_version + 1) + print(self.version) case ReleaseLevel.MINOR: pass case ReleaseLevel.MAJOR: From 1fe186cc4a243ac32831c97188cf91e55c33673c Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 8 Feb 2023 12:14:53 +0100 Subject: [PATCH 008/243] WIP conversion issues --- devops_test.py | 39 ++++++++++++++++++++++++++++++--------- package.json | 30 +++++++++++++++--------------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/devops_test.py b/devops_test.py index a0aa37f..2958295 100644 --- a/devops_test.py +++ b/devops_test.py @@ -5,7 +5,7 @@ def init_project(): # validate_values() version = Version('package.json') version.parse() - version.increment(ReleaseLevel.PATCH) + version.increment(ReleaseLevel.MAJOR) print(version.get()) def prepare_release(): @@ -37,19 +37,40 @@ class Version(): self.version = json_data['version'] def increment(self, level: ReleaseLevel): - match level: - case ReleaseLevel.SNAPSHOT: - if "-SNAPSHOT" not in self.version: + + if level is ReleaseLevel.SNAPSHOT: + if "-SNAPSHOT" not in self.version: self.version = self.version + "-SNAPSHOT" - case ReleaseLevel.PATCH: + else: + # convert array to int + # e.g. patch index +1 + # convert back to str + # join + split_versiont = split_version. + + + match level: + + + case ReleaseLevel.PATCH: + self.version = self.version.replace("-SNAPSHOT", "") split_version = self.version.split('.') patch_version = int(split_version[ReleaseLevel.PATCH.value]) - self.version = split_version[:ReleaseLevel.PATCH.value] + str(patch_version + 1) - print(self.version) + self.version = ".".join(split_version[:ReleaseLevel.PATCH.value]) + "." + str(patch_version + 1) + case ReleaseLevel.MINOR: - pass + self.version = self.version.replace("-SNAPSHOT", "") + split_version = self.version.split('.') + minor_version = int(split_version[ReleaseLevel.MINOR.value]) + self.version = ".".join(split_version[:ReleaseLevel.MINOR.value]) + "." + str(minor_version + 1) + ".0" + case ReleaseLevel.MAJOR: - pass + self.version = self.version.replace("-SNAPSHOT", "") + split_version = self.version.split('.') + major_version = int(split_version[ReleaseLevel.MAJOR.value]) + self.version = "".join(split_version[:ReleaseLevel.MAJOR.value]) + str(major_version + 1) + ".0" + ".0" + + def get(self) -> str: return self.version \ No newline at end of file diff --git a/package.json b/package.json index 03f0a3e..c582f21 100644 --- a/package.json +++ b/package.json @@ -2,32 +2,32 @@ "name": "dummy", "description": "Generate c4k yaml for a jitsi deployment.", "author": "meissa GmbH", - "version": "1.3.2", + "version": "1.3.2-SNAPSHOT", "homepage": "https://gitlab.com/domaindrivenarchitecture/c4k-jitsi#readme", "repository": "https://www.npmjs.com/package/c4k-jitsi", "license": "APACHE2", "main": "c4k-jitsi.js", "bin": { - "c4k-jitsi": "./c4k-jitsi.js" + "c4k-jitsi": "./c4k-jitsi.js" }, "keywords": [ - "cljs", - "jitsi", - "k8s", - "c4k", - "deployment", - "yaml", - "convention4kubernetes" + "cljs", + "jitsi", + "k8s", + "c4k", + "deployment", + "yaml", + "convention4kubernetes" ], "bugs": { - "url": "https://gitlab.com/domaindrivenarchitecture/c4k-jitsi/issues" + "url": "https://gitlab.com/domaindrivenarchitecture/c4k-jitsi/issues" }, "dependencies": { - "js-base64": "^3.6.1", - "js-yaml": "^4.0.0" + "js-base64": "^3.6.1", + "js-yaml": "^4.0.0" }, "devDependencies": { - "shadow-cljs": "^2.11.18", - "source-map-support": "^0.5.19" + "shadow-cljs": "^2.11.18", + "source-map-support": "^0.5.19" } - } \ No newline at end of file +} \ No newline at end of file From eb6f5eb5684d8829e39af19ba882f567b88a19b4 Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 8 Feb 2023 12:37:00 +0100 Subject: [PATCH 009/243] Add write_json logic --- devops_test.py | 75 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/devops_test.py b/devops_test.py index 2958295..4224191 100644 --- a/devops_test.py +++ b/devops_test.py @@ -24,53 +24,52 @@ class ReleaseLevel(Enum): class Version(): def __init__(self, config_file_path): - self.version = "0.0.0" + self.version = [] + self.is_snapshot = False self.config_file_path = config_file_path + self.config_file_type = config_file_path.split('.')[-1] def parse(self): - if self.config_file_path.split('.')[-1] == 'json': - self.__parse_json() + match self.config_file_type: + case 'json': + self.__parse_json() def __parse_json(self): with open(self.config_file_path, 'r') as json_file: - json_data = json.load(json_file) - self.version = json_data['version'] + json_version = json.load(json_file)['version'] + if '-SNAPSHOT' in json_version: + self.is_snapshot = True + json_version = json_version.replace('-SNAPSHOT', '') + self.version = [int(x) for x in json_version.split('.')] def increment(self, level: ReleaseLevel): - - if level is ReleaseLevel.SNAPSHOT: - if "-SNAPSHOT" not in self.version: - self.version = self.version + "-SNAPSHOT" - else: - # convert array to int - # e.g. patch index +1 - # convert back to str - # join - split_versiont = split_version. - - - match level: - - - case ReleaseLevel.PATCH: - self.version = self.version.replace("-SNAPSHOT", "") - split_version = self.version.split('.') - patch_version = int(split_version[ReleaseLevel.PATCH.value]) - self.version = ".".join(split_version[:ReleaseLevel.PATCH.value]) + "." + str(patch_version + 1) - + self.is_snapshot = False + match level: + case ReleaseLevel.SNAPSHOT: + self.is_snapshot = True + case ReleaseLevel.PATCH: + self.version[ReleaseLevel.PATCH.value] += 1 case ReleaseLevel.MINOR: - self.version = self.version.replace("-SNAPSHOT", "") - split_version = self.version.split('.') - minor_version = int(split_version[ReleaseLevel.MINOR.value]) - self.version = ".".join(split_version[:ReleaseLevel.MINOR.value]) + "." + str(minor_version + 1) + ".0" - + self.version[ReleaseLevel.PATCH.value] = 0 + self.version[ReleaseLevel.MINOR.value] += 1 case ReleaseLevel.MAJOR: - self.version = self.version.replace("-SNAPSHOT", "") - split_version = self.version.split('.') - major_version = int(split_version[ReleaseLevel.MAJOR.value]) - self.version = "".join(split_version[:ReleaseLevel.MAJOR.value]) + str(major_version + 1) + ".0" + ".0" - - + self.version[ReleaseLevel.PATCH.value] = 0 + self.version[ReleaseLevel.MINOR.value] = 0 + self.version[ReleaseLevel.MAJOR.value] += 1 + + def write(self): + match self.config_file_type: + case 'json': + self.__write_json() + + def __write_json(self): + with open(self.config_file_path, 'wr') as json_file: + json_data = json.load(json_file) + json_data['version'] = self.get() + json.dump(json_data, json_file) def get(self) -> str: - return self.version \ No newline at end of file + version_string = ".".join([str(x) for x in self.version]) + if self.is_snapshot: + version_string += "-SNAPSHOT" + return version_string \ No newline at end of file From 8a3e77aadf113bc3077adb495ecd8d782430b163 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 8 Feb 2023 12:57:15 +0100 Subject: [PATCH 010/243] Implement json Versioning POC --- devops_test.py | 8 +++++--- package.json | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/devops_test.py b/devops_test.py index 4224191..190ea5d 100644 --- a/devops_test.py +++ b/devops_test.py @@ -6,6 +6,7 @@ def init_project(): version = Version('package.json') version.parse() version.increment(ReleaseLevel.MAJOR) + version.write() print(version.get()) def prepare_release(): @@ -19,7 +20,6 @@ class ReleaseLevel(Enum): MINOR = 1 PATCH = 2 SNAPSHOT = 3 - class Version(): @@ -63,10 +63,12 @@ class Version(): self.__write_json() def __write_json(self): - with open(self.config_file_path, 'wr') as json_file: + with open(self.config_file_path, 'r+') as json_file: json_data = json.load(json_file) json_data['version'] = self.get() - json.dump(json_data, json_file) + json_file.seek(0) + json.dump(json_data, json_file, indent=4) + json_file.truncate() def get(self) -> str: version_string = ".".join([str(x) for x in self.version]) diff --git a/package.json b/package.json index c582f21..bbf2883 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "dummy", "description": "Generate c4k yaml for a jitsi deployment.", "author": "meissa GmbH", - "version": "1.3.2-SNAPSHOT", + "version": "3.0.0", "homepage": "https://gitlab.com/domaindrivenarchitecture/c4k-jitsi#readme", "repository": "https://www.npmjs.com/package/c4k-jitsi", "license": "APACHE2", From 1995a7b200088c3fb238d9fcb56500c1931e77cf Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 9 Feb 2023 11:43:10 +0100 Subject: [PATCH 011/243] Add support for gradle files --- build.gradle | 6352 ++++++++++++++++++++++++++++++++++++++++++++++++ devops_test.py | 39 +- 2 files changed, 6388 insertions(+), 3 deletions(-) create mode 100644 build.gradle diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6a8577e --- /dev/null +++ b/build.gradle @@ -0,0 +1,6352 @@ +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1" + +repositories { + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +repositories { + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +repositories { + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +repositories { + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +repositories { + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +repositories { + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + mavenCentral() +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" +} + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} +buildscript { + ext.kotlin_version = "1.7.0" + ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID + + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +apply plugin: "org.jetbrains.kotlin.jvm" +apply plugin: "java-library" +apply plugin: "java-test-fixtures" +apply plugin: "maven-publish" +apply plugin: "kotlinx-serialization" + + +group = "org.domaindrivenarchitecture.provs" +version = "0.17.1-SNAPSHOT" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + + +test { + // set properties for the tests + def propertiesForTests = ["testdockerwithoutsudo"] + for (def prop : propertiesForTests) { + def value = System.getProperty(prop) + if (value != null) { + systemProperty prop, value + } + } + + useJUnitPlatform { + def excludedTags = System.getProperty("excludeTags") + if (System.getProperty("excludeTags") != null) { + excludeTags(excludedTags.split(",")) + } + if (System.getenv("CI_JOB_TOKEN") != null) { + excludeTags("containernonci") + } + } +} + +compileJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" +compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" + +// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc +java { + withSourcesJar() + withJavadocJar() +} + + +dependencies { + + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") + api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") + + api('com.charleskorn.kaml:kaml:0.43.0') + + api("org.slf4j:slf4j-api:1.7.36") + api('ch.qos.logback:logback-classic:1.2.11') + api('ch.qos.logback:logback-core:1.2.11') + + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + implementation("com.hierynomus:sshj:0.32.0") + + implementation("aws.sdk.kotlin:s3:0.17.1-beta") + + testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") + testFixturesApi('io.mockk:mockk:1.12.3') + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + + +task uberjarDesktop(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" + } + archiveFileName = "provs-desktop.jar" +} + + +task uberjarServer(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" + } + archiveFileName = "provs-server.jar" +} + + +task uberjarSyspec(type: Jar) { + + from sourceSets.main.output + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } + } { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes "Implementation-Title": "Uberjar of provs", + "Implementation-Version": project.version, + "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" + } + archiveFileName = "provs-syspec.jar" +} +def projectRoot = rootProject.projectDir + + +// copy jar to /usr/local/bin and make it executable +// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) +task installlocally { + dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) + doLast { + exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } + exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } + } +} + +task sourceJar(type: Jar, dependsOn: classes) { + from sourceSets.main.allSource + archiveClassifier.set("sources") +} + + +publishing { + publications { + library(MavenPublication) { + from components.java + } + } + repositories { + if (System.getenv("CI_JOB_TOKEN") != null) { + // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html + maven { + url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = "Job-Token" + value = System.getenv("CI_JOB_TOKEN") + } + authentication { + header(HttpHeaderAuthentication) + } + } + } else { + mavenLocal() + } + } +} diff --git a/devops_test.py b/devops_test.py index 190ea5d..0ba4358 100644 --- a/devops_test.py +++ b/devops_test.py @@ -1,13 +1,13 @@ import json from enum import Enum +import re def init_project(): # validate_values() - version = Version('package.json') + version = Version('build.gradle') version.parse() - version.increment(ReleaseLevel.MAJOR) + version.increment(ReleaseLevel.SNAPSHOT) version.write() - print(version.get()) def prepare_release(): pass @@ -33,6 +33,8 @@ class Version(): match self.config_file_type: case 'json': self.__parse_json() + case 'gradle': + self.__parse_gradle() def __parse_json(self): with open(self.config_file_path, 'r') as json_file: @@ -42,6 +44,25 @@ class Version(): json_version = json_version.replace('-SNAPSHOT', '') self.version = [int(x) for x in json_version.split('.')] + def __parse_gradle(self): + with open(self.config_file_path, 'r') as gradle_file: + contents = gradle_file.read() + version_line = re.search("\nversion = .*\n", contents) + if version_line is None: + raise Exception("Version not found in gradle file") + version_line = version_line.group() + version_string = re.search('[0-9]*\.[0-9]*\.[0-9]*(-SNAPSHOT)?', version_line) + if version_string is None: + raise Exception("Version not found in gradle file") + + version_string = version_string.group() + if '-SNAPSHOT' in version_string: + self.is_snapshot = True + version_string = version_string.replace('-SNAPSHOT', '') + + self.version = [int(x) for x in version_string.split('.')] + + def increment(self, level: ReleaseLevel): self.is_snapshot = False match level: @@ -61,6 +82,10 @@ class Version(): match self.config_file_type: case 'json': self.__write_json() + case 'gradle': + self.__write_gradle() + case _: + raise Exception(f'The file type "{self.config_file_type}" is not implemented') def __write_json(self): with open(self.config_file_path, 'r+') as json_file: @@ -70,6 +95,14 @@ class Version(): json.dump(json_data, json_file, indent=4) json_file.truncate() + def __write_gradle(self): + with open(self.config_file_path, 'r+') as gradle_file: + gradle_contents = gradle_file.read() + version_substitute = re.sub("\nversion = .*\n", f'\nversion = "{self.get()}"\n', gradle_contents) + gradle_file.seek(0) + gradle_file.write(version_substitute) + gradle_file.truncate() + def get(self) -> str: version_string = ".".join([str(x) for x in self.version]) if self.is_snapshot: From 4a34763fb2bc14ef586eb4900b615c18d6304e0a Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 9 Feb 2023 12:02:31 +0100 Subject: [PATCH 012/243] Add basic tests for version increment --- devops_test.py | 23 +++++++++++++++-------- test/test_version.py | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 test/test_version.py diff --git a/devops_test.py b/devops_test.py index 0ba4358..8adcb5f 100644 --- a/devops_test.py +++ b/devops_test.py @@ -4,8 +4,7 @@ import re def init_project(): # validate_values() - version = Version('build.gradle') - version.parse() + version = Version.from_file('build.gradle') version.increment(ReleaseLevel.SNAPSHOT) version.write() @@ -23,11 +22,17 @@ class ReleaseLevel(Enum): class Version(): - def __init__(self, config_file_path): - self.version = [] - self.is_snapshot = False - self.config_file_path = config_file_path - self.config_file_type = config_file_path.split('.')[-1] + def __init__(self, version, is_snapshot): + self.version = version + self.is_snapshot = is_snapshot + + @classmethod + def from_file(cls, config_file_path): + ret_cls = cls(None, False) + ret_cls.config_file_path = config_file_path + ret_cls.config_file_type = config_file_path.split('.')[-1] + ret_cls.parse() + return ret_cls def parse(self): match self.config_file_type: @@ -35,6 +40,8 @@ class Version(): self.__parse_json() case 'gradle': self.__parse_gradle() + case _: + raise Exception(f'The file type "{self.config_file_type}" is not implemented') def __parse_json(self): with open(self.config_file_path, 'r') as json_file: @@ -51,7 +58,7 @@ class Version(): if version_line is None: raise Exception("Version not found in gradle file") version_line = version_line.group() - version_string = re.search('[0-9]*\.[0-9]*\.[0-9]*(-SNAPSHOT)?', version_line) + version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) if version_string is None: raise Exception("Version not found in gradle file") diff --git a/test/test_version.py b/test/test_version.py new file mode 100644 index 0000000..d72416b --- /dev/null +++ b/test/test_version.py @@ -0,0 +1,25 @@ +from devops_test import Version, ReleaseLevel + +def test_version(): + version = Version([1, 2, 3], False) + + version.increment(ReleaseLevel.SNAPSHOT) + assert version.get() == "1.2.3-SNAPSHOT" + assert version.version == [1, 2, 3] + assert version.is_snapshot + + version.increment(ReleaseLevel.PATCH) + assert version.get() == "1.2.4" + assert version.version == [1, 2, 4] + assert not version.is_snapshot + + version.increment(ReleaseLevel.SNAPSHOT) + assert version.get() == "1.2.4-SNAPSHOT" + version.increment(ReleaseLevel.SNAPSHOT) + assert version.get() == "1.2.4-SNAPSHOT" + + version.increment(ReleaseLevel.MINOR) + assert version.get() == "1.3.0" + + version.increment(ReleaseLevel.MAJOR) + assert version.get() == "2.0.0" \ No newline at end of file From d50b8bd95b1feef96e375e800564e80dcc1c6e7b Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 9 Feb 2023 12:38:04 +0100 Subject: [PATCH 013/243] Move parse and write code to file handler --- devops_test.py | 71 ++---------------------------------------- file_handlers.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 68 deletions(-) create mode 100644 file_handlers.py diff --git a/devops_test.py b/devops_test.py index 8adcb5f..1a2806b 100644 --- a/devops_test.py +++ b/devops_test.py @@ -1,6 +1,5 @@ -import json from enum import Enum -import re +from file_handlers import FileHandler def init_project(): # validate_values() @@ -28,47 +27,8 @@ class Version(): @classmethod def from_file(cls, config_file_path): - ret_cls = cls(None, False) - ret_cls.config_file_path = config_file_path - ret_cls.config_file_type = config_file_path.split('.')[-1] - ret_cls.parse() - return ret_cls - - def parse(self): - match self.config_file_type: - case 'json': - self.__parse_json() - case 'gradle': - self.__parse_gradle() - case _: - raise Exception(f'The file type "{self.config_file_type}" is not implemented') - - def __parse_json(self): - with open(self.config_file_path, 'r') as json_file: - json_version = json.load(json_file)['version'] - if '-SNAPSHOT' in json_version: - self.is_snapshot = True - json_version = json_version.replace('-SNAPSHOT', '') - self.version = [int(x) for x in json_version.split('.')] - - def __parse_gradle(self): - with open(self.config_file_path, 'r') as gradle_file: - contents = gradle_file.read() - version_line = re.search("\nversion = .*\n", contents) - if version_line is None: - raise Exception("Version not found in gradle file") - version_line = version_line.group() - version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) - if version_string is None: - raise Exception("Version not found in gradle file") - - version_string = version_string.group() - if '-SNAPSHOT' in version_string: - self.is_snapshot = True - version_string = version_string.replace('-SNAPSHOT', '') - - self.version = [int(x) for x in version_string.split('.')] - + file_handler = FileHandler.from_file_path(config_file_path) + return cls(file_handler.parse()) def increment(self, level: ReleaseLevel): self.is_snapshot = False @@ -85,31 +45,6 @@ class Version(): self.version[ReleaseLevel.MINOR.value] = 0 self.version[ReleaseLevel.MAJOR.value] += 1 - def write(self): - match self.config_file_type: - case 'json': - self.__write_json() - case 'gradle': - self.__write_gradle() - case _: - raise Exception(f'The file type "{self.config_file_type}" is not implemented') - - def __write_json(self): - with open(self.config_file_path, 'r+') as json_file: - json_data = json.load(json_file) - json_data['version'] = self.get() - json_file.seek(0) - json.dump(json_data, json_file, indent=4) - json_file.truncate() - - def __write_gradle(self): - with open(self.config_file_path, 'r+') as gradle_file: - gradle_contents = gradle_file.read() - version_substitute = re.sub("\nversion = .*\n", f'\nversion = "{self.get()}"\n', gradle_contents) - gradle_file.seek(0) - gradle_file.write(version_substitute) - gradle_file.truncate() - def get(self) -> str: version_string = ".".join([str(x) for x in self.version]) if self.is_snapshot: diff --git a/file_handlers.py b/file_handlers.py new file mode 100644 index 0000000..c7fec26 --- /dev/null +++ b/file_handlers.py @@ -0,0 +1,81 @@ +from abc import ABC, abstractmethod +import json +import re + +class FileHandler(ABC): + + @classmethod + def from_file_path(cls, file_path): + config_file_type = file_path.split('.')[-1] + match config_file_type: + case 'json': + file_handler = JsonFileHandler() + case 'gradle': + file_handler = GradleFileHandler() + case _: + raise Exception(f'The file type "{config_file_type}" is not implemented') + + file_handler.config_file_path = file_path + file_handler.config_file_type = config_file_type + return file_handler + + @abstractmethod + def parse(self) -> tuple[list[int], bool]: + pass + + @abstractmethod + def write(self): + pass + +class JsonFileHandler(FileHandler): + + def parse(self) -> tuple[list[int], bool]: + with open(self.config_file_path, 'r') as json_file: + json_version = json.load(json_file)['version'] + is_snapshot = False + if '-SNAPSHOT' in json_version: + is_snapshot = True + json_version = json_version.replace('-SNAPSHOT', '') + version = [int(x) for x in json_version.split('.')] + return version, is_snapshot + + def write(self): + with open(self.config_file_path, 'r+') as json_file: + json_data = json.load(json_file) + json_data['version'] = self.get() + json_file.seek(0) + json.dump(json_data, json_file, indent=4) + json_file.truncate() + + +class GradleFileHandler(FileHandler): + + def parse(self) -> tuple[list[int], bool]: + with open(self.config_file_path, 'r') as gradle_file: + contents = gradle_file.read() + version_line = re.search("\nversion = .*\n", contents) + if version_line is None: + raise Exception("Version not found in gradle file") + + version_line = version_line.group() + version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + if version_string is None: + raise Exception("Version not found in gradle file") + + version_string = version_string.group() + if '-SNAPSHOT' in version_string: + self.is_snapshot = True + version_string = version_string.replace('-SNAPSHOT', '') + + self.version = [int(x) for x in version_string.split('.')] + + def write(self): + with open(self.config_file_path, 'r+') as gradle_file: + gradle_contents = gradle_file.read() + version_substitute = re.sub("\nversion = .*\n", f'\nversion = "{self.get()}"\n', gradle_contents) + gradle_file.seek(0) + gradle_file.write(version_substitute) + gradle_file.truncate() + +a = FileHandler.from_file_path('build.gradle') +print(type(a)) \ No newline at end of file From fd84063edc73aede317019dead5549aef2ba6b2e Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 9 Feb 2023 12:52:58 +0100 Subject: [PATCH 014/243] Fix file handling and version creation --- devops_test.py | 15 ++++++++++++--- file_handlers.py | 10 +++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/devops_test.py b/devops_test.py index 1a2806b..ec0b2ab 100644 --- a/devops_test.py +++ b/devops_test.py @@ -4,8 +4,12 @@ from file_handlers import FileHandler def init_project(): # validate_values() version = Version.from_file('build.gradle') - version.increment(ReleaseLevel.SNAPSHOT) - version.write() + v2 = Version.from_file('package.json') + print(type(version.file_handler)) + print(type(v2.file_handler)) + # version.increment(ReleaseLevel.SNAPSHOT) + # version.write() + print(version.get()) def prepare_release(): pass @@ -24,11 +28,16 @@ class Version(): def __init__(self, version, is_snapshot): self.version = version self.is_snapshot = is_snapshot + self.file_handler = None @classmethod def from_file(cls, config_file_path): file_handler = FileHandler.from_file_path(config_file_path) - return cls(file_handler.parse()) + version, is_snapshot = file_handler.parse() + inst = cls(version, is_snapshot) + inst.file_handler = file_handler + + return inst def increment(self, level: ReleaseLevel): self.is_snapshot = False diff --git a/file_handlers.py b/file_handlers.py index c7fec26..3543833 100644 --- a/file_handlers.py +++ b/file_handlers.py @@ -63,11 +63,14 @@ class GradleFileHandler(FileHandler): raise Exception("Version not found in gradle file") version_string = version_string.group() + is_snapshot = False if '-SNAPSHOT' in version_string: - self.is_snapshot = True + is_snapshot = True version_string = version_string.replace('-SNAPSHOT', '') - self.version = [int(x) for x in version_string.split('.')] + version = [int(x) for x in version_string.split('.')] + + return version, is_snapshot def write(self): with open(self.config_file_path, 'r+') as gradle_file: @@ -76,6 +79,3 @@ class GradleFileHandler(FileHandler): gradle_file.seek(0) gradle_file.write(version_substitute) gradle_file.truncate() - -a = FileHandler.from_file_path('build.gradle') -print(type(a)) \ No newline at end of file From ca3db5efe9e01ee66d5b16c76139eb01d965af24 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Feb 2023 13:20:42 +0100 Subject: [PATCH 015/243] WIP Add tests for gradle and json --- build.gradle | 2 +- devops_test.py | 15 +++++++++------ file_handlers.py | 10 +++++----- test/config.gradle | 2 ++ test/config.json | 3 +++ test/{test_version.py => test_version_class.py} | 13 ++++++++++++- 6 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 test/config.gradle create mode 100644 test/config.json rename test/{test_version.py => test_version_class.py} (76%) diff --git a/build.gradle b/build.gradle index 6a8577e..6fa64a8 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ apply plugin: "kotlinx-serialization" group = "org.domaindrivenarchitecture.provs" -version = "0.17.1" +version = "0.17.1-SNAPSHOT" repositories { mavenCentral() diff --git a/devops_test.py b/devops_test.py index ec0b2ab..17b9220 100644 --- a/devops_test.py +++ b/devops_test.py @@ -3,12 +3,9 @@ from file_handlers import FileHandler def init_project(): # validate_values() - version = Version.from_file('build.gradle') - v2 = Version.from_file('package.json') - print(type(version.file_handler)) - print(type(v2.file_handler)) - # version.increment(ReleaseLevel.SNAPSHOT) - # version.write() + version = Version.from_file('build.gradle') + version.increment(ReleaseLevel.SNAPSHOT) + version.to_file() print(version.get()) def prepare_release(): @@ -39,6 +36,12 @@ class Version(): return inst + def to_file(self): + if self.file_handler is None: + raise Exception('Version was not created by from_file method.') + else: + self.file_handler.write(self.get()) + def increment(self, level: ReleaseLevel): self.is_snapshot = False match level: diff --git a/file_handlers.py b/file_handlers.py index 3543833..fe6495f 100644 --- a/file_handlers.py +++ b/file_handlers.py @@ -24,7 +24,7 @@ class FileHandler(ABC): pass @abstractmethod - def write(self): + def write(self, version_string): pass class JsonFileHandler(FileHandler): @@ -39,10 +39,10 @@ class JsonFileHandler(FileHandler): version = [int(x) for x in json_version.split('.')] return version, is_snapshot - def write(self): + def write(self, version_string): with open(self.config_file_path, 'r+') as json_file: json_data = json.load(json_file) - json_data['version'] = self.get() + json_data['version'] = version_string json_file.seek(0) json.dump(json_data, json_file, indent=4) json_file.truncate() @@ -72,10 +72,10 @@ class GradleFileHandler(FileHandler): return version, is_snapshot - def write(self): + def write(self, version_string): with open(self.config_file_path, 'r+') as gradle_file: gradle_contents = gradle_file.read() - version_substitute = re.sub("\nversion = .*\n", f'\nversion = "{self.get()}"\n', gradle_contents) + version_substitute = re.sub("\nversion = .*\n", f'\nversion = "{version_string}"\n', gradle_contents) gradle_file.seek(0) gradle_file.write(version_substitute) gradle_file.truncate() diff --git a/test/config.gradle b/test/config.gradle new file mode 100644 index 0000000..9123ba2 --- /dev/null +++ b/test/config.gradle @@ -0,0 +1,2 @@ + +version = "12.4.678" \ No newline at end of file diff --git a/test/config.json b/test/config.json new file mode 100644 index 0000000..f407ec1 --- /dev/null +++ b/test/config.json @@ -0,0 +1,3 @@ +{ + "version": "123.123.456" +} \ No newline at end of file diff --git a/test/test_version.py b/test/test_version_class.py similarity index 76% rename from test/test_version.py rename to test/test_version_class.py index d72416b..f90446c 100644 --- a/test/test_version.py +++ b/test/test_version_class.py @@ -22,4 +22,15 @@ def test_version(): assert version.get() == "1.3.0" version.increment(ReleaseLevel.MAJOR) - assert version.get() == "2.0.0" \ No newline at end of file + assert version.get() == "2.0.0" + + +def test_gradle(): + # validate_values() + version = Version.from_file('build.gradle') + + + + version.increment(ReleaseLevel.SNAPSHOT) + version.to_file() + print(version.get()) \ No newline at end of file From 86c66dc7f7818ace98014dd098d4081ed5ecef58 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 9 Feb 2023 13:33:56 +0100 Subject: [PATCH 016/243] Implement tests for file handling --- file_handlers.py | 6 +++--- test/config.gradle | 2 +- test/test_version_class.py | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/file_handlers.py b/file_handlers.py index fe6495f..8248e8a 100644 --- a/file_handlers.py +++ b/file_handlers.py @@ -6,11 +6,11 @@ class FileHandler(ABC): @classmethod def from_file_path(cls, file_path): - config_file_type = file_path.split('.')[-1] + config_file_type = file_path.suffix match config_file_type: - case 'json': + case '.json': file_handler = JsonFileHandler() - case 'gradle': + case '.gradle': file_handler = GradleFileHandler() case _: raise Exception(f'The file type "{config_file_type}" is not implemented') diff --git a/test/config.gradle b/test/config.gradle index 9123ba2..5c6a467 100644 --- a/test/config.gradle +++ b/test/config.gradle @@ -1,2 +1,2 @@ -version = "12.4.678" \ No newline at end of file +version = "12.4.678" diff --git a/test/test_version_class.py b/test/test_version_class.py index f90446c..55b6df2 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -1,4 +1,5 @@ from devops_test import Version, ReleaseLevel +from pathlib import Path def test_version(): version = Version([1, 2, 3], False) @@ -25,12 +26,36 @@ def test_version(): assert version.get() == "2.0.0" -def test_gradle(): - # validate_values() - version = Version.from_file('build.gradle') - +def test_gradle(tmp_path): + # init + file_name = 'config.gradle' + with open(f'test/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + f = tmp_path / file_name + f.write_text(contents) + # test + version = Version.from_file(f) version.increment(ReleaseLevel.SNAPSHOT) version.to_file() - print(version.get()) \ No newline at end of file + + # check + assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() + +def test_json(tmp_path): + # init + file_name = 'config.json' + with open(f'test/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + version = Version.from_file(f) + version.increment(ReleaseLevel.SNAPSHOT) + version.to_file() + + # check + assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() \ No newline at end of file From a4c8f8934a53bb38687c311c0fc323a4a632c398 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Feb 2023 13:56:45 +0100 Subject: [PATCH 017/243] Implement clj file handler --- file_handlers.py | 42 ++++++++++++++++++++++++++++++++-- project.clj | 46 ++++++++++++++++++++++++++++++++++++++ test/config.clj | 5 +++++ test/test_version_class.py | 19 +++++++++++++++- 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 project.clj create mode 100644 test/config.clj diff --git a/file_handlers.py b/file_handlers.py index 8248e8a..6c0813a 100644 --- a/file_handlers.py +++ b/file_handlers.py @@ -12,6 +12,8 @@ class FileHandler(ABC): file_handler = JsonFileHandler() case '.gradle': file_handler = GradleFileHandler() + case '.clj': + file_handler = CljFileHandler() case _: raise Exception(f'The file type "{config_file_type}" is not implemented') @@ -54,13 +56,14 @@ class GradleFileHandler(FileHandler): with open(self.config_file_path, 'r') as gradle_file: contents = gradle_file.read() version_line = re.search("\nversion = .*\n", contents) + exception = Exception("Version not found in gradle file") if version_line is None: - raise Exception("Version not found in gradle file") + raise exception version_line = version_line.group() version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) if version_string is None: - raise Exception("Version not found in gradle file") + raise exception version_string = version_string.group() is_snapshot = False @@ -79,3 +82,38 @@ class GradleFileHandler(FileHandler): gradle_file.seek(0) gradle_file.write(version_substitute) gradle_file.truncate() + +class CljFileHandler(FileHandler): + + def parse(self) -> tuple[list[int], bool]: + with open(self.config_file_path, 'r') as clj_file: + contents = clj_file.read() + version_line = re.search("^\\(defproject .*\n", contents) + exception = Exception("Version not found in clj file") + if version_line is None: + raise exception + + version_line = version_line.group() + version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + if version_string is None: + raise exception + + version_string = version_string.group() + is_snapshot = False + if '-SNAPSHOT' in version_string: + is_snapshot = True + version_string = version_string.replace('-SNAPSHOT', '') + + version = [int(x) for x in version_string.split('.')] + + return version, is_snapshot + + def write(self, version_string): + with open(self.config_file_path, 'r+') as clj_file: + clj_first = clj_file.readline() + clj_rest = clj_file.read() + version_substitute = re.sub('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', f'"{version_string}"\n', clj_first) + clj_file.seek(0) + clj_file.write(version_substitute) + clj_file.write(clj_rest) + clj_file.truncate() \ No newline at end of file diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..6e14c1f --- /dev/null +++ b/project.clj @@ -0,0 +1,46 @@ +(defproject org.domaindrivenarchitecture/c4k-website "1.1.3-SNAPSHOT" + :description "website c4k-installation package" + :url "https://domaindrivenarchitecture.org" + :license {:name "Apache License, Version 2.0" + :url "https://www.apache.org/licenses/LICENSE-2.0.html"} + :dependencies [[org.clojure/clojure "1.11.1"] + [org.clojure/tools.reader "1.3.6"] + [org.domaindrivenarchitecture/c4k-common-clj "5.0.1"] + [hickory "0.7.1"]] + :target-path "target/%s/" + :source-paths ["src/main/cljc" + "src/main/clj"] + :resource-paths ["src/main/resources"] + :repositories [["snapshots" :clojars] + ["releases" :clojars]] + :deploy-repositories [["snapshots" {:sign-releases false :url "https://clojars.org/repo"}] + ["releases" {:sign-releases false :url "https://clojars.org/repo"}]] + :profiles {:test {:test-paths ["src/test/cljc"] + :resource-paths ["src/test/resources"] + :dependencies [[dda/data-test "0.1.1"]]} + :dev {:plugins [[lein-shell "0.5.0"]]} + :uberjar {:aot :all + :main dda.c4k-website.uberjar + :uberjar-name "c4k-website-standalone.jar" + :dependencies [[org.clojure/tools.cli "1.0.214"] + [ch.qos.logback/logback-classic "1.4.5" + :exclusions [com.sun.mail/javax.mail]] + [org.slf4j/jcl-over-slf4j "2.0.6"]]}} + :release-tasks [["test"] + ["vcs" "assert-committed"] + ["change" "version" "leiningen.release/bump-version" "release"] + ["vcs" "commit"] + ["vcs" "tag" "v" "--no-sign"] + ["change" "version" "leiningen.release/bump-version"]] + :aliases {"native" ["shell" + "native-image" + "--report-unsupported-elements-at-runtime" + "--initialize-at-build-time" + "-jar" "target/uberjar/c4k-website-standalone.jar" + "-H:ResourceConfigurationFiles=graalvm-resource-config.json" + "-H:Log=registerResource" + "-H:Name=target/graalvm/${:name}"] + "inst" ["shell" + "sh" + "-c" + "lein uberjar && sudo install -m=755 target/uberjar/c4k-website-standalone.jar /usr/local/bin/c4k-website-standalone.jar"]}) diff --git a/test/config.clj b/test/config.clj new file mode 100644 index 0000000..c329c34 --- /dev/null +++ b/test/config.clj @@ -0,0 +1,5 @@ +(defproject org.domaindrivenarchitecture/c4k-website "1.1.3" + :description "website c4k-installation package" + :url "https://domaindrivenarchitecture.org" + :license {:name "Apache License, Version 2.0" + :url "https://www.apache.org/licenses/LICENSE-2.0.html"}) \ No newline at end of file diff --git a/test/test_version_class.py b/test/test_version_class.py index 55b6df2..9811e09 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -58,4 +58,21 @@ def test_json(tmp_path): version.to_file() # check - assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() \ No newline at end of file + assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() + +def test_clj(tmp_path): + # init + file_name = 'config.clj' + with open(f'test/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + version = Version.from_file(f) + version.increment(ReleaseLevel.SNAPSHOT) + version.to_file() + + # check + assert '1.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file From 9d1726051dfa212974cf46d6bbf8d1d35d9c0ef2 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 9 Feb 2023 14:32:55 +0100 Subject: [PATCH 018/243] Implement python file handler --- file_handlers.py | 46 +++++++++++++++++++--- test/config.py | 78 ++++++++++++++++++++++++++++++++++++++ test/test_version_class.py | 21 +++++++++- 3 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 test/config.py diff --git a/file_handlers.py b/file_handlers.py index 6c0813a..e3f627d 100644 --- a/file_handlers.py +++ b/file_handlers.py @@ -13,7 +13,9 @@ class FileHandler(ABC): case '.gradle': file_handler = GradleFileHandler() case '.clj': - file_handler = CljFileHandler() + file_handler = ClojureFileHandler() + case '.py': + file_handler = PythonFileHandler() case _: raise Exception(f'The file type "{config_file_type}" is not implemented') @@ -55,7 +57,7 @@ class GradleFileHandler(FileHandler): def parse(self) -> tuple[list[int], bool]: with open(self.config_file_path, 'r') as gradle_file: contents = gradle_file.read() - version_line = re.search("\nversion = .*\n", contents) + version_line = re.search("\nversion = .*", contents) exception = Exception("Version not found in gradle file") if version_line is None: raise exception @@ -77,13 +79,47 @@ class GradleFileHandler(FileHandler): def write(self, version_string): with open(self.config_file_path, 'r+') as gradle_file: - gradle_contents = gradle_file.read() - version_substitute = re.sub("\nversion = .*\n", f'\nversion = "{version_string}"\n', gradle_contents) + contents = gradle_file.read() + version_substitute = re.sub('\nversion = "[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?"', f'\nversion = "{version_string}"', contents) gradle_file.seek(0) gradle_file.write(version_substitute) gradle_file.truncate() -class CljFileHandler(FileHandler): + +class PythonFileHandler(FileHandler): + + def parse(self) -> tuple[list[int], bool]: + with open(self.config_file_path, 'r') as python_file: + contents = python_file.read() + version_line = re.search("\nversion = .*\n", contents) + exception = Exception("Version not found in gradle file") + if version_line is None: + raise exception + + version_line = version_line.group() + version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + if version_string is None: + raise exception + + version_string = version_string.group() + is_snapshot = False + if '-SNAPSHOT' in version_string: + is_snapshot = True + version_string = version_string.replace('-SNAPSHOT', '') + + version = [int(x) for x in version_string.split('.')] + + return version, is_snapshot + + def write(self, version_string): + with open(self.config_file_path, 'r+') as python_file: + contents = python_file.read() + version_substitute = re.sub('\nversion = "[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?"', f'\nversion = "{version_string}"', contents) + python_file.seek(0) + python_file.write(version_substitute) + python_file.truncate() + +class ClojureFileHandler(FileHandler): def parse(self) -> tuple[list[int], bool]: with open(self.config_file_path, 'r') as clj_file: diff --git a/test/config.py b/test/config.py new file mode 100644 index 0000000..bb47bcd --- /dev/null +++ b/test/config.py @@ -0,0 +1,78 @@ +# dda_devops_build +# Copyright 2019 meissa GmbH. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from pybuilder.core import init, use_plugin, Author + +use_plugin("python.core") +use_plugin("copy_resources") +use_plugin("filter_resources") +#use_plugin("python.unittest") +#use_plugin("python.coverage") +use_plugin("python.distutils") + +#use_plugin("python.install_dependencies") + +default_task = "publish" + +name = "ddadevops" +version = "3.1.3" +summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud" +description = __doc__ +authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")] +url = "https://github.com/DomainDrivenArchitecture/dda-devops-build" +requires_python = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4" # CHECK IF NEW VERSION EXISTS +license = "Apache Software License" + +@init +def initialize(project): + #project.build_depends_on('mockito') + #project.build_depends_on('unittest-xml-reporting') + + project.set_property("verbose", True) + project.get_property("filter_resources_glob").append("main/python/ddadevops/__init__.py") + #project.set_property("dir_source_unittest_python", "src/unittest/python") + + project.set_property("copy_resources_target", "$dir_dist/ddadevops") + project.get_property("copy_resources_glob").append("LICENSE") + project.get_property("copy_resources_glob").append("src/main/resources/terraform/*") + project.get_property("copy_resources_glob").append("src/main/resources/docker/image/resources/*") + project.include_file("ddadevops", "LICENSE") + project.include_file("ddadevops", "src/main/resources/terraform/*") + project.include_file("ddadevops", "src/main/resources/docker/image/resources/*") + + #project.set_property('distutils_upload_sign', True) + #project.set_property('distutils_upload_sign_identity', '') + project.set_property("distutils_readme_description", True) + project.set_property("distutils_description_overwrite", True) + project.set_property("distutils_classifiers", [ + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Operating System :: POSIX :: Linux', + 'Operating System :: OS Independent', + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Topic :: Software Development :: Build Tools', + 'Topic :: Software Development :: Quality Assurance', + 'Topic :: Software Development :: Testing' + ]) diff --git a/test/test_version_class.py b/test/test_version_class.py index 9811e09..ddf38c3 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -60,7 +60,7 @@ def test_json(tmp_path): # check assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() -def test_clj(tmp_path): +def test_clojure(tmp_path): # init file_name = 'config.clj' with open(f'test/{file_name}', 'r') as gradle_file: @@ -75,4 +75,21 @@ def test_clj(tmp_path): version.to_file() # check - assert '1.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file + assert '1.1.3-SNAPSHOT' in f.read_text() + +def test_python(tmp_path): + # init + file_name = 'config.py' + with open(f'test/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + version = Version.from_file(f) + version.increment(ReleaseLevel.SNAPSHOT) + version.to_file() + + # check + assert '3.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file From 3659b888f8590c5487ff6877c639dc7f33cba8bd Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 9 Feb 2023 17:09:42 +0100 Subject: [PATCH 019/243] Move test resources to distinct folder --- test/{ => resources}/config.clj | 0 test/{ => resources}/config.gradle | 0 test/{ => resources}/config.json | 0 test/{ => resources}/config.py | 0 test/test_version_class.py | 8 ++++---- 5 files changed, 4 insertions(+), 4 deletions(-) rename test/{ => resources}/config.clj (100%) rename test/{ => resources}/config.gradle (100%) rename test/{ => resources}/config.json (100%) rename test/{ => resources}/config.py (100%) diff --git a/test/config.clj b/test/resources/config.clj similarity index 100% rename from test/config.clj rename to test/resources/config.clj diff --git a/test/config.gradle b/test/resources/config.gradle similarity index 100% rename from test/config.gradle rename to test/resources/config.gradle diff --git a/test/config.json b/test/resources/config.json similarity index 100% rename from test/config.json rename to test/resources/config.json diff --git a/test/config.py b/test/resources/config.py similarity index 100% rename from test/config.py rename to test/resources/config.py diff --git a/test/test_version_class.py b/test/test_version_class.py index ddf38c3..5c7b49a 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -29,7 +29,7 @@ def test_version(): def test_gradle(tmp_path): # init file_name = 'config.gradle' - with open(f'test/{file_name}', 'r') as gradle_file: + with open(f'test/resources/{file_name}', 'r') as gradle_file: contents = gradle_file.read() f = tmp_path / file_name @@ -46,7 +46,7 @@ def test_gradle(tmp_path): def test_json(tmp_path): # init file_name = 'config.json' - with open(f'test/{file_name}', 'r') as gradle_file: + with open(f'test/resources/{file_name}', 'r') as gradle_file: contents = gradle_file.read() f = tmp_path / file_name @@ -63,7 +63,7 @@ def test_json(tmp_path): def test_clojure(tmp_path): # init file_name = 'config.clj' - with open(f'test/{file_name}', 'r') as gradle_file: + with open(f'test/resources/{file_name}', 'r') as gradle_file: contents = gradle_file.read() f = tmp_path / file_name @@ -80,7 +80,7 @@ def test_clojure(tmp_path): def test_python(tmp_path): # init file_name = 'config.py' - with open(f'test/{file_name}', 'r') as gradle_file: + with open(f'test/resources/{file_name}', 'r') as gradle_file: contents = gradle_file.read() f = tmp_path / file_name From 2f0bd5c211d59bc7d67d6d70671924130c3e2f7c Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 9 Feb 2023 17:20:58 +0100 Subject: [PATCH 020/243] Remove old config files --- build.gradle | 6352 -------------------------------------------------- package.json | 33 - project.clj | 46 - 3 files changed, 6431 deletions(-) delete mode 100644 build.gradle delete mode 100644 package.json delete mode 100644 project.clj diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 6fa64a8..0000000 --- a/build.gradle +++ /dev/null @@ -1,6352 +0,0 @@ -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - -repositories { - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -repositories { - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -repositories { - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -repositories { - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -repositories { - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -repositories { - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - mavenCentral() -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" -} - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} -buildscript { - ext.kotlin_version = "1.7.0" - ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID - - repositories { mavenCentral() } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -apply plugin: "org.jetbrains.kotlin.jvm" -apply plugin: "java-library" -apply plugin: "java-test-fixtures" -apply plugin: "maven-publish" -apply plugin: "kotlinx-serialization" - - -group = "org.domaindrivenarchitecture.provs" -version = "0.17.1-SNAPSHOT" - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(11) - } -} - - -test { - // set properties for the tests - def propertiesForTests = ["testdockerwithoutsudo"] - for (def prop : propertiesForTests) { - def value = System.getProperty(prop) - if (value != null) { - systemProperty prop, value - } - } - - useJUnitPlatform { - def excludedTags = System.getProperty("excludeTags") - if (System.getProperty("excludeTags") != null) { - excludeTags(excludedTags.split(",")) - } - if (System.getenv("CI_JOB_TOKEN") != null) { - excludeTags("containernonci") - } - } -} - -compileJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars" -compileTestJava.options.debugOptions.debugLevel = "source,lines,vars" - -// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc -java { - withSourcesJar() - withJavadocJar() -} - - -dependencies { - - api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2") - api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4") - - api('com.charleskorn.kaml:kaml:0.43.0') - - api("org.slf4j:slf4j-api:1.7.36") - api('ch.qos.logback:logback-classic:1.2.11') - api('ch.qos.logback:logback-core:1.2.11') - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") - implementation("com.hierynomus:sshj:0.32.0") - - implementation("aws.sdk.kotlin:s3:0.17.1-beta") - - testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2") - testFixturesApi('io.mockk:mockk:1.12.3') - - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") -} - - -task uberjarDesktop(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" - } - archiveFileName = "provs-desktop.jar" -} - - -task uberjarServer(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.server.application.ApplicationKt" - } - archiveFileName = "provs-server.jar" -} - - -task uberjarSyspec(type: Jar) { - - from sourceSets.main.output - - dependsOn configurations.runtimeClasspath - from { - configurations.runtimeClasspath.findAll { it.name.endsWith("jar") }.collect { zipTree(it) } - } { - duplicatesStrategy(DuplicatesStrategy.EXCLUDE) - exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" - } - - manifest { - attributes "Implementation-Title": "Uberjar of provs", - "Implementation-Version": project.version, - "Main-Class": "org.domaindrivenarchitecture.provs.syspec.application.ApplicationKt" - } - archiveFileName = "provs-syspec.jar" -} -def projectRoot = rootProject.projectDir - - -// copy jar to /usr/local/bin and make it executable -// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper) -task installlocally { - dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec) - doLast { - exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-server.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-desktop.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo cp $projectRoot/build/libs/provs-syspec.jar /usr/local/bin/") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-server.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-desktop.jar") } - exec { commandLine("sh", "-c", "sudo chmod 755 /usr/local/bin/provs-syspec.jar") } - } -} - -task sourceJar(type: Jar, dependsOn: classes) { - from sourceSets.main.allSource - archiveClassifier.set("sources") -} - - -publishing { - publications { - library(MavenPublication) { - from components.java - } - } - repositories { - if (System.getenv("CI_JOB_TOKEN") != null) { - // see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html - maven { - url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = "Job-Token" - value = System.getenv("CI_JOB_TOKEN") - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - mavenLocal() - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index bbf2883..0000000 --- a/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "dummy", - "description": "Generate c4k yaml for a jitsi deployment.", - "author": "meissa GmbH", - "version": "3.0.0", - "homepage": "https://gitlab.com/domaindrivenarchitecture/c4k-jitsi#readme", - "repository": "https://www.npmjs.com/package/c4k-jitsi", - "license": "APACHE2", - "main": "c4k-jitsi.js", - "bin": { - "c4k-jitsi": "./c4k-jitsi.js" - }, - "keywords": [ - "cljs", - "jitsi", - "k8s", - "c4k", - "deployment", - "yaml", - "convention4kubernetes" - ], - "bugs": { - "url": "https://gitlab.com/domaindrivenarchitecture/c4k-jitsi/issues" - }, - "dependencies": { - "js-base64": "^3.6.1", - "js-yaml": "^4.0.0" - }, - "devDependencies": { - "shadow-cljs": "^2.11.18", - "source-map-support": "^0.5.19" - } -} \ No newline at end of file diff --git a/project.clj b/project.clj deleted file mode 100644 index 6e14c1f..0000000 --- a/project.clj +++ /dev/null @@ -1,46 +0,0 @@ -(defproject org.domaindrivenarchitecture/c4k-website "1.1.3-SNAPSHOT" - :description "website c4k-installation package" - :url "https://domaindrivenarchitecture.org" - :license {:name "Apache License, Version 2.0" - :url "https://www.apache.org/licenses/LICENSE-2.0.html"} - :dependencies [[org.clojure/clojure "1.11.1"] - [org.clojure/tools.reader "1.3.6"] - [org.domaindrivenarchitecture/c4k-common-clj "5.0.1"] - [hickory "0.7.1"]] - :target-path "target/%s/" - :source-paths ["src/main/cljc" - "src/main/clj"] - :resource-paths ["src/main/resources"] - :repositories [["snapshots" :clojars] - ["releases" :clojars]] - :deploy-repositories [["snapshots" {:sign-releases false :url "https://clojars.org/repo"}] - ["releases" {:sign-releases false :url "https://clojars.org/repo"}]] - :profiles {:test {:test-paths ["src/test/cljc"] - :resource-paths ["src/test/resources"] - :dependencies [[dda/data-test "0.1.1"]]} - :dev {:plugins [[lein-shell "0.5.0"]]} - :uberjar {:aot :all - :main dda.c4k-website.uberjar - :uberjar-name "c4k-website-standalone.jar" - :dependencies [[org.clojure/tools.cli "1.0.214"] - [ch.qos.logback/logback-classic "1.4.5" - :exclusions [com.sun.mail/javax.mail]] - [org.slf4j/jcl-over-slf4j "2.0.6"]]}} - :release-tasks [["test"] - ["vcs" "assert-committed"] - ["change" "version" "leiningen.release/bump-version" "release"] - ["vcs" "commit"] - ["vcs" "tag" "v" "--no-sign"] - ["change" "version" "leiningen.release/bump-version"]] - :aliases {"native" ["shell" - "native-image" - "--report-unsupported-elements-at-runtime" - "--initialize-at-build-time" - "-jar" "target/uberjar/c4k-website-standalone.jar" - "-H:ResourceConfigurationFiles=graalvm-resource-config.json" - "-H:Log=registerResource" - "-H:Name=target/graalvm/${:name}"] - "inst" ["shell" - "sh" - "-c" - "lein uberjar && sudo install -m=755 target/uberjar/c4k-website-standalone.jar /usr/local/bin/c4k-website-standalone.jar"]}) From 3661e47bfff794ca4b6de063775da9cf67dbc00d Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 16 Feb 2023 14:21:27 +0100 Subject: [PATCH 021/243] WIP create ReleaseMixin --- .gitignore | 5 ++- build.py | 2 +- release_mixin.py | 45 +++++++++++++++++++ ...core.classpath.extract-native-dependencies | 1 + devops_test.py => version.py | 0 5 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 release_mixin.py create mode 100644 target/default+test/stale/leiningen.core.classpath.extract-native-dependencies rename devops_test.py => version.py (100%) diff --git a/.gitignore b/.gitignore index a9c5fbc..7f3a111 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,7 @@ # Go workspace file go.work -__pycache__ \ No newline at end of file +__pycache__ + +.clj-kondo/ +.lsp/ \ No newline at end of file diff --git a/build.py b/build.py index 3efe26d..183e28e 100644 --- a/build.py +++ b/build.py @@ -1,6 +1,6 @@ from pybuilder.core import task, init from ddadevops import * -from devops_test import * +from version import * def main(): diff --git a/release_mixin.py b/release_mixin.py new file mode 100644 index 0000000..6d709f7 --- /dev/null +++ b/release_mixin.py @@ -0,0 +1,45 @@ + +from ddadevops import DevopsBuild +from ddadevops import execute +from ddadevops import gopass_field_from_path, gopass_password_from_path +from version import Version + + +def add_release_mixin_config(config, release_type, commit, file): + config.update({'ReleaseMixin': + {'release_type': release_type, + 'commit': commit, + 'file': file}}) + return config + + +class ReleaseMixin(DevopsBuild): + + def __init__(self, project, config): + super().__init__(project, config) + release_mixin_config = config['ReleaseMixin'] + self.release_type = release_mixin_config['release_type4'] + self.commit = release_mixin_config['commit'] + self.file = release_mixin_config['file'] + + def read_commit_message(self): + pass + + def calculate_release_type(self): + pass + + def get_version(self): + + return Version.from_file(self.file) + + def create_release_version(self): + pass + + def create_bump_version(self): + pass + + + + + + diff --git a/target/default+test/stale/leiningen.core.classpath.extract-native-dependencies b/target/default+test/stale/leiningen.core.classpath.extract-native-dependencies new file mode 100644 index 0000000..03ba243 --- /dev/null +++ b/target/default+test/stale/leiningen.core.classpath.extract-native-dependencies @@ -0,0 +1 @@ +[{:dependencies {args4j {:vsn "2.0.26", :native-prefix nil}, org.clojure/data.json {:vsn "0.2.6", :native-prefix nil}, org.clojure/clojure {:vsn "1.11.1", :native-prefix nil}, org.clojure/core.specs.alpha {:vsn "0.2.62", :native-prefix nil}, org.clojure/spec.alpha {:vsn "0.3.218", :native-prefix nil}, dda/data-test {:vsn "0.1.1", :native-prefix nil}, quoin {:vsn "0.1.2", :native-prefix nil}, org.domaindrivenarchitecture/c4k-common-clj {:vsn "5.0.1", :native-prefix nil}, hickory {:vsn "0.7.1", :native-prefix nil}, org.clojure/google-closure-library {:vsn "0.0-20160609-f42b4a24", :native-prefix nil}, org.clojure/clojurescript {:vsn "1.9.293", :native-prefix nil}, aero {:vsn "1.1.6", :native-prefix nil}, orchestra {:vsn "2021.01.01-1", :native-prefix nil}, org.flatland/ordered {:vsn "1.5.9", :native-prefix nil}, com.google.jsinterop/jsinterop-annotations {:vsn "1.0.0", :native-prefix nil}, org.yaml/snakeyaml {:vsn "1.33", :native-prefix nil}, org.mozilla/rhino {:vsn "1.7R5", :native-prefix nil}, org.clojure/google-closure-library-third-party {:vsn "0.0-20160609-f42b4a24", :native-prefix nil}, pjstadig/humane-test-output {:vsn "0.11.0", :native-prefix nil}, com.google.javascript/closure-compiler-externs {:vsn "v20160911", :native-prefix nil}, clojure-complete {:vsn "0.2.5", :native-prefix nil}, com.google.guava/guava {:vsn "19.0", :native-prefix nil}, viebel/codox-klipse-theme {:vsn "0.0.1", :native-prefix nil}, clj-commons/clj-yaml {:vsn "1.0.26", :native-prefix nil}, prismatic/schema {:vsn "1.1.10", :native-prefix nil}, org.clojure/tools.reader {:vsn "1.3.6", :native-prefix nil}, nrepl {:vsn "0.6.0", :native-prefix nil}, org.jsoup/jsoup {:vsn "1.9.2", :native-prefix nil}, expound {:vsn "0.9.0", :native-prefix nil}, com.google.javascript/closure-compiler-unshaded {:vsn "v20160911", :native-prefix nil}, com.google.protobuf/protobuf-java {:vsn "2.5.0", :native-prefix nil}, com.google.code.findbugs/jsr305 {:vsn "1.3.9", :native-prefix nil}, com.google.code.gson/gson {:vsn "2.2.4", :native-prefix nil}}, :native-path "target/default+test/native"} {:native-path "target/default+test/native", :dependencies {args4j {:vsn "2.0.26", :native-prefix nil, :native? false}, org.clojure/data.json {:vsn "0.2.6", :native-prefix nil, :native? false}, org.clojure/clojure {:vsn "1.11.1", :native-prefix nil, :native? false}, org.clojure/core.specs.alpha {:vsn "0.2.62", :native-prefix nil, :native? false}, org.clojure/spec.alpha {:vsn "0.3.218", :native-prefix nil, :native? false}, dda/data-test {:vsn "0.1.1", :native-prefix nil, :native? false}, quoin {:vsn "0.1.2", :native-prefix nil, :native? false}, org.domaindrivenarchitecture/c4k-common-clj {:vsn "5.0.1", :native-prefix nil, :native? false}, hickory {:vsn "0.7.1", :native-prefix nil, :native? false}, org.clojure/google-closure-library {:vsn "0.0-20160609-f42b4a24", :native-prefix nil, :native? false}, org.clojure/clojurescript {:vsn "1.9.293", :native-prefix nil, :native? false}, aero {:vsn "1.1.6", :native-prefix nil, :native? false}, orchestra {:vsn "2021.01.01-1", :native-prefix nil, :native? false}, org.flatland/ordered {:vsn "1.5.9", :native-prefix nil, :native? false}, com.google.jsinterop/jsinterop-annotations {:vsn "1.0.0", :native-prefix nil, :native? false}, org.yaml/snakeyaml {:vsn "1.33", :native-prefix nil, :native? false}, org.mozilla/rhino {:vsn "1.7R5", :native-prefix nil, :native? false}, org.clojure/google-closure-library-third-party {:vsn "0.0-20160609-f42b4a24", :native-prefix nil, :native? false}, pjstadig/humane-test-output {:vsn "0.11.0", :native-prefix nil, :native? false}, com.google.javascript/closure-compiler-externs {:vsn "v20160911", :native-prefix nil, :native? false}, clojure-complete {:vsn "0.2.5", :native-prefix nil, :native? false}, com.google.guava/guava {:vsn "19.0", :native-prefix nil, :native? false}, viebel/codox-klipse-theme {:vsn "0.0.1", :native-prefix nil, :native? false}, clj-commons/clj-yaml {:vsn "1.0.26", :native-prefix nil, :native? false}, prismatic/schema {:vsn "1.1.10", :native-prefix nil, :native? false}, org.clojure/tools.reader {:vsn "1.3.6", :native-prefix nil, :native? false}, nrepl {:vsn "0.6.0", :native-prefix nil, :native? false}, org.jsoup/jsoup {:vsn "1.9.2", :native-prefix nil, :native? false}, expound {:vsn "0.9.0", :native-prefix nil, :native? false}, com.google.javascript/closure-compiler-unshaded {:vsn "v20160911", :native-prefix nil, :native? false}, com.google.protobuf/protobuf-java {:vsn "2.5.0", :native-prefix nil, :native? false}, com.google.code.findbugs/jsr305 {:vsn "1.3.9", :native-prefix nil, :native? false}, com.google.code.gson/gson {:vsn "2.2.4", :native-prefix nil, :native? false}}}] \ No newline at end of file diff --git a/devops_test.py b/version.py similarity index 100% rename from devops_test.py rename to version.py From e68ae468040a19b99423cd8a2ed57e2be3579f57 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 16 Feb 2023 15:59:38 +0100 Subject: [PATCH 022/243] Prepare build.py --- build.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/build.py b/build.py index 183e28e..91d74dc 100644 --- a/build.py +++ b/build.py @@ -1,7 +1,13 @@ from pybuilder.core import task, init from ddadevops import * from version import * +from release_mixin import * +CONFIG_FILE = '' +COMMIT_ID = '' + +class MyBuild(ReleaseMixin): + pass def main(): init_project() @@ -9,23 +15,24 @@ def main(): @init def initialize(project): project.build_depends_on('ddadevops>=3.1.2') - # build = get_devops_build() - # build.init() - init_project() + + if COMMIT_ID == '': + COMMIT_ID = 'HEAD' + + config = create_release_mixin_config(CONFIG_FILE, COMMIT_ID) + + build = MyBuild(project, config) + + build.init() + @task -def release(project): - # build = get_devops_build(project) - # build.prepare_release() # mit config file - prepare_release() - # build() - # build.publish() - # build.release_in_git() - # release_in_git() - -@task -def build(project): +def prepare(project): build = get_devops_build(project) - # ToDo: Implement for project + build.prepare_release() -main() \ No newline at end of file +@task +def tag_and_push(project): + build = get_devops_build(project) + build.tag_and_push() + From 22c5f6777e7750a62bfea7cde14f7f3d143733b7 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 16 Feb 2023 16:22:11 +0100 Subject: [PATCH 023/243] Prepare release_mixin.py --- release_mixin.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 6d709f7..f83a49f 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -5,10 +5,9 @@ from ddadevops import gopass_field_from_path, gopass_password_from_path from version import Version -def add_release_mixin_config(config, release_type, commit, file): +def create_release_mixin_config(config, release_type, commit, file): config.update({'ReleaseMixin': - {'release_type': release_type, - 'commit': commit, + {'commit_id': commit, 'file': file}}) return config @@ -17,26 +16,18 @@ class ReleaseMixin(DevopsBuild): def __init__(self, project, config): super().__init__(project, config) - release_mixin_config = config['ReleaseMixin'] - self.release_type = release_mixin_config['release_type4'] - self.commit = release_mixin_config['commit'] + release_mixin_config = config['ReleaseMixin'] + self.commit_id = release_mixin_config['commit_id'] self.file = release_mixin_config['file'] - def read_commit_message(self): - pass + def init(self): + release_and_bump_version = InitReleaseService(self.commit_id, self.file).get_version() + return release_and_bump_version - def calculate_release_type(self): - pass - def get_version(self): - return Version.from_file(self.file) - def create_release_version(self): - pass - - def create_bump_version(self): - pass + From 38c7aa97ca20db42f586c1f6142ef75d35f8fb9e Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 16 Feb 2023 16:55:01 +0100 Subject: [PATCH 024/243] Prepare services.py --- services.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 services.py diff --git a/services.py b/services.py new file mode 100644 index 0000000..49f9a62 --- /dev/null +++ b/services.py @@ -0,0 +1,39 @@ +class InitReleaseService(): + + def __init__(self, commit_id, file): + self.commit_id = commit_id + self.file = file + + def __read_commit_message(self): + pass + + def __calculate_release_type(self): + pass + + def get_version(self): + + current_version = VersionRepository.get_current_version(self.file) + commit_message = self.read_commit_message(self.commit_id) + release_type = self.calculate_release_type(commit_message) + + release_version = create_release_version(current_version,release_type) + bump_version = create_bump_version(current_version,release_type) + + release_and_bump_version = tuple(release_version, bump_version) + + return release_and_bump_version + + def create_release_version(self): + pass + + def create_bump_version(self): + pass + + + +class PrepareReleaseService(): + pass + +class TagAndPushReleaseService(): + pass + From 7eb7a2647a59d782899e2ba2dcf02bb943a83ab0 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 16 Feb 2023 16:55:23 +0100 Subject: [PATCH 025/243] WIP Prepare version_repository.py --- version_repository.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 version_repository.py diff --git a/version_repository.py b/version_repository.py new file mode 100644 index 0000000..57c362d --- /dev/null +++ b/version_repository.py @@ -0,0 +1,17 @@ +from file_handlers import FileHandler + +class VersionRepository(): + + def __init__(self, file): + self.file = file + + @classmethod + def load_file(cls, file): + file_handler = FileHandler.from_file_path(file) + version, is_snapshot = file_handler.parse() + inst = cls(version, is_snapshot) + inst.file_handler = file_handler + + return inst + + \ No newline at end of file From dac901769d9e8ccdf15df4d0e88ebb8467e9f578 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 13:33:17 +0100 Subject: [PATCH 026/243] Add architecture img --- architecture.png | Bin 0 -> 66829 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 architecture.png diff --git a/architecture.png b/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..f169118d4b9211ccb8028a0de39f1da71f2fd8a5 GIT binary patch literal 66829 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 Ge*AC7Pe-Nz literal 0 HcmV?d00001 From 88c3acb73cba34aee23ad087aad45fcde902f8a3 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 13:33:28 +0100 Subject: [PATCH 027/243] WIP Create version repo in services with classmethod The goal is to create a single version_repo object for each file loaded. In that way we may avoid loading the file for loading and writing. --- services.py | 7 ++++--- version.py | 18 +----------------- version_repository.py | 37 +++++++++++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/services.py b/services.py index 49f9a62..6074e4b 100644 --- a/services.py +++ b/services.py @@ -3,6 +3,7 @@ class InitReleaseService(): def __init__(self, commit_id, file): self.commit_id = commit_id self.file = file + self.version_repo = none def __read_commit_message(self): pass @@ -12,12 +13,12 @@ class InitReleaseService(): def get_version(self): - current_version = VersionRepository.get_current_version(self.file) + version_repo = VersionRepository.get_current_version(self.file) commit_message = self.read_commit_message(self.commit_id) release_type = self.calculate_release_type(commit_message) - release_version = create_release_version(current_version,release_type) - bump_version = create_bump_version(current_version,release_type) + release_version = create_release_version(version_repo.version ,release_type) + bump_version = create_bump_version(version_repo.version,release_type) release_and_bump_version = tuple(release_version, bump_version) diff --git a/version.py b/version.py index 17b9220..a90a281 100644 --- a/version.py +++ b/version.py @@ -24,23 +24,7 @@ class Version(): def __init__(self, version, is_snapshot): self.version = version - self.is_snapshot = is_snapshot - self.file_handler = None - - @classmethod - def from_file(cls, config_file_path): - file_handler = FileHandler.from_file_path(config_file_path) - version, is_snapshot = file_handler.parse() - inst = cls(version, is_snapshot) - inst.file_handler = file_handler - - return inst - - def to_file(self): - if self.file_handler is None: - raise Exception('Version was not created by from_file method.') - else: - self.file_handler.write(self.get()) + self.is_snapshot = is_snapshot def increment(self, level: ReleaseLevel): self.is_snapshot = False diff --git a/version_repository.py b/version_repository.py index 57c362d..a018e65 100644 --- a/version_repository.py +++ b/version_repository.py @@ -4,14 +4,35 @@ class VersionRepository(): def __init__(self, file): self.file = file + self.file_handler = None + self.version = None + self.is_snapshot = None + + def load_file(self): + self.file_handler = FileHandler.from_file_path(self.file) + return file_handler + + def write_file(self): + if self.file_handler is None: + raise Exception('Version was not created by load_file method.') + if self.version is None or self.is_snapshot is None: + raise Exception('Version or is_snapshot attribute not set.') + else: + self.file_handler.write(self.version) + + def parse_file(self, file_handler): + version, is_snapshot = file_handler.parse() + return version, is_snapshot @classmethod - def load_file(cls, file): - file_handler = FileHandler.from_file_path(file) - version, is_snapshot = file_handler.parse() - inst = cls(version, is_snapshot) - inst.file_handler = file_handler - - return inst + def get_current_version(cls, file): + inst = cls(file) - \ No newline at end of file + file_handler= inst.load_file(file) + version, is_snapshot = inst.parse_file(file_handler) + + inst.version = version + inst.is_snapshot = is_snapshot + inst.file_handler = file_handler + + return inst From ecdea88a6be013f234753be2cb27ca437eb740e9 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 13:47:14 +0100 Subject: [PATCH 028/243] Refactor version variable Avoid ambiguous variable names. We are handling a version list and a version string. --- services.py | 6 +++--- version.py | 8 ++++---- version_repository.py | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/services.py b/services.py index 6074e4b..74cfa99 100644 --- a/services.py +++ b/services.py @@ -13,12 +13,12 @@ class InitReleaseService(): def get_version(self): - version_repo = VersionRepository.get_current_version(self.file) + version_repo = VersionRepository.get(self.file) commit_message = self.read_commit_message(self.commit_id) release_type = self.calculate_release_type(commit_message) - release_version = create_release_version(version_repo.version ,release_type) - bump_version = create_bump_version(version_repo.version,release_type) + release_version = create_release_version(version_repo.version_list, release_type) + bump_version = create_bump_version(version_repo.version_list, release_type) release_and_bump_version = tuple(release_version, bump_version) diff --git a/version.py b/version.py index a90a281..c1e4c59 100644 --- a/version.py +++ b/version.py @@ -22,8 +22,8 @@ class ReleaseLevel(Enum): class Version(): - def __init__(self, version, is_snapshot): - self.version = version + def __init__(self, version_list, is_snapshot): + self.version_list = version_list self.is_snapshot = is_snapshot def increment(self, level: ReleaseLevel): @@ -41,8 +41,8 @@ class Version(): self.version[ReleaseLevel.MINOR.value] = 0 self.version[ReleaseLevel.MAJOR.value] += 1 - def get(self) -> str: - version_string = ".".join([str(x) for x in self.version]) + def get_version_string(self) -> str: + version_string = ".".join([str(x) for x in self.version_list]) if self.is_snapshot: version_string += "-SNAPSHOT" return version_string \ No newline at end of file diff --git a/version_repository.py b/version_repository.py index a018e65..28d46ce 100644 --- a/version_repository.py +++ b/version_repository.py @@ -5,33 +5,33 @@ class VersionRepository(): def __init__(self, file): self.file = file self.file_handler = None - self.version = None + self.version_list = None self.is_snapshot = None def load_file(self): self.file_handler = FileHandler.from_file_path(self.file) return file_handler - def write_file(self): + def write_file(self, version_string): if self.file_handler is None: raise Exception('Version was not created by load_file method.') - if self.version is None or self.is_snapshot is None: + if self.version_list is None or self.is_snapshot is None: raise Exception('Version or is_snapshot attribute not set.') else: - self.file_handler.write(self.version) + self.file_handler.write(version_string) def parse_file(self, file_handler): - version, is_snapshot = file_handler.parse() - return version, is_snapshot + version_list, is_snapshot = file_handler.parse() + return version_list, is_snapshot @classmethod - def get_current_version(cls, file): + def get(cls, file) -> VersionRepository: inst = cls(file) file_handler= inst.load_file(file) - version, is_snapshot = inst.parse_file(file_handler) + version_list, is_snapshot = inst.parse_file(file_handler) - inst.version = version + inst.version_list = version inst.is_snapshot = is_snapshot inst.file_handler = file_handler From 61629e9d97380a10afdb91893a93aba45816982f Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 14:07:58 +0100 Subject: [PATCH 029/243] Fix undefined var --- version_repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_repository.py b/version_repository.py index 28d46ce..741aaa4 100644 --- a/version_repository.py +++ b/version_repository.py @@ -31,7 +31,7 @@ class VersionRepository(): file_handler= inst.load_file(file) version_list, is_snapshot = inst.parse_file(file_handler) - inst.version_list = version + inst.version_list = version_list inst.is_snapshot = is_snapshot inst.file_handler = file_handler From 91f3d7d9721b6ef009c7f57946ff8dbb47441526 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 14:24:55 +0100 Subject: [PATCH 030/243] Refactor version class --- version.py | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/version.py b/version.py index c1e4c59..8d1ea87 100644 --- a/version.py +++ b/version.py @@ -4,7 +4,7 @@ from file_handlers import FileHandler def init_project(): # validate_values() version = Version.from_file('build.gradle') - version.increment(ReleaseLevel.SNAPSHOT) + version.increment(ReleaseType.SNAPSHOT) version.to_file() print(version.get()) @@ -14,7 +14,7 @@ def prepare_release(): def release_in_git(): pass -class ReleaseLevel(Enum): +class ReleaseType(Enum): MAJOR = 0 MINOR = 1 PATCH = 2 @@ -22,27 +22,39 @@ class ReleaseLevel(Enum): class Version(): - def __init__(self, version_list, is_snapshot): - self.version_list = version_list - self.is_snapshot = is_snapshot + def __init__(self, version_list, release_type): + self.version_list = None + self.is_snapshot = None + self.version_string = None - def increment(self, level: ReleaseLevel): + def increment(self, release_type: ReleaseType): self.is_snapshot = False - match level: - case ReleaseLevel.SNAPSHOT: + match release_type: + case ReleaseType.SNAPSHOT: self.is_snapshot = True - case ReleaseLevel.PATCH: - self.version[ReleaseLevel.PATCH.value] += 1 - case ReleaseLevel.MINOR: - self.version[ReleaseLevel.PATCH.value] = 0 - self.version[ReleaseLevel.MINOR.value] += 1 - case ReleaseLevel.MAJOR: - self.version[ReleaseLevel.PATCH.value] = 0 - self.version[ReleaseLevel.MINOR.value] = 0 - self.version[ReleaseLevel.MAJOR.value] += 1 + case ReleaseType.PATCH: + self.version_list[ReleaseType.PATCH.value] += 1 + case ReleaseType.MINOR: + self.version_list[ReleaseType.PATCH.value] = 0 + self.version_list[ReleaseType.MINOR.value] += 1 + case ReleaseType.MAJOR: + self.version_list[ReleaseType.PATCH.value] = 0 + self.version_list[ReleaseType.MINOR.value] = 0 + self.version_list[ReleaseType.MAJOR.value] += 1 def get_version_string(self) -> str: - version_string = ".".join([str(x) for x in self.version_list]) + self.version_string = ".".join([str(x) for x in self.version_list]) if self.is_snapshot: - version_string += "-SNAPSHOT" - return version_string \ No newline at end of file + self.version_string += "-SNAPSHOT" + + @classmethod + def create_release_version(cls, version_list, release_type): + inst = cls(version_list, release_type) + inst.increment(release_type) + + return inst + + + @classmethod + def create_bump_version(cls, version_list, release_type): + pass \ No newline at end of file From eb998cfdbbb0ab85c91c605405fe2b0fd80a993d Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 14:45:15 +0100 Subject: [PATCH 031/243] Implement creation of release version --- services.py | 27 ++++++++++++++------------- version.py | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/services.py b/services.py index 74cfa99..b4ba72f 100644 --- a/services.py +++ b/services.py @@ -3,7 +3,7 @@ class InitReleaseService(): def __init__(self, commit_id, file): self.commit_id = commit_id self.file = file - self.version_repo = none + self.version_repo = None def __read_commit_message(self): pass @@ -11,23 +11,24 @@ class InitReleaseService(): def __calculate_release_type(self): pass - def get_version(self): + def get_version_repo(self): + self.version_repo = VersionRepository.get(self.file) - version_repo = VersionRepository.get(self.file) - commit_message = self.read_commit_message(self.commit_id) - release_type = self.calculate_release_type(commit_message) - - release_version = create_release_version(version_repo.version_list, release_type) - bump_version = create_bump_version(version_repo.version_list, release_type) - - release_and_bump_version = tuple(release_version, bump_version) - - return release_and_bump_version + def get_version_list(self): + self.get_version_repo() + return self.version_repo.version_list def create_release_version(self): - pass + commit_message = self.read_commit_message(self.commit_id) + release_type = self.calculate_release_type(commit_message) + version_list = self.get_version_list() + + release_version = Version.create_release_version(version_list, release_type) + self.version_repo.write_file(release_version.get_version_string()) + def create_bump_version(self): + bump_version = create_bump_version(version_repo.version_list, release_type) pass diff --git a/version.py b/version.py index 8d1ea87..162e28c 100644 --- a/version.py +++ b/version.py @@ -24,8 +24,8 @@ class Version(): def __init__(self, version_list, release_type): self.version_list = None - self.is_snapshot = None self.version_string = None + self.is_snapshot = None def increment(self, release_type: ReleaseType): self.is_snapshot = False From fa7e10d3ce0b8573772f9e47cbaa2aac6132f680 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 15:50:58 +0100 Subject: [PATCH 032/243] Implement creation of bump version --- services.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services.py b/services.py index b4ba72f..c7240aa 100644 --- a/services.py +++ b/services.py @@ -26,10 +26,14 @@ class InitReleaseService(): release_version = Version.create_release_version(version_list, release_type) self.version_repo.write_file(release_version.get_version_string()) - def create_bump_version(self): - bump_version = create_bump_version(version_repo.version_list, release_type) - pass + if self.version_repo == None: + raise Exception('VersionRepo was not created. Did you run create_lease_version()?') + version_list = self.get_version_list() + + bump_version = Version.create_bump_version(version_list, ReleaseType.SNAPSHOT) + self.version_repo.write_file(bump_version.get_version_string()) + From c13734493296543ff07709a94e5068fc9103b875 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 16:21:02 +0100 Subject: [PATCH 033/243] Implement bump release --- services.py | 2 +- version.py | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/services.py b/services.py index c7240aa..952dc2d 100644 --- a/services.py +++ b/services.py @@ -31,7 +31,7 @@ class InitReleaseService(): raise Exception('VersionRepo was not created. Did you run create_lease_version()?') version_list = self.get_version_list() - bump_version = Version.create_bump_version(version_list, ReleaseType.SNAPSHOT) + bump_version = Version.create_bump_version(version_list) self.version_repo.write_file(bump_version.get_version_string()) diff --git a/version.py b/version.py index 162e28c..863f82e 100644 --- a/version.py +++ b/version.py @@ -3,7 +3,7 @@ from file_handlers import FileHandler def init_project(): # validate_values() - version = Version.from_file('build.gradle') + version = Version.from_file('build.gradle') version.increment(ReleaseType.SNAPSHOT) version.to_file() print(version.get()) @@ -19,6 +19,7 @@ class ReleaseType(Enum): MINOR = 1 PATCH = 2 SNAPSHOT = 3 + BUMP = None class Version(): @@ -30,6 +31,9 @@ class Version(): def increment(self, release_type: ReleaseType): self.is_snapshot = False match release_type: + case ReleaseType.BUMP: + self.is_snapshot = True + self.version_list[ReleaseType.PATCH.value] += 1 case ReleaseType.SNAPSHOT: self.is_snapshot = True case ReleaseType.PATCH: @@ -45,16 +49,19 @@ class Version(): def get_version_string(self) -> str: self.version_string = ".".join([str(x) for x in self.version_list]) if self.is_snapshot: - self.version_string += "-SNAPSHOT" + self.version_string += "-SNAPSHOT" @classmethod def create_release_version(cls, version_list, release_type): inst = cls(version_list, release_type) - inst.increment(release_type) - + if release_type == ReleaseType.PATCH: + inst.is_snapshot = False + else: + inst.increment(release_type) return inst - @classmethod - def create_bump_version(cls, version_list, release_type): - pass \ No newline at end of file + def create_bump_version(cls, version_list): + inst = cls(version_list, ReleaseType.BUMP) + inst.increment(release_type) + return inst From 54c3917ec23acc1fc977fcd1252bbf34a02e6cce Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 16:37:01 +0100 Subject: [PATCH 034/243] Clean up --- version.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/version.py b/version.py index 863f82e..0a08d99 100644 --- a/version.py +++ b/version.py @@ -1,26 +1,6 @@ from enum import Enum from file_handlers import FileHandler -def init_project(): - # validate_values() - version = Version.from_file('build.gradle') - version.increment(ReleaseType.SNAPSHOT) - version.to_file() - print(version.get()) - -def prepare_release(): - pass - -def release_in_git(): - pass - -class ReleaseType(Enum): - MAJOR = 0 - MINOR = 1 - PATCH = 2 - SNAPSHOT = 3 - BUMP = None - class Version(): def __init__(self, version_list, release_type): From 3d3b26b70e1e452c859021cc53ee42079b243508 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 16:37:30 +0100 Subject: [PATCH 035/243] Refactor release type --- release_type.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 release_type.py diff --git a/release_type.py b/release_type.py new file mode 100644 index 0000000..e8cccbe --- /dev/null +++ b/release_type.py @@ -0,0 +1,6 @@ +class ReleaseType(Enum): + MAJOR = 0 + MINOR = 1 + PATCH = 2 + SNAPSHOT = 3 + BUMP = None \ No newline at end of file From e790bc6eeea9d696d32e4e0619764ccf65b17f09 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 16:37:49 +0100 Subject: [PATCH 036/243] WIP Tests --- test/test_version_class.py | 78 +++++++++----------------------------- 1 file changed, 17 insertions(+), 61 deletions(-) diff --git a/test/test_version_class.py b/test/test_version_class.py index 5c7b49a..a6782b4 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -1,29 +1,36 @@ -from devops_test import Version, ReleaseLevel +from version import Version +from version_repository import VersionRepository +from release_type import ReleaseType from pathlib import Path def test_version(): version = Version([1, 2, 3], False) version.increment(ReleaseLevel.SNAPSHOT) - assert version.get() == "1.2.3-SNAPSHOT" + assert version.get_version_string() == "1.2.3-SNAPSHOT" assert version.version == [1, 2, 3] assert version.is_snapshot + version.increment(ReleaseLevel.BUMP) + assert version.get_version_string() == "1.2.4-SNAPSHOT" + assert version.version == [1, 2, 4] + assert not version.is_snapshot + version.increment(ReleaseLevel.PATCH) - assert version.get() == "1.2.4" + assert version.get_version_string() == "1.2.5" assert version.version == [1, 2, 4] assert not version.is_snapshot version.increment(ReleaseLevel.SNAPSHOT) - assert version.get() == "1.2.4-SNAPSHOT" + assert version.get_version_string() == "1.2.5-SNAPSHOT" version.increment(ReleaseLevel.SNAPSHOT) - assert version.get() == "1.2.4-SNAPSHOT" + assert version.get_version_string() == "1.2.5-SNAPSHOT" version.increment(ReleaseLevel.MINOR) - assert version.get() == "1.3.0" + assert version.get_version_string() == "1.3.0" version.increment(ReleaseLevel.MAJOR) - assert version.get() == "2.0.0" + assert version.get_version_string() == "2.0.0" def test_gradle(tmp_path): @@ -36,60 +43,9 @@ def test_gradle(tmp_path): f.write_text(contents) # test - version = Version.from_file(f) - version.increment(ReleaseLevel.SNAPSHOT) - version.to_file() + version_repo = VersionRepository.get(f) + version = Version.create_release_version(ReleaseLevel.SNAPSHOT) + version_repo.write_file(version.get_version_string) # check assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() - -def test_json(tmp_path): - # init - file_name = 'config.json' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) - - # test - version = Version.from_file(f) - version.increment(ReleaseLevel.SNAPSHOT) - version.to_file() - - # check - assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() - -def test_clojure(tmp_path): - # init - file_name = 'config.clj' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) - - # test - version = Version.from_file(f) - version.increment(ReleaseLevel.SNAPSHOT) - version.to_file() - - # check - assert '1.1.3-SNAPSHOT' in f.read_text() - -def test_python(tmp_path): - # init - file_name = 'config.py' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) - - # test - version = Version.from_file(f) - version.increment(ReleaseLevel.SNAPSHOT) - version.to_file() - - # check - assert '3.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file From 80c0799d1a522817246a3a8a6c74627285895d4a Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 16:45:36 +0100 Subject: [PATCH 037/243] Add proper imports --- test/test_version_class.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/test_version_class.py b/test/test_version_class.py index a6782b4..906ddb5 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -1,7 +1,25 @@ +from pathlib import Path +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +# now we can import the module in the parent +# directory. + from version import Version from version_repository import VersionRepository -from release_type import ReleaseType -from pathlib import Path +from release_type import ReleaseType def test_version(): version = Version([1, 2, 3], False) From 1743dce46d8adb2f222865963e138dad8f6caff6 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 16:50:12 +0100 Subject: [PATCH 038/243] Add missing imports and cleanup --- release_type.py | 1 + version.py | 2 +- version_repository.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/release_type.py b/release_type.py index e8cccbe..e4217a5 100644 --- a/release_type.py +++ b/release_type.py @@ -1,3 +1,4 @@ +from enum import Enum class ReleaseType(Enum): MAJOR = 0 MINOR = 1 diff --git a/version.py b/version.py index 0a08d99..0a5dd1f 100644 --- a/version.py +++ b/version.py @@ -1,4 +1,4 @@ -from enum import Enum +from release_type import ReleaseType from file_handlers import FileHandler class Version(): diff --git a/version_repository.py b/version_repository.py index 741aaa4..516a6f1 100644 --- a/version_repository.py +++ b/version_repository.py @@ -25,7 +25,7 @@ class VersionRepository(): return version_list, is_snapshot @classmethod - def get(cls, file) -> VersionRepository: + def get(cls, file): inst = cls(file) file_handler= inst.load_file(file) From d539c7331864273d0a6e149f3f8c3a009a06e6b6 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 16:53:53 +0100 Subject: [PATCH 039/243] Fix test method and attribute calls --- test/test_version_class.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/test_version_class.py b/test/test_version_class.py index 906ddb5..abc7b35 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -24,30 +24,30 @@ from release_type import ReleaseType def test_version(): version = Version([1, 2, 3], False) - version.increment(ReleaseLevel.SNAPSHOT) + version.increment(ReleaseType.SNAPSHOT) assert version.get_version_string() == "1.2.3-SNAPSHOT" - assert version.version == [1, 2, 3] + assert version.version_list == [1, 2, 3] assert version.is_snapshot - version.increment(ReleaseLevel.BUMP) + version.increment(ReleaseType.BUMP) assert version.get_version_string() == "1.2.4-SNAPSHOT" - assert version.version == [1, 2, 4] + assert version.version_list == [1, 2, 4] assert not version.is_snapshot - version.increment(ReleaseLevel.PATCH) + version.increment(ReleaseType.PATCH) assert version.get_version_string() == "1.2.5" - assert version.version == [1, 2, 4] + assert version.version_list == [1, 2, 5] assert not version.is_snapshot - version.increment(ReleaseLevel.SNAPSHOT) + version.increment(ReleaseType.SNAPSHOT) assert version.get_version_string() == "1.2.5-SNAPSHOT" - version.increment(ReleaseLevel.SNAPSHOT) + version.increment(ReleaseType.SNAPSHOT) assert version.get_version_string() == "1.2.5-SNAPSHOT" - version.increment(ReleaseLevel.MINOR) + version.increment(ReleaseType.MINOR) assert version.get_version_string() == "1.3.0" - version.increment(ReleaseLevel.MAJOR) + version.increment(ReleaseType.MAJOR) assert version.get_version_string() == "2.0.0" @@ -62,7 +62,7 @@ def test_gradle(tmp_path): # test version_repo = VersionRepository.get(f) - version = Version.create_release_version(ReleaseLevel.SNAPSHOT) + version = Version.create_release_version(ReleaseType.SNAPSHOT) version_repo.write_file(version.get_version_string) # check From b06728c55322ea3273a30d34546012b7d3ee4028 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 17:04:50 +0100 Subject: [PATCH 040/243] Fix return val and hardcoded None --- version.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/version.py b/version.py index 0a5dd1f..64b854d 100644 --- a/version.py +++ b/version.py @@ -3,8 +3,8 @@ from file_handlers import FileHandler class Version(): - def __init__(self, version_list, release_type): - self.version_list = None + def __init__(self, version_list: list): + self.version_list = version_list self.version_string = None self.is_snapshot = None @@ -30,6 +30,7 @@ class Version(): self.version_string = ".".join([str(x) for x in self.version_list]) if self.is_snapshot: self.version_string += "-SNAPSHOT" + return self.version_string @classmethod def create_release_version(cls, version_list, release_type): From f96733f8b5712b36294a94b424f039cb831d7c41 Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 17:05:30 +0100 Subject: [PATCH 041/243] Fix version instance --- test/test_version_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_version_class.py b/test/test_version_class.py index abc7b35..aaad5af 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -22,7 +22,7 @@ from version_repository import VersionRepository from release_type import ReleaseType def test_version(): - version = Version([1, 2, 3], False) + version = Version([1, 2, 3]) version.increment(ReleaseType.SNAPSHOT) assert version.get_version_string() == "1.2.3-SNAPSHOT" From 156d33631404b6b8e1e3c5bf00c1b03cc3c58f6d Mon Sep 17 00:00:00 2001 From: erik Date: Mon, 20 Feb 2023 17:07:14 +0100 Subject: [PATCH 042/243] Fix logical error --- test/test_version_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_version_class.py b/test/test_version_class.py index aaad5af..9607ab4 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -32,7 +32,7 @@ def test_version(): version.increment(ReleaseType.BUMP) assert version.get_version_string() == "1.2.4-SNAPSHOT" assert version.version_list == [1, 2, 4] - assert not version.is_snapshot + assert version.is_snapshot version.increment(ReleaseType.PATCH) assert version.get_version_string() == "1.2.5" From 1dc5a1e9e8861e4ca5ce4b9cbe255c07ff728e79 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 21 Feb 2023 09:54:18 +0100 Subject: [PATCH 043/243] WIP Fix returns and function calls --- version_repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version_repository.py b/version_repository.py index 516a6f1..725884b 100644 --- a/version_repository.py +++ b/version_repository.py @@ -10,7 +10,7 @@ class VersionRepository(): def load_file(self): self.file_handler = FileHandler.from_file_path(self.file) - return file_handler + return self.file_handler def write_file(self, version_string): if self.file_handler is None: @@ -28,7 +28,7 @@ class VersionRepository(): def get(cls, file): inst = cls(file) - file_handler= inst.load_file(file) + file_handler = inst.load_file() version_list, is_snapshot = inst.parse_file(file_handler) inst.version_list = version_list From ea45671bea8bdfba26c3d4c2516d7def76d184cc Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 21 Feb 2023 10:07:30 +0100 Subject: [PATCH 044/243] Fix version and gradle test --- test/test_version_class.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_version_class.py b/test/test_version_class.py index 9607ab4..881ea7f 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -22,7 +22,7 @@ from version_repository import VersionRepository from release_type import ReleaseType def test_version(): - version = Version([1, 2, 3]) + version = Version([1, 2, 3], ReleaseType.SNAPSHOT) version.increment(ReleaseType.SNAPSHOT) assert version.get_version_string() == "1.2.3-SNAPSHOT" @@ -62,8 +62,9 @@ def test_gradle(tmp_path): # test version_repo = VersionRepository.get(f) - version = Version.create_release_version(ReleaseType.SNAPSHOT) - version_repo.write_file(version.get_version_string) + version_list = version_repo.version_list + version = Version.create_release_version(version_list, ReleaseType.SNAPSHOT) + version_repo.write_file(version.get_version_string()) # check assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() From bb170175837e9c75f9b3662d9fa00e1f509bd7a2 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 21 Feb 2023 10:08:04 +0100 Subject: [PATCH 045/243] readd release type argument --- version.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/version.py b/version.py index 64b854d..492411d 100644 --- a/version.py +++ b/version.py @@ -3,8 +3,9 @@ from file_handlers import FileHandler class Version(): - def __init__(self, version_list: list): + def __init__(self, version_list: list, release_type: ReleaseType): self.version_list = version_list + self.release_type = release_type self.version_string = None self.is_snapshot = None From e9301dedd17fede7fb52ddc742e203f58ffb8c1e Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 21 Feb 2023 10:17:16 +0100 Subject: [PATCH 046/243] Readd tests for python, clojure and json --- test/test_version_class.py | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/test_version_class.py b/test/test_version_class.py index 881ea7f..eb3404d 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -68,3 +68,57 @@ def test_gradle(tmp_path): # check assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() + +def test_json(tmp_path): + # init + file_name = 'config.json' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + version_repo = VersionRepository.get(f) + version_list = version_repo.version_list + version = Version.create_release_version(version_list, ReleaseType.SNAPSHOT) + version_repo.write_file(version.get_version_string()) + + # check + assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() + +def test_clojure(tmp_path): + # init + file_name = 'config.clj' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + version_repo = VersionRepository.get(f) + version_list = version_repo.version_list + version = Version.create_release_version(version_list, ReleaseType.SNAPSHOT) + version_repo.write_file(version.get_version_string()) + + # check + assert '1.1.3-SNAPSHOT' in f.read_text() + +def test_python(tmp_path): + # init + file_name = 'config.py' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + version_repo = VersionRepository.get(f) + version_list = version_repo.version_list + version = Version.create_release_version(version_list, ReleaseType.SNAPSHOT) + version_repo.write_file(version.get_version_string()) + + # check + assert '3.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file From fce565d2d508a9bf9fccd383c0a0038e0107bb4e Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 21 Feb 2023 15:08:39 +0100 Subject: [PATCH 047/243] Implement git handler --- git_handler.py | 77 ++++++++++++++++++++++++++++++++++++++++ test/test_git_handler.py | 30 ++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 git_handler.py create mode 100644 test/test_git_handler.py diff --git a/git_handler.py b/git_handler.py new file mode 100644 index 0000000..3f30551 --- /dev/null +++ b/git_handler.py @@ -0,0 +1,77 @@ +import os +import subprocess as sub + +# define semantics for release types by commit messages +## snapshot - snapshot release +## fix/patch - patch release +## bump - version bump release +## feature/feat/minor - minor release +## major/breaking - major release + +GIT = 'git' +LOG = 'log' +FORMAT = '"%h %s"' +FORMAT_DEC = "%d" +PRETTY_OPTION = '--pretty=' +DECORATE_OPTION = '--decorate=full' + +class GitRepo(): + + def __init__(self): + self.commits = None + self.tags = None + + def __clean_commit_string(self, commit_string): + return commit_string.replace('\"', "").replace('\n', "").split() + + def __clean_tag_string(self, tag_string): + return tag_string.replace(" ", "").replace('\n', "") + + def get_tags(self): + stream = sub.Popen([GIT, + LOG, + PRETTY_OPTION + FORMAT_DEC, + DECORATE_OPTION], + stdout=sub.PIPE, + stderr=sub.PIPE, + text=True, + encoding="UTF-8") + stdout = stream.stdout.readlines() + stderr = stream.stderr.readlines() + + if len(stderr) > 0: + raise Exception(f"Git command failed with: {stderr}") + + return stdout + + def get_commits(self): + stream = sub.Popen([GIT, + LOG, + PRETTY_OPTION + FORMAT], + stdout=sub.PIPE, + stderr=sub.PIPE, + text=True, + encoding="UTF-8") + stdout = stream.stdout.readlines() + stderr = stream.stderr.readlines() + + if len(stderr) > 0: + raise Exception(f"Git command failed with: {stderr}") + + self.tags = self.get_tags() + self.commits = {} + + if len(self.tags) != len(stdout): + raise Exception("Tags list did not match commits list") + + for i, elem in enumerate(stdout): + commit_string = self.__clean_commit_string(elem) + + commit_id = commit_string[0] + commit_message = " ".join(commit_string[1:len(commit_string)]) + commit_tag = self.__clean_tag_string(self.tags[i]) + + self.commits[commit_id] = [commit_tag, commit_message] + + return self.commits + diff --git a/test/test_git_handler.py b/test/test_git_handler.py new file mode 100644 index 0000000..77d95c9 --- /dev/null +++ b/test/test_git_handler.py @@ -0,0 +1,30 @@ +from pathlib import Path +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +# now we can import the module in the parent +# directory. + +from git_handler import * + +def test_git_handler(): + + # init + repo = GitRepo() + repo.get_commits() + + #test + assert type(repo.commits) == dict + assert repo.commits["decd36b"] == ["(tag:refs/tags/TEST)", "Initial commit"] From 6883435248052a7d37724780f365c1a26e66302b Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 21 Feb 2023 15:52:09 +0100 Subject: [PATCH 048/243] Refactor for code consistency --- services.py | 25 +++++++++++-------------- version.py | 30 +++++++++++++----------------- version_repository.py | 23 ++++++++--------------- 3 files changed, 32 insertions(+), 46 deletions(-) diff --git a/services.py b/services.py index 952dc2d..3792d41 100644 --- a/services.py +++ b/services.py @@ -1,3 +1,5 @@ +import version_repository +import release_type class InitReleaseService(): def __init__(self, commit_id, file): @@ -11,28 +13,23 @@ class InitReleaseService(): def __calculate_release_type(self): pass - def get_version_repo(self): - self.version_repo = VersionRepository.get(self.file) - - def get_version_list(self): - self.get_version_repo() - return self.version_repo.version_list + def get_version(self, release_type): + self.version_repo = VersionRepository(self.file) + return repo.get_version(release_type) def create_release_version(self): commit_message = self.read_commit_message(self.commit_id) release_type = self.calculate_release_type(commit_message) - version_list = self.get_version_list() - - release_version = Version.create_release_version(version_list, release_type) - self.version_repo.write_file(release_version.get_version_string()) + version = self.get_version(release_type).create_release_version() + + self.version_repo.write_file(version.get_version_string()) def create_bump_version(self): if self.version_repo == None: raise Exception('VersionRepo was not created. Did you run create_lease_version()?') - version_list = self.get_version_list() - - bump_version = Version.create_bump_version(version_list) - self.version_repo.write_file(bump_version.get_version_string()) + version = self.get_version(ReleaseType.BUMP).create_bump_version() + + self.version_repo.write_file(version.get_version_string()) diff --git a/version.py b/version.py index 492411d..59a6c5c 100644 --- a/version.py +++ b/version.py @@ -7,11 +7,11 @@ class Version(): self.version_list = version_list self.release_type = release_type self.version_string = None - self.is_snapshot = None + self.is_snapshot = is_snapshot - def increment(self, release_type: ReleaseType): + def increment(self): self.is_snapshot = False - match release_type: + match self.release_type: case ReleaseType.BUMP: self.is_snapshot = True self.version_list[ReleaseType.PATCH.value] += 1 @@ -26,24 +26,20 @@ class Version(): self.version_list[ReleaseType.PATCH.value] = 0 self.version_list[ReleaseType.MINOR.value] = 0 self.version_list[ReleaseType.MAJOR.value] += 1 + case None + raise Exception("Release Type was not set!") def get_version_string(self) -> str: self.version_string = ".".join([str(x) for x in self.version_list]) if self.is_snapshot: self.version_string += "-SNAPSHOT" return self.version_string - - @classmethod - def create_release_version(cls, version_list, release_type): - inst = cls(version_list, release_type) - if release_type == ReleaseType.PATCH: - inst.is_snapshot = False + + def create_release_version(self): + if self.release_type == ReleaseType.PATCH: + self.is_snapshot = False else: - inst.increment(release_type) - return inst - - @classmethod - def create_bump_version(cls, version_list): - inst = cls(version_list, ReleaseType.BUMP) - inst.increment(release_type) - return inst + self.increment(release_type) + + def create_bump_version(self): + self.increment(ReleaseType.BUMP) diff --git a/version_repository.py b/version_repository.py index 725884b..a5bf519 100644 --- a/version_repository.py +++ b/version_repository.py @@ -1,16 +1,15 @@ from file_handlers import FileHandler +from version import Version class VersionRepository(): def __init__(self, file): self.file = file - self.file_handler = None - self.version_list = None - self.is_snapshot = None + self.file_handler = None def load_file(self): self.file_handler = FileHandler.from_file_path(self.file) - return self.file_handler + return file_handler def write_file(self, version_string): if self.file_handler is None: @@ -23,16 +22,10 @@ class VersionRepository(): def parse_file(self, file_handler): version_list, is_snapshot = file_handler.parse() return version_list, is_snapshot + + def get_version(self, release_type): - @classmethod - def get(cls, file): - inst = cls(file) + self.file_handler = self.load_file(self.file) + version_list, is_snapshot = self.parse_file(file_handler) - file_handler = inst.load_file() - version_list, is_snapshot = inst.parse_file(file_handler) - - inst.version_list = version_list - inst.is_snapshot = is_snapshot - inst.file_handler = file_handler - - return inst + return Version(version_list, release_type) From 94b993ccf5a7ecea53c85a9e3a738576c08fcf31 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 21 Feb 2023 16:14:47 +0100 Subject: [PATCH 049/243] Fix bugs and tests --- test/test_version_class.py | 61 ++++++++++++++++++++------------------ version.py | 6 ++-- version_repository.py | 19 ++++++------ 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/test/test_version_class.py b/test/test_version_class.py index eb3404d..8880488 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -19,36 +19,39 @@ sys.path.append(parent) from version import Version from version_repository import VersionRepository -from release_type import ReleaseType +from release_type import ReleaseType def test_version(): version = Version([1, 2, 3], ReleaseType.SNAPSHOT) - version.increment(ReleaseType.SNAPSHOT) + version.increment() assert version.get_version_string() == "1.2.3-SNAPSHOT" assert version.version_list == [1, 2, 3] assert version.is_snapshot - version.increment(ReleaseType.BUMP) + version = Version([1, 2, 3], ReleaseType.BUMP) + version.increment() assert version.get_version_string() == "1.2.4-SNAPSHOT" assert version.version_list == [1, 2, 4] assert version.is_snapshot - version.increment(ReleaseType.PATCH) - assert version.get_version_string() == "1.2.5" - assert version.version_list == [1, 2, 5] + version = Version([1, 2, 3], ReleaseType.PATCH) + version.increment() + assert version.get_version_string() == "1.2.4" + assert version.version_list == [1, 2, 4] assert not version.is_snapshot - version.increment(ReleaseType.SNAPSHOT) - assert version.get_version_string() == "1.2.5-SNAPSHOT" - version.increment(ReleaseType.SNAPSHOT) - assert version.get_version_string() == "1.2.5-SNAPSHOT" - - version.increment(ReleaseType.MINOR) + version = Version([1, 2, 3], ReleaseType.MINOR) + version.increment() assert version.get_version_string() == "1.3.0" + assert version.version_list == [1, 3, 0] + assert not version.is_snapshot - version.increment(ReleaseType.MAJOR) + version = Version([1, 2, 3], ReleaseType.MAJOR) + version.increment() assert version.get_version_string() == "2.0.0" + assert version.version_list == [2, 0, 0] + assert not version.is_snapshot def test_gradle(tmp_path): @@ -61,10 +64,10 @@ def test_gradle(tmp_path): f.write_text(contents) # test - version_repo = VersionRepository.get(f) - version_list = version_repo.version_list - version = Version.create_release_version(version_list, ReleaseType.SNAPSHOT) - version_repo.write_file(version.get_version_string()) + repo = VersionRepository(f) + version = repo.get_version(ReleaseType.SNAPSHOT) + version.create_release_version() + repo.write_file(version.get_version_string()) # check assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() @@ -79,10 +82,10 @@ def test_json(tmp_path): f.write_text(contents) # test - version_repo = VersionRepository.get(f) - version_list = version_repo.version_list - version = Version.create_release_version(version_list, ReleaseType.SNAPSHOT) - version_repo.write_file(version.get_version_string()) + repo = VersionRepository(f) + version = repo.get_version(ReleaseType.SNAPSHOT) + version.create_release_version() + repo.write_file(version.get_version_string()) # check assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() @@ -97,10 +100,10 @@ def test_clojure(tmp_path): f.write_text(contents) # test - version_repo = VersionRepository.get(f) - version_list = version_repo.version_list - version = Version.create_release_version(version_list, ReleaseType.SNAPSHOT) - version_repo.write_file(version.get_version_string()) + repo = VersionRepository(f) + version = repo.get_version(ReleaseType.SNAPSHOT) + version.create_release_version() + repo.write_file(version.get_version_string()) # check assert '1.1.3-SNAPSHOT' in f.read_text() @@ -115,10 +118,10 @@ def test_python(tmp_path): f.write_text(contents) # test - version_repo = VersionRepository.get(f) - version_list = version_repo.version_list - version = Version.create_release_version(version_list, ReleaseType.SNAPSHOT) - version_repo.write_file(version.get_version_string()) + repo = VersionRepository(f) + version = repo.get_version(ReleaseType.SNAPSHOT) + version.create_release_version() + repo.write_file(version.get_version_string()) # check assert '3.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file diff --git a/version.py b/version.py index 59a6c5c..6439195 100644 --- a/version.py +++ b/version.py @@ -7,7 +7,7 @@ class Version(): self.version_list = version_list self.release_type = release_type self.version_string = None - self.is_snapshot = is_snapshot + self.is_snapshot = None def increment(self): self.is_snapshot = False @@ -26,7 +26,7 @@ class Version(): self.version_list[ReleaseType.PATCH.value] = 0 self.version_list[ReleaseType.MINOR.value] = 0 self.version_list[ReleaseType.MAJOR.value] += 1 - case None + case None: raise Exception("Release Type was not set!") def get_version_string(self) -> str: @@ -39,7 +39,7 @@ class Version(): if self.release_type == ReleaseType.PATCH: self.is_snapshot = False else: - self.increment(release_type) + self.increment() def create_bump_version(self): self.increment(ReleaseType.BUMP) diff --git a/version_repository.py b/version_repository.py index a5bf519..b135b79 100644 --- a/version_repository.py +++ b/version_repository.py @@ -9,23 +9,24 @@ class VersionRepository(): def load_file(self): self.file_handler = FileHandler.from_file_path(self.file) - return file_handler + return self.file_handler def write_file(self, version_string): if self.file_handler is None: - raise Exception('Version was not created by load_file method.') - if self.version_list is None or self.is_snapshot is None: - raise Exception('Version or is_snapshot attribute not set.') + raise Exception('Version was not created by load_file method.') else: self.file_handler.write(version_string) - def parse_file(self, file_handler): - version_list, is_snapshot = file_handler.parse() + def parse_file(self): + version_list, is_snapshot = self.file_handler.parse() return version_list, is_snapshot def get_version(self, release_type): - self.file_handler = self.load_file(self.file) - version_list, is_snapshot = self.parse_file(file_handler) + self.file_handler = self.load_file() + version_list, is_snapshot = self.parse_file() + version = Version(version_list, release_type) + version.is_snapshot = is_snapshot + + return version - return Version(version_list, release_type) From 72e9add5aff01c9c2f7b15382c7d4d420cc52b5f Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 09:58:57 +0100 Subject: [PATCH 050/243] Add system repository for command line handling --- system_repository.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 system_repository.py diff --git a/system_repository.py b/system_repository.py new file mode 100644 index 0000000..18f5893 --- /dev/null +++ b/system_repository.py @@ -0,0 +1,22 @@ +import subprocess as sub + +class SystemRepository(): + + def __init__(self): + self.stdout = [""] + self.stderr = [""] + + def run(self, *args): + stream = sub.Popen(args, + stdout=sub.PIPE, + stderr=sub.PIPE, + text=True, + encoding="UTF-8") + self.stdout = stream.stdout.readlines() + self.stderr = stream.stderr.readlines() + + def run_checked(self, *args): + self.run(args) + + if len(self.stderr) > 0: + raise Exception(f"Command failed with: {self.stderr}") From 0f4899bf39782ca857403ec0142ec2e8db6eed7b Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 09:59:16 +0100 Subject: [PATCH 051/243] Fix typos in services.py --- services.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services.py b/services.py index 3792d41..542a2ba 100644 --- a/services.py +++ b/services.py @@ -1,5 +1,6 @@ -import version_repository -import release_type +from version_repository import VersionRepository +from release_type import ReleaseType + class InitReleaseService(): def __init__(self, commit_id, file): @@ -15,7 +16,7 @@ class InitReleaseService(): def get_version(self, release_type): self.version_repo = VersionRepository(self.file) - return repo.get_version(release_type) + return self.version_repo.get_version(release_type) def create_release_version(self): commit_message = self.read_commit_message(self.commit_id) From 0b3daf595a36346bd25e6f9d80defb634be9babe Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 09:59:35 +0100 Subject: [PATCH 052/243] WIP rework git handler --- git_handler.py | 69 ++++++++++---------------------------------------- 1 file changed, 13 insertions(+), 56 deletions(-) diff --git a/git_handler.py b/git_handler.py index 3f30551..95d2915 100644 --- a/git_handler.py +++ b/git_handler.py @@ -1,5 +1,7 @@ import os import subprocess as sub +from system_repository import SystemRepository +from release_type import ReleaseType # define semantics for release types by commit messages ## snapshot - snapshot release @@ -14,64 +16,19 @@ FORMAT = '"%h %s"' FORMAT_DEC = "%d" PRETTY_OPTION = '--pretty=' DECORATE_OPTION = '--decorate=full' - + + +# git log --oneline --format="%s %b" origin/master...HEAD + class GitRepo(): def __init__(self): - self.commits = None - self.tags = None + self.latest_commit = None + self.system_repository = SystemRepository() - def __clean_commit_string(self, commit_string): - return commit_string.replace('\"', "").replace('\n', "").split() + def get_latest_commit(self): + self.latest_commit = self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', '-n' + '1') - def __clean_tag_string(self, tag_string): - return tag_string.replace(" ", "").replace('\n', "") - - def get_tags(self): - stream = sub.Popen([GIT, - LOG, - PRETTY_OPTION + FORMAT_DEC, - DECORATE_OPTION], - stdout=sub.PIPE, - stderr=sub.PIPE, - text=True, - encoding="UTF-8") - stdout = stream.stdout.readlines() - stderr = stream.stderr.readlines() - - if len(stderr) > 0: - raise Exception(f"Git command failed with: {stderr}") - - return stdout - - def get_commits(self): - stream = sub.Popen([GIT, - LOG, - PRETTY_OPTION + FORMAT], - stdout=sub.PIPE, - stderr=sub.PIPE, - text=True, - encoding="UTF-8") - stdout = stream.stdout.readlines() - stderr = stream.stderr.readlines() - - if len(stderr) > 0: - raise Exception(f"Git command failed with: {stderr}") - - self.tags = self.get_tags() - self.commits = {} - - if len(self.tags) != len(stdout): - raise Exception("Tags list did not match commits list") - - for i, elem in enumerate(stdout): - commit_string = self.__clean_commit_string(elem) - - commit_id = commit_string[0] - commit_message = " ".join(commit_string[1:len(commit_string)]) - commit_tag = self.__clean_tag_string(self.tags[i]) - - self.commits[commit_id] = [commit_tag, commit_message] - - return self.commits - + def get_release_type_from_latest_commit(self): + if self.latest_commit is None: + self.get_latest_commit() From ac564ade7ecc55012b28ce9d9ece7166ef2032be Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 10:25:21 +0100 Subject: [PATCH 053/243] Update tests for git handler --- test/test_git_handler.py | 46 ++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/test/test_git_handler.py b/test/test_git_handler.py index 77d95c9..5623bc1 100644 --- a/test/test_git_handler.py +++ b/test/test_git_handler.py @@ -17,14 +17,48 @@ sys.path.append(parent) # now we can import the module in the parent # directory. -from git_handler import * +from git_repository import * +from release_type import ReleaseType -def test_git_handler(): +def test_git_repository(): # init - repo = GitRepo() - repo.get_commits() + commit_string = "Major bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() + + #test + assert release_type == ReleaseType.MAJOR + + + # init + commit_string = "MINOR bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() #test - assert type(repo.commits) == dict - assert repo.commits["decd36b"] == ["(tag:refs/tags/TEST)", "Initial commit"] + assert release_type == ReleaseType.MINOR + + # init + commit_string = "PATCH bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() + + # test + assert release_type == ReleaseType.PATCH + + # init + commit_string = "SNAPSHOT bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() + + #test + assert release_type == ReleaseType.SNAPSHOT + + # init + commit_string = "bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() + + #test + assert release_type == None From ff4cb702669b8aed14e2c840bcc0376bb463a114 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 10:26:25 +0100 Subject: [PATCH 054/243] Rename for consitency --- test/{test_git_handler.py => test_git_repository.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{test_git_handler.py => test_git_repository.py} (100%) diff --git a/test/test_git_handler.py b/test/test_git_repository.py similarity index 100% rename from test/test_git_handler.py rename to test/test_git_repository.py From 33fdf3073a236e477f9a752b6b7f215749dddcaf Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 10:26:49 +0100 Subject: [PATCH 055/243] Rename for consitency --- git_handler.py | 34 ---------------------------------- git_repository.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 34 deletions(-) delete mode 100644 git_handler.py create mode 100644 git_repository.py diff --git a/git_handler.py b/git_handler.py deleted file mode 100644 index 95d2915..0000000 --- a/git_handler.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -import subprocess as sub -from system_repository import SystemRepository -from release_type import ReleaseType - -# define semantics for release types by commit messages -## snapshot - snapshot release -## fix/patch - patch release -## bump - version bump release -## feature/feat/minor - minor release -## major/breaking - major release - -GIT = 'git' -LOG = 'log' -FORMAT = '"%h %s"' -FORMAT_DEC = "%d" -PRETTY_OPTION = '--pretty=' -DECORATE_OPTION = '--decorate=full' - - -# git log --oneline --format="%s %b" origin/master...HEAD - -class GitRepo(): - - def __init__(self): - self.latest_commit = None - self.system_repository = SystemRepository() - - def get_latest_commit(self): - self.latest_commit = self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', '-n' + '1') - - def get_release_type_from_latest_commit(self): - if self.latest_commit is None: - self.get_latest_commit() diff --git a/git_repository.py b/git_repository.py new file mode 100644 index 0000000..83c9ea5 --- /dev/null +++ b/git_repository.py @@ -0,0 +1,35 @@ +import os +import subprocess as sub +from system_repository import SystemRepository +from release_type import ReleaseType + +class GitRepository(): + + def __init__(self): + self.latest_commit = None + self.system_repository = SystemRepository() + + @classmethod + def create_from_commit_string(cls, commit_string): + inst = cls() + inst.latest_commit = commit_string + return inst + + def get_latest_commit(self): + self.latest_commit = self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', '-n' + '1') + + def get_release_type_from_latest_commit(self): + if self.latest_commit is None: + self.get_latest_commit() + + if ReleaseType.MAJOR.name in self.latest_commit.upper(): + return ReleaseType.MAJOR + elif ReleaseType.MINOR.name in self.latest_commit.upper(): + return ReleaseType.MINOR + elif ReleaseType.PATCH.name in self.latest_commit.upper(): + return ReleaseType.PATCH + elif ReleaseType.SNAPSHOT.name in self.latest_commit.upper(): + return ReleaseType.SNAPSHOT + else: + return None + From f94d1879dc321a2d1c864901862c92b57ea74722 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 10:27:07 +0100 Subject: [PATCH 056/243] Implement release type calculation --- services.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/services.py b/services.py index 542a2ba..8da851d 100644 --- a/services.py +++ b/services.py @@ -1,26 +1,24 @@ from version_repository import VersionRepository from release_type import ReleaseType +from git_repository import GitRepository class InitReleaseService(): def __init__(self, commit_id, file): self.commit_id = commit_id self.file = file - self.version_repo = None - - def __read_commit_message(self): - pass + self.version_repo = None def __calculate_release_type(self): - pass - + return GitRepository().get_release_type_from_latest_commit() + def get_version(self, release_type): self.version_repo = VersionRepository(self.file) return self.version_repo.get_version(release_type) def create_release_version(self): commit_message = self.read_commit_message(self.commit_id) - release_type = self.calculate_release_type(commit_message) + release_type = self.__calculate_release_type(commit_message) version = self.get_version(release_type).create_release_version() self.version_repo.write_file(version.get_version_string()) From e578878b82019af7f4ac7811c379a3db116fb8dc Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 10:27:29 +0100 Subject: [PATCH 057/243] WIP test the services --- test/test_services.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/test_services.py diff --git a/test/test_services.py b/test/test_services.py new file mode 100644 index 0000000..e69de29 From 167eec4620fb673dda94e6236625991da3158825 Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 10:57:24 +0100 Subject: [PATCH 058/243] Make services testable --- services.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/services.py b/services.py index 8da851d..37445b2 100644 --- a/services.py +++ b/services.py @@ -4,28 +4,29 @@ from git_repository import GitRepository class InitReleaseService(): - def __init__(self, commit_id, file): - self.commit_id = commit_id + def __init__(self, file): self.file = file self.version_repo = None - def __calculate_release_type(self): - return GitRepository().get_release_type_from_latest_commit() + def __calculate_release_type(self, commit_string = None): + if commit_string is None: + return GitRepository().get_release_type_from_latest_commit() + else: + return GitRepository.create_from_commit_string(commit_string).get_release_type_from_latest_commit() def get_version(self, release_type): self.version_repo = VersionRepository(self.file) return self.version_repo.get_version(release_type) - def create_release_version(self): - commit_message = self.read_commit_message(self.commit_id) - release_type = self.__calculate_release_type(commit_message) + def create_release_version(self, commit_string = None): + release_type = self.__calculate_release_type(commit_string) version = self.get_version(release_type).create_release_version() self.version_repo.write_file(version.get_version_string()) def create_bump_version(self): if self.version_repo == None: - raise Exception('VersionRepo was not created. Did you run create_lease_version()?') + raise Exception('VersionRepo was not created. Did you run create_release_version()?') version = self.get_version(ReleaseType.BUMP).create_bump_version() self.version_repo.write_file(version.get_version_string()) From fea572016106e079a8c548cf2be99fbc6c36cdb2 Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 10:57:42 +0100 Subject: [PATCH 059/243] Return version objects instead of mutating --- version.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/version.py b/version.py index 6439195..61d92f5 100644 --- a/version.py +++ b/version.py @@ -36,10 +36,14 @@ class Version(): return self.version_string def create_release_version(self): - if self.release_type == ReleaseType.PATCH: - self.is_snapshot = False - else: - self.increment() + release_version = Version(self.version_list, self.release_type) + release_version.is_snapshot = self.is_snapshot + release_version.increment() + return release_version def create_bump_version(self): - self.increment(ReleaseType.BUMP) + bump_version = Version(self.version_list, self.release_type) + bump_version.is_snapshot = self.is_snapshot + bump_version.release_type = ReleaseType.BUMP + bump_version.increment() + return bump_version From e2f562aa8c16cd90a86603f06dc34c96f5fc2e75 Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 10:57:55 +0100 Subject: [PATCH 060/243] Remove wildcard import --- test/test_git_repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_git_repository.py b/test/test_git_repository.py index 5623bc1..a1e9db8 100644 --- a/test/test_git_repository.py +++ b/test/test_git_repository.py @@ -17,7 +17,7 @@ sys.path.append(parent) # now we can import the module in the parent # directory. -from git_repository import * +from git_repository import GitRepository from release_type import ReleaseType def test_git_repository(): From 9250d366b97655f7477778c95a1bd7153ddef356 Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 10:58:11 +0100 Subject: [PATCH 061/243] Use newly created version object --- test/test_version_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_version_class.py b/test/test_version_class.py index 8880488..486a2a7 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -66,7 +66,7 @@ def test_gradle(tmp_path): # test repo = VersionRepository(f) version = repo.get_version(ReleaseType.SNAPSHOT) - version.create_release_version() + version = version.create_release_version() repo.write_file(version.get_version_string()) # check @@ -84,7 +84,7 @@ def test_json(tmp_path): # test repo = VersionRepository(f) version = repo.get_version(ReleaseType.SNAPSHOT) - version.create_release_version() + version = version.create_release_version() repo.write_file(version.get_version_string()) # check @@ -102,7 +102,7 @@ def test_clojure(tmp_path): # test repo = VersionRepository(f) version = repo.get_version(ReleaseType.SNAPSHOT) - version.create_release_version() + version = version.create_release_version() repo.write_file(version.get_version_string()) # check @@ -120,7 +120,7 @@ def test_python(tmp_path): # test repo = VersionRepository(f) version = repo.get_version(ReleaseType.SNAPSHOT) - version.create_release_version() + version = version.create_release_version() repo.write_file(version.get_version_string()) # check From 3bd8497522e90901898a68dc092d0e3d527bd4dc Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 10:58:27 +0100 Subject: [PATCH 062/243] WIP Create test_services --- test/test_services.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/test_services.py b/test/test_services.py index e69de29..085cf90 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -0,0 +1,39 @@ +from pathlib import Path +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +# now we can import the module in the parent +# directory. + +from services import InitReleaseService +from release_type import ReleaseType + +def test_init_release_service(tmp_path): + # init + file_name = 'config.json' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + release_service = InitReleaseService(f) + release_service.create_release_version(commit_string='Release MINOR') + + assert '"version": "123.124.1"' in f.read_text() + + release_service.create_bump_version() + + assert '"version": "123.124.2-SNAPSHOT"' in f.read_text() \ No newline at end of file From 1d2cb7882eac42134e93b56dc2dbcd0ba6d9931e Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 10:59:35 +0100 Subject: [PATCH 063/243] Fix test --- test/test_services.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_services.py b/test/test_services.py index 085cf90..45367f9 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -32,8 +32,8 @@ def test_init_release_service(tmp_path): release_service = InitReleaseService(f) release_service.create_release_version(commit_string='Release MINOR') - assert '"version": "123.124.1"' in f.read_text() + assert '"version": "123.124.0"' in f.read_text() release_service.create_bump_version() - assert '"version": "123.124.2-SNAPSHOT"' in f.read_text() \ No newline at end of file + assert '"version": "123.124.1-SNAPSHOT"' in f.read_text() \ No newline at end of file From 821e06f82a4d1c38a98c2f4b8d6ec0a9a9273da0 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 11:33:44 +0100 Subject: [PATCH 064/243] Pass VersionRepo to InitReleaseService Update Tests. --- services.py | 48 +++++++++++++++++++++++++++++-------------- test/test_services.py | 12 ++++++----- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/services.py b/services.py index 37445b2..35573bb 100644 --- a/services.py +++ b/services.py @@ -1,12 +1,14 @@ from version_repository import VersionRepository from release_type import ReleaseType from git_repository import GitRepository +from version import Version class InitReleaseService(): - def __init__(self, file): - self.file = file - self.version_repo = None + def __init__(self, version_repo: VersionRepository): + if version_repo is None: + raise Exception('VersionRepo was not created. Did you run create_release_version()?') + self.version_repo = version_repo def __calculate_release_type(self, commit_string = None): if commit_string is None: @@ -14,27 +16,43 @@ class InitReleaseService(): else: return GitRepository.create_from_commit_string(commit_string).get_release_type_from_latest_commit() - def get_version(self, release_type): - self.version_repo = VersionRepository(self.file) + def get_version(self, release_type): return self.version_repo.get_version(release_type) def create_release_version(self, commit_string = None): release_type = self.__calculate_release_type(commit_string) - version = self.get_version(release_type).create_release_version() - - self.version_repo.write_file(version.get_version_string()) + version = self.get_version(release_type).create_release_version() + return version - def create_bump_version(self): - if self.version_repo == None: - raise Exception('VersionRepo was not created. Did you run create_release_version()?') + def create_bump_version(self): version = self.get_version(ReleaseType.BUMP).create_bump_version() + return version + - self.version_repo.write_file(version.get_version_string()) - - - class PrepareReleaseService(): + + def __init__(self): + self.version_repo + pass + + def run_tests(self): # maybe auto? + pass + + def prepare_release(self, version: Version): + # self.version_repo.write_file(version.get_version_string()) # side effect + pass + + def prepare_bump(self, version: Version): + # self.version_repo.write_file(version.get_version_string()) # side effect + pass + + + # write + + + # add + # commit pass class TagAndPushReleaseService(): diff --git a/test/test_services.py b/test/test_services.py index 45367f9..a5deaa1 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -19,6 +19,7 @@ sys.path.append(parent) from services import InitReleaseService from release_type import ReleaseType +from version_repository import VersionRepository def test_init_release_service(tmp_path): # init @@ -29,11 +30,12 @@ def test_init_release_service(tmp_path): f = tmp_path / file_name f.write_text(contents) - release_service = InitReleaseService(f) - release_service.create_release_version(commit_string='Release MINOR') + repo = VersionRepository(f) + release_service = InitReleaseService(repo) + version = release_service.create_release_version(commit_string='Release MINOR') - assert '"version": "123.124.0"' in f.read_text() + assert "123.124.0" in version.get_version_string() - release_service.create_bump_version() + version = release_service.create_bump_version() - assert '"version": "123.124.1-SNAPSHOT"' in f.read_text() \ No newline at end of file + assert "123.123.457-SNAPSHOT" in version.get_version_string() \ No newline at end of file From 1112b1ad4902f098feefb8c90221276ced8efacb Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 11:34:27 +0100 Subject: [PATCH 065/243] WIP Implement prepare and tagandpush services --- build.py | 9 +++++++-- release_mixin.py | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/build.py b/build.py index 91d74dc..9e4d198 100644 --- a/build.py +++ b/build.py @@ -21,9 +21,14 @@ def initialize(project): config = create_release_mixin_config(CONFIG_FILE, COMMIT_ID) - build = MyBuild(project, config) - build.init() + build = MyBuild(project, config) + release_version, bump_version = build.init() + build.prepare() + build.tag_and_push() + + + @task diff --git a/release_mixin.py b/release_mixin.py index f83a49f..9b8a5d2 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -3,6 +3,7 @@ from ddadevops import DevopsBuild from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path from version import Version +from version_repository import VersionRepository def create_release_mixin_config(config, release_type, commit, file): @@ -16,14 +17,23 @@ class ReleaseMixin(DevopsBuild): def __init__(self, project, config): super().__init__(project, config) - release_mixin_config = config['ReleaseMixin'] - self.commit_id = release_mixin_config['commit_id'] + release_mixin_config = config['ReleaseMixin'] self.file = release_mixin_config['file'] + self.version_repo = VersionRepository(self.file) - def init(self): - release_and_bump_version = InitReleaseService(self.commit_id, self.file).get_version() + def init(self): # returns versions + release_and_bump_version = InitReleaseService(self.version_repo).get_version() return release_and_bump_version + def prepare(self, version: Version): # writes into files, add. commit + pass + + def tag_and_push(): # correct tag and do push + pass + + + + From ee8b49f078eb200f5b4127193b9f321de3fd2705 Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 12:04:46 +0100 Subject: [PATCH 066/243] Add some useful git functions --- git_repository.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/git_repository.py b/git_repository.py index 83c9ea5..f891646 100644 --- a/git_repository.py +++ b/git_repository.py @@ -1,5 +1,6 @@ import os import subprocess as sub +from pathlib import Path from system_repository import SystemRepository from release_type import ReleaseType @@ -33,3 +34,18 @@ class GitRepository(): else: return None + def get_current_branch(self): + self.system_repository.run_checked('git', 'branch', '--show-current') + + def add_file(self, file_path: Path): + self.system_repository.run_checked('git', 'add', file_path) + + def commit(self, commit_message: str): + self.system_repository.run_checked('git', 'commit', '-m', commit_message) + + def push(self): + self.system_repository.run_checked('git', 'push') + + def checkout(self, branch: str): + self.system_repository.run_checked('git', 'checkout', branch) + From 2d8ca4ecddbac8732c042089ca7c00e3fc60361e Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 12:05:02 +0100 Subject: [PATCH 067/243] Fix test_services --- test/test_services.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_services.py b/test/test_services.py index a5deaa1..f7ec74c 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -36,6 +36,6 @@ def test_init_release_service(tmp_path): assert "123.124.0" in version.get_version_string() - version = release_service.create_bump_version() + version = version.create_bump_version() - assert "123.123.457-SNAPSHOT" in version.get_version_string() \ No newline at end of file + assert "123.124.1-SNAPSHOT" in version.get_version_string() \ No newline at end of file From 238b0304b66336ce7f052d7163f08551b8eacf4c Mon Sep 17 00:00:00 2001 From: bom Date: Wed, 22 Feb 2023 12:05:34 +0100 Subject: [PATCH 068/243] Implement prepare for ReleaseMixin --- release_mixin.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 9b8a5d2..c78ec1f 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -1,15 +1,15 @@ - from ddadevops import DevopsBuild from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path from version import Version from version_repository import VersionRepository +from services import InitReleaseService, PrepareReleaseService, TagAndPushReleaseService +from git_repository import GitRepository - -def create_release_mixin_config(config, release_type, commit, file): - config.update({'ReleaseMixin': - {'commit_id': commit, - 'file': file}}) +def create_release_mixin_config(config, release_type, config_file, main_branch): + config.update({'ReleaseMixin': + {'main_branch': main_branch, + 'file': config_file}}) return config @@ -19,14 +19,28 @@ class ReleaseMixin(DevopsBuild): super().__init__(project, config) release_mixin_config = config['ReleaseMixin'] self.file = release_mixin_config['file'] + self.main_branch = release_mixin_config['main_branch'] self.version_repo = VersionRepository(self.file) def init(self): # returns versions - release_and_bump_version = InitReleaseService(self.version_repo).get_version() - return release_and_bump_version + init_service = InitReleaseService(self.version_repo) + release_version = init_service.create_release_version() + bump_version = release_version.create_bump_version() + return release_version, bump_version - def prepare(self, version: Version): # writes into files, add. commit - pass + def prepare(self, release_version: Version, bump_version: Version): # writes into files, add. commit + git_repository = GitRepository() + if self.main_branch not in git_repository.get_current_branch(): + raise Exception('Trying to release while not on main branch') + + self.version_repo.write_file(release_version.get_version_string()) + git_repository.add_file(self.file) + git_repository.commit(f'Release {release_version.get_version_string()}') + + self.version_repo.write_file(bump_version.get_version_string()) + git_repository.add_file(self.file) + git_repository.commit(f'Version bump {bump_version.get_version_string()}') + def tag_and_push(): # correct tag and do push pass From 03f8507968499c7ea19e4f86a4e9fbe1a9115230 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 14:10:19 +0100 Subject: [PATCH 069/243] Setup build.py --- build.py | 54 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/build.py b/build.py index 9e4d198..af78598 100644 --- a/build.py +++ b/build.py @@ -4,40 +4,42 @@ from version import * from release_mixin import * CONFIG_FILE = '' -COMMIT_ID = '' +MAIN_BRANCH = 'main' class MyBuild(ReleaseMixin): pass -def main(): - init_project() - @init def initialize(project): project.build_depends_on('ddadevops>=3.1.2') - - if COMMIT_ID == '': - COMMIT_ID = 'HEAD' - - config = create_release_mixin_config(CONFIG_FILE, COMMIT_ID) - - - build = MyBuild(project, config) - release_version, bump_version = build.init() - build.prepare() - build.tag_and_push() - - - - + config = create_release_mixin_config(CONFIG_FILE, MAIN_BRANCH) + build = MyBuild(project, config) + build.init() @task -def prepare(project): +def release(project): build = get_devops_build(project) - build.prepare_release() -@task -def tag_and_push(project): - build = get_devops_build(project) - build.tag_and_push() - + prepare_release(build) + tag_and_push_release(build) + + prepare_version_bump(build) + tag_and_push_version_bump(build) + +def prepare_release(build): + release_version = build.release_version + build.prepare(release_version) + +def tag_and_push_release(build): + release_version = build.release_version + build.tag_and_push(release_version) + +def prepare_version_bump(build): + bump_version = build.bump_version + build.prepare(bump_version) + +def tag_and_push_version_bump(build): + bump_version = build.bump_version + build.tag_and_push(bump_version) + + From a07c23434e506e20634e9723a5ceb3f8cb049b35 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 14:10:53 +0100 Subject: [PATCH 070/243] Implement tag_annotated and bugfix --- git_repository.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/git_repository.py b/git_repository.py index f891646..0622e9f 100644 --- a/git_repository.py +++ b/git_repository.py @@ -17,7 +17,8 @@ class GitRepository(): return inst def get_latest_commit(self): - self.latest_commit = self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', '-n' + '1') + self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', '-n' + '1') + self.latest_commit = self.system_repository.stdout def get_release_type_from_latest_commit(self): if self.latest_commit is None: @@ -34,6 +35,9 @@ class GitRepository(): else: return None + def tag_annotated(self, annotation: str, message: str): + self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message) + def get_current_branch(self): self.system_repository.run_checked('git', 'branch', '--show-current') @@ -48,4 +52,3 @@ class GitRepository(): def checkout(self, branch: str): self.system_repository.run_checked('git', 'checkout', branch) - From 7d7347653e1ed1e9aa70f9784cfb9217bf833203 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 14:11:39 +0100 Subject: [PATCH 071/243] Update release_mixin methods --- release_mixin.py | 73 ++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index c78ec1f..b1a6d8f 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -6,12 +6,19 @@ from version_repository import VersionRepository from services import InitReleaseService, PrepareReleaseService, TagAndPushReleaseService from git_repository import GitRepository -def create_release_mixin_config(config, release_type, config_file, main_branch): - config.update({'ReleaseMixin': - {'main_branch': main_branch, - 'file': config_file}}) +def create_release_mixin_config(config_file, main_branch) -> dict: + config = {} + config.update( + {'ReleaseMixin': + {'main_branch': main_branch, + 'file': config_file}}) return config +def add_versions(config, release_version, bump_version) -> dict: + config['ReleaseMixin'].update( + {'release_version': release_version, + 'bump_version': bump_version}) + return config class ReleaseMixin(DevopsBuild): @@ -21,40 +28,40 @@ class ReleaseMixin(DevopsBuild): self.file = release_mixin_config['file'] self.main_branch = release_mixin_config['main_branch'] self.version_repo = VersionRepository(self.file) + self.release_version = None + self.bump_version = None - def init(self): # returns versions + def init(self): init_service = InitReleaseService(self.version_repo) - release_version = init_service.create_release_version() - bump_version = release_version.create_bump_version() - return release_version, bump_version + self.release_version = init_service.create_release_version() + self.bump_version = self.release_version.create_bump_version() + return self.release_version, self.bump_version - def prepare(self, release_version: Version, bump_version: Version): # writes into files, add. commit + def prepare(self, version): git_repository = GitRepository() if self.main_branch not in git_repository.get_current_branch(): raise Exception('Trying to release while not on main branch') - - self.version_repo.write_file(release_version.get_version_string()) - git_repository.add_file(self.file) - git_repository.commit(f'Release {release_version.get_version_string()}') - - self.version_repo.write_file(bump_version.get_version_string()) - git_repository.add_file(self.file) - git_repository.commit(f'Version bump {bump_version.get_version_string()}') + self.version_repo.write_file(version.get_version_string()) + git_repository.add_file(self.file) + match version.release_type: + case None: + raise Exception('Release type not set but trying to commit.') + case ReleaseType.BUMP: + git_repository.commit(f'Version bump') + case _: + git_repository.commit(f'Release {version.get_version_string()}') - def tag_and_push(): # correct tag and do push - pass - - - - - - - - - - - - - - + def tag_and_push(self, version): + git_repository = GitRepository() + match version.release_type: + case None: + raise Exception('Release type not set but trying to tag and push.') + case ReleaseType.BUMP: + annotation = 'v' + version.get_version_string() + message = 'Version bump' + case _: + annotation = 'v' + self.release_version.get_version_string() + message = 'Release' + annotation + git_repository.tag_annotated(annotation, message) + git_repository.push() From 0d0bde4b9e05dd83b58f1eaa2032dcf707c608f3 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 14:12:03 +0100 Subject: [PATCH 072/243] Remove whitespaces --- system_repository.py | 2 +- test/test_version_class.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/system_repository.py b/system_repository.py index 18f5893..31c9f78 100644 --- a/system_repository.py +++ b/system_repository.py @@ -5,7 +5,7 @@ class SystemRepository(): def __init__(self): self.stdout = [""] self.stderr = [""] - + def run(self, *args): stream = sub.Popen(args, stdout=sub.PIPE, diff --git a/test/test_version_class.py b/test/test_version_class.py index 486a2a7..24c9d33 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -5,15 +5,15 @@ import os # getting the name of the directory # where the this file is present. current = os.path.dirname(os.path.realpath(__file__)) - + # Getting the parent directory name # where the current directory is present. parent = os.path.dirname(current) - + # adding the parent directory to # the sys.path. sys.path.append(parent) - + # now we can import the module in the parent # directory. From 1e2593e35853f87db4bc4af0ffaae5a6e4448ced Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 14:12:54 +0100 Subject: [PATCH 073/243] No settings in repo --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7f3a111..6b02ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ go.work __pycache__ .clj-kondo/ -.lsp/ \ No newline at end of file +.lsp/ + +# vs code settings +.vscode \ No newline at end of file From d22dbbf050a4dac6e525c7562bc3d60d374f4b00 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 15:14:48 +0100 Subject: [PATCH 074/243] Add import, remove return --- release_mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_mixin.py b/release_mixin.py index b1a6d8f..85c63c7 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -5,6 +5,7 @@ from version import Version from version_repository import VersionRepository from services import InitReleaseService, PrepareReleaseService, TagAndPushReleaseService from git_repository import GitRepository +from release_type import ReleaseType def create_release_mixin_config(config_file, main_branch) -> dict: config = {} @@ -35,7 +36,6 @@ class ReleaseMixin(DevopsBuild): init_service = InitReleaseService(self.version_repo) self.release_version = init_service.create_release_version() self.bump_version = self.release_version.create_bump_version() - return self.release_version, self.bump_version def prepare(self, version): git_repository = GitRepository() From 294d664bb8d24a250daf3060cd94b18148002a8d Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 15:15:23 +0100 Subject: [PATCH 075/243] WIP Add test for release_mixin --- test/test_release_mixin.py | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 test/test_release_mixin.py diff --git a/test/test_release_mixin.py b/test/test_release_mixin.py new file mode 100644 index 0000000..601f3fd --- /dev/null +++ b/test/test_release_mixin.py @@ -0,0 +1,56 @@ +import sys +import os +from pathlib import Path +from ddadevops import * + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +# now we can import the module in the parent +# directory. + +from pybuilder.core import task, init, Project +from version import * +from release_mixin import ReleaseMixin, create_release_mixin_config + +MAIN_BRANCH = 'main' + + +class MyBuild(ReleaseMixin): + pass + +def initialize(project, CONFIG_FILE): + project.build_depends_on('ddadevops>=3.1.2') + config = create_release_mixin_config(CONFIG_FILE, MAIN_BRANCH) + build = MyBuild(project, config) + return build + +def test_release_mixin(tmp_path): + + with open(f'test/resources/config.json', 'r') as json_file: + contents = json_file.read() + + CONFIG_FILE = tmp_path / "config.json" + CONFIG_FILE.write_text(contents) + + base_dir = "." + project = Project(base_dir) + + # init + build = initialize(project, CONFIG_FILE) + build.init() + release_version = build.release_version + bump_version = build.bump_version + + # test + assert "123.124.0" in release_version.get_version_string() + assert "123.124.1-SNAPSHOT" in bump_version.get_version_string() \ No newline at end of file From 99b8884d1120fb349845b2f85059f3c473e6708a Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 15:15:56 +0100 Subject: [PATCH 076/243] WIP implement prepare release service --- services.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services.py b/services.py index 35573bb..36c2ff1 100644 --- a/services.py +++ b/services.py @@ -32,9 +32,9 @@ class InitReleaseService(): class PrepareReleaseService(): - def __init__(self): - self.version_repo - pass + def __init__(self, version_repo: VersionRepository): + self.version_repo = version_repo + def run_tests(self): # maybe auto? pass From d759036989ea180aac4572bfc6a64b2e85bfb255 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 16:19:36 +0100 Subject: [PATCH 077/243] Create realistic test conditions --- test/test_release_mixin.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/test/test_release_mixin.py b/test/test_release_mixin.py index 601f3fd..e83ad85 100644 --- a/test/test_release_mixin.py +++ b/test/test_release_mixin.py @@ -23,7 +23,10 @@ from version import * from release_mixin import ReleaseMixin, create_release_mixin_config MAIN_BRANCH = 'main' - +STAGE = 'test' +PROJECT_ROOT_PATH = '.' +MODULE = 'test' +BUILD_DIR_NAME = "build_dir" class MyBuild(ReleaseMixin): pass @@ -31,11 +34,16 @@ class MyBuild(ReleaseMixin): def initialize(project, CONFIG_FILE): project.build_depends_on('ddadevops>=3.1.2') config = create_release_mixin_config(CONFIG_FILE, MAIN_BRANCH) + config.update({'stage': STAGE}) + config.update({'module': MODULE}) + config.update({'project_root_path': PROJECT_ROOT_PATH}) + config.update({'build_dir_name': BUILD_DIR_NAME}) build = MyBuild(project, config) return build def test_release_mixin(tmp_path): + #init with open(f'test/resources/config.json', 'r') as json_file: contents = json_file.read() @@ -46,11 +54,16 @@ def test_release_mixin(tmp_path): project = Project(base_dir) # init - build = initialize(project, CONFIG_FILE) + build = initialize(project, CONFIG_FILE) + build.commit_string = "MAJOR bla" build.init() release_version = build.release_version + + # test + assert "124.0.0" in release_version.get_version_string() + + # init bump_version = build.bump_version # test - assert "123.124.0" in release_version.get_version_string() - assert "123.124.1-SNAPSHOT" in bump_version.get_version_string() \ No newline at end of file + assert "124.0.1-SNAPSHOT" in bump_version.get_version_string() \ No newline at end of file From c91bbe787b365d0d7fb9813d7b8c062458442f03 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 16:20:46 +0100 Subject: [PATCH 078/243] Create string from return value stdout returns a list of strings. This needed to be handled. --- git_repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git_repository.py b/git_repository.py index 0622e9f..4fdfc64 100644 --- a/git_repository.py +++ b/git_repository.py @@ -17,8 +17,8 @@ class GitRepository(): return inst def get_latest_commit(self): - self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', '-n' + '1') - self.latest_commit = self.system_repository.stdout + self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', '-n 1') + self.latest_commit = " ".join(self.system_repository.stdout) # returns a list of strings otherwise def get_release_type_from_latest_commit(self): if self.latest_commit is None: From fa73c52fe2033bc96115e087f48b7c6f782f7611 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 16:23:03 +0100 Subject: [PATCH 079/243] Remove asterisk on destructured args Otherwise we would create a tuple containing a tuple of arguments and an empty tuple. --- system_repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system_repository.py b/system_repository.py index 31c9f78..914276f 100644 --- a/system_repository.py +++ b/system_repository.py @@ -6,7 +6,7 @@ class SystemRepository(): self.stdout = [""] self.stderr = [""] - def run(self, *args): + def run(self, args): stream = sub.Popen(args, stdout=sub.PIPE, stderr=sub.PIPE, From 2fe05b0e06342cea2af501e1c0755bde0d2f2506 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 22 Feb 2023 16:25:18 +0100 Subject: [PATCH 080/243] Use deep copy of release_version object Release version is modified again, when calling create_bump_version on it. --- release_mixin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 85c63c7..48afeaf 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -1,3 +1,4 @@ +import copy from ddadevops import DevopsBuild from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path @@ -31,11 +32,13 @@ class ReleaseMixin(DevopsBuild): self.version_repo = VersionRepository(self.file) self.release_version = None self.bump_version = None + self.commit_string = None def init(self): init_service = InitReleaseService(self.version_repo) - self.release_version = init_service.create_release_version() - self.bump_version = self.release_version.create_bump_version() + self.release_version = init_service.create_release_version(self.commit_string) + release_version_copy = copy.deepcopy(self.release_version) # otherwise we'll modify the release_version again + self.bump_version = release_version_copy.create_bump_version() def prepare(self, version): git_repository = GitRepository() From 0df4db54d818c65cc496174a07d308be71b8fef6 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 14:16:17 +0100 Subject: [PATCH 081/243] Copy the version list when creating new Version --- release_mixin.py | 3 +-- version.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 48afeaf..fbf5756 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -37,8 +37,7 @@ class ReleaseMixin(DevopsBuild): def init(self): init_service = InitReleaseService(self.version_repo) self.release_version = init_service.create_release_version(self.commit_string) - release_version_copy = copy.deepcopy(self.release_version) # otherwise we'll modify the release_version again - self.bump_version = release_version_copy.create_bump_version() + self.bump_version = self.release_version.create_bump_version() def prepare(self, version): git_repository = GitRepository() diff --git a/version.py b/version.py index 61d92f5..7dea4f7 100644 --- a/version.py +++ b/version.py @@ -36,14 +36,13 @@ class Version(): return self.version_string def create_release_version(self): - release_version = Version(self.version_list, self.release_type) + release_version = Version(self.version_list.copy(), self.release_type) release_version.is_snapshot = self.is_snapshot release_version.increment() return release_version def create_bump_version(self): - bump_version = Version(self.version_list, self.release_type) + bump_version = Version(self.version_list.copy(), ReleaseType.BUMP) bump_version.is_snapshot = self.is_snapshot - bump_version.release_type = ReleaseType.BUMP bump_version.increment() return bump_version From 992c72bda3308eb2be9761e31a17a1ae3f81d616 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 14:23:32 +0100 Subject: [PATCH 082/243] Give 'file' a more descriptive name --- release_mixin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index fbf5756..4b8dc38 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -13,7 +13,7 @@ def create_release_mixin_config(config_file, main_branch) -> dict: config.update( {'ReleaseMixin': {'main_branch': main_branch, - 'file': config_file}}) + 'config_file': config_file}}) return config def add_versions(config, release_version, bump_version) -> dict: @@ -27,9 +27,9 @@ class ReleaseMixin(DevopsBuild): def __init__(self, project, config): super().__init__(project, config) release_mixin_config = config['ReleaseMixin'] - self.file = release_mixin_config['file'] + self.config_file = release_mixin_config['config_file'] self.main_branch = release_mixin_config['main_branch'] - self.version_repo = VersionRepository(self.file) + self.version_repo = VersionRepository(self.config_file) self.release_version = None self.bump_version = None self.commit_string = None @@ -45,7 +45,7 @@ class ReleaseMixin(DevopsBuild): raise Exception('Trying to release while not on main branch') self.version_repo.write_file(version.get_version_string()) - git_repository.add_file(self.file) + git_repository.add_file(self.config_file) match version.release_type: case None: raise Exception('Release type not set but trying to commit.') From 1ce4bd9c55c2d888665dc9c05fc9df931f3ded80 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 14:38:54 +0100 Subject: [PATCH 083/243] Remove release_type as requirement for Version Require it in functions that actually use it instead --- services.py | 15 +++++++-------- test/test_version_class.py | 36 ++++++++++++++++++------------------ version.py | 17 ++++++++--------- version_repository.py | 4 ++-- 4 files changed, 35 insertions(+), 37 deletions(-) diff --git a/services.py b/services.py index 36c2ff1..212f016 100644 --- a/services.py +++ b/services.py @@ -5,7 +5,7 @@ from version import Version class InitReleaseService(): - def __init__(self, version_repo: VersionRepository): + def __init__(self, version_repo: VersionRepository): if version_repo is None: raise Exception('VersionRepo was not created. Did you run create_release_version()?') self.version_repo = version_repo @@ -16,25 +16,24 @@ class InitReleaseService(): else: return GitRepository.create_from_commit_string(commit_string).get_release_type_from_latest_commit() - def get_version(self, release_type): - return self.version_repo.get_version(release_type) + def get_version(self): + return self.version_repo.get_version() def create_release_version(self, commit_string = None): release_type = self.__calculate_release_type(commit_string) - version = self.get_version(release_type).create_release_version() + version = self.get_version().create_release_version(release_type) return version - def create_bump_version(self): - version = self.get_version(ReleaseType.BUMP).create_bump_version() + def create_bump_version(self): + version = self.get_version().create_bump_version() return version - class PrepareReleaseService(): def __init__(self, version_repo: VersionRepository): self.version_repo = version_repo - + def run_tests(self): # maybe auto? pass diff --git a/test/test_version_class.py b/test/test_version_class.py index 24c9d33..4f1ac6b 100644 --- a/test/test_version_class.py +++ b/test/test_version_class.py @@ -22,33 +22,33 @@ from version_repository import VersionRepository from release_type import ReleaseType def test_version(): - version = Version([1, 2, 3], ReleaseType.SNAPSHOT) + version = Version([1, 2, 3]) - version.increment() + 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([1, 2, 3], ReleaseType.BUMP) - version.increment() + version = Version([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([1, 2, 3], ReleaseType.PATCH) - version.increment() + version = Version([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([1, 2, 3], ReleaseType.MINOR) - version.increment() + version = Version([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([1, 2, 3], ReleaseType.MAJOR) - version.increment() + version = Version([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 @@ -65,8 +65,8 @@ def test_gradle(tmp_path): # test repo = VersionRepository(f) - version = repo.get_version(ReleaseType.SNAPSHOT) - version = version.create_release_version() + version = repo.get_version() + version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) # check @@ -83,8 +83,8 @@ def test_json(tmp_path): # test repo = VersionRepository(f) - version = repo.get_version(ReleaseType.SNAPSHOT) - version = version.create_release_version() + version = repo.get_version() + version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) # check @@ -101,8 +101,8 @@ def test_clojure(tmp_path): # test repo = VersionRepository(f) - version = repo.get_version(ReleaseType.SNAPSHOT) - version = version.create_release_version() + version = repo.get_version() + version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) # check @@ -119,8 +119,8 @@ def test_python(tmp_path): # test repo = VersionRepository(f) - version = repo.get_version(ReleaseType.SNAPSHOT) - version = version.create_release_version() + version = repo.get_version() + version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) # check diff --git a/version.py b/version.py index 7dea4f7..3cfca62 100644 --- a/version.py +++ b/version.py @@ -3,15 +3,14 @@ from file_handlers import FileHandler class Version(): - def __init__(self, version_list: list, release_type: ReleaseType): + def __init__(self, version_list: list): self.version_list = version_list - self.release_type = release_type self.version_string = None self.is_snapshot = None - def increment(self): + def increment(self, release_type: ReleaseType): self.is_snapshot = False - match self.release_type: + match release_type: case ReleaseType.BUMP: self.is_snapshot = True self.version_list[ReleaseType.PATCH.value] += 1 @@ -35,14 +34,14 @@ class Version(): self.version_string += "-SNAPSHOT" return self.version_string - def create_release_version(self): - release_version = Version(self.version_list.copy(), self.release_type) + def create_release_version(self, release_type: ReleaseType): + release_version = Version(self.version_list.copy()) release_version.is_snapshot = self.is_snapshot - release_version.increment() + release_version.increment(release_type) return release_version def create_bump_version(self): - bump_version = Version(self.version_list.copy(), ReleaseType.BUMP) + bump_version = Version(self.version_list.copy()) bump_version.is_snapshot = self.is_snapshot - bump_version.increment() + bump_version.increment(ReleaseType.BUMP) return bump_version diff --git a/version_repository.py b/version_repository.py index b135b79..2c3ec63 100644 --- a/version_repository.py +++ b/version_repository.py @@ -21,11 +21,11 @@ class VersionRepository(): version_list, is_snapshot = self.file_handler.parse() return version_list, is_snapshot - def get_version(self, release_type): + def get_version(self): self.file_handler = self.load_file() version_list, is_snapshot = self.parse_file() - version = Version(version_list, release_type) + version = Version(version_list) version.is_snapshot = is_snapshot return version From c58ddf70dac4b5d66db000b8298f54bb5815c0cf Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 14:40:10 +0100 Subject: [PATCH 084/243] Compact updating config into one call --- test/test_release_mixin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_release_mixin.py b/test/test_release_mixin.py index e83ad85..4b129c6 100644 --- a/test/test_release_mixin.py +++ b/test/test_release_mixin.py @@ -34,10 +34,10 @@ class MyBuild(ReleaseMixin): def initialize(project, CONFIG_FILE): project.build_depends_on('ddadevops>=3.1.2') config = create_release_mixin_config(CONFIG_FILE, MAIN_BRANCH) - config.update({'stage': STAGE}) - config.update({'module': MODULE}) - config.update({'project_root_path': PROJECT_ROOT_PATH}) - config.update({'build_dir_name': BUILD_DIR_NAME}) + config.update({'stage': STAGE, + 'module': MODULE, + 'project_root_path': PROJECT_ROOT_PATH, + 'build_dir_name': BUILD_DIR_NAME}) build = MyBuild(project, config) return build From fa6beebc4e2e838b9681f6b37d67281c9d9b9458 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 15:07:22 +0100 Subject: [PATCH 085/243] Move commit logic to prepare_release_service --- release_mixin.py | 23 +++++++++-------------- services.py | 35 ++++++++++++++++++----------------- test/test_release_mixin.py | 2 +- 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 4b8dc38..b31e838 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -30,29 +30,24 @@ class ReleaseMixin(DevopsBuild): self.config_file = release_mixin_config['config_file'] self.main_branch = release_mixin_config['main_branch'] self.version_repo = VersionRepository(self.config_file) + self.git_repo = GitRepository() self.release_version = None self.bump_version = None self.commit_string = None - def init(self): + def init_release(self): init_service = InitReleaseService(self.version_repo) self.release_version = init_service.create_release_version(self.commit_string) self.bump_version = self.release_version.create_bump_version() - def prepare(self, version): - git_repository = GitRepository() - if self.main_branch not in git_repository.get_current_branch(): - raise Exception('Trying to release while not on main branch') + def prepare_release(self): + prepare_release_service = PrepareReleaseService(self.version_repo, self.config_file, self.main_branch) + if self.release_version is None or self.bump_version is None: + raise Exception('prepare_release was called before init_release') - self.version_repo.write_file(version.get_version_string()) - git_repository.add_file(self.config_file) - match version.release_type: - case None: - raise Exception('Release type not set but trying to commit.') - case ReleaseType.BUMP: - git_repository.commit(f'Version bump') - case _: - git_repository.commit(f'Release {version.get_version_string()}') + # prepare_release_service.run_tests() # not implemented + prepare_release_service.write_and_commit_release(self.release_version) + prepare_release_service.write_and_commit_bump(self.bump_version) def tag_and_push(self, version): git_repository = GitRepository() diff --git a/services.py b/services.py index 212f016..04eac74 100644 --- a/services.py +++ b/services.py @@ -1,3 +1,4 @@ +from pathlib import Path from version_repository import VersionRepository from release_type import ReleaseType from git_repository import GitRepository @@ -31,28 +32,28 @@ class InitReleaseService(): class PrepareReleaseService(): - def __init__(self, version_repo: VersionRepository): - self.version_repo = version_repo - + def __init__(self, version_repository: VersionRepository, git_repository: GitRepository, config_file: Path, main_branch: str): + self.version_repository = version_repository + self.git_repository = git_repository + self.main_branch = main_branch + self.config_file = config_file def run_tests(self): # maybe auto? - pass + raise NotImplementedError - def prepare_release(self, version: Version): - # self.version_repo.write_file(version.get_version_string()) # side effect - pass + def __write_and_commit_version(self, version: Version, commit_message: str): + if self.main_branch != self.git_repository.get_current_branch(): + raise Exception('Trying to release while not on main branch') + + self.version_repository.write_file(version.get_version_string()) + self.git_repository.add_file(self.config_file) + self.git_repository.commit(commit_message) - def prepare_bump(self, version: Version): - # self.version_repo.write_file(version.get_version_string()) # side effect - pass + def write_and_commit_release(self, release_version: Version): + self.__write_and_commit_version(release_version, commit_message=f'Release {release_version.get_version_string()}') - - # write - - - # add - # commit - pass + def write_and_commit_bump(self, bump_version: Version): + self.__write_and_commit_version(bump_version, commit_message=f'Version bump') class TagAndPushReleaseService(): pass diff --git a/test/test_release_mixin.py b/test/test_release_mixin.py index 4b129c6..67b25a9 100644 --- a/test/test_release_mixin.py +++ b/test/test_release_mixin.py @@ -56,7 +56,7 @@ def test_release_mixin(tmp_path): # init build = initialize(project, CONFIG_FILE) build.commit_string = "MAJOR bla" - build.init() + build.init_release() release_version = build.release_version # test From e7152301dddc91c7667f544d57664f2b01b28e43 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 15:33:00 +0100 Subject: [PATCH 086/243] Add utility functions to git_repository --- git_repository.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/git_repository.py b/git_repository.py index 4fdfc64..a448847 100644 --- a/git_repository.py +++ b/git_repository.py @@ -16,9 +16,14 @@ class GitRepository(): inst.latest_commit = commit_string return inst + def get_latest_n_commits(self, n: int): + self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', f'-n {n}') + return self.system_repository.stdout + def get_latest_commit(self): - self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', '-n 1') - self.latest_commit = " ".join(self.system_repository.stdout) # returns a list of strings otherwise + output = self.get_latest_n_commits(1) + self.latest_commit = " ".join(output) # returns a list of strings otherwise + return self.latest_commit def get_release_type_from_latest_commit(self): if self.latest_commit is None: @@ -38,6 +43,9 @@ class GitRepository(): def tag_annotated(self, annotation: str, message: str): self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message) + def tag_annotated(self, annotation: str, message: str, count: int): + self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') + def get_current_branch(self): self.system_repository.run_checked('git', 'branch', '--show-current') From 4a23b0b800c61ec764f1fa9492e5b5b9879a0bef Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 15:33:34 +0100 Subject: [PATCH 087/243] Implement and use TagAndPushReleaseService --- release_mixin.py | 16 +++------------- services.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index b31e838..44adc92 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -49,16 +49,6 @@ class ReleaseMixin(DevopsBuild): prepare_release_service.write_and_commit_release(self.release_version) prepare_release_service.write_and_commit_bump(self.bump_version) - def tag_and_push(self, version): - git_repository = GitRepository() - match version.release_type: - case None: - raise Exception('Release type not set but trying to tag and push.') - case ReleaseType.BUMP: - annotation = 'v' + version.get_version_string() - message = 'Version bump' - case _: - annotation = 'v' + self.release_version.get_version_string() - message = 'Release' + annotation - git_repository.tag_annotated(annotation, message) - git_repository.push() + def tag_and_push(self): + tag_and_push_release_service = TagAndPushReleaseService(self.git_repo) + tag_and_push_release_service.tag_and_push_release(self.release_version) diff --git a/services.py b/services.py index 04eac74..0751046 100644 --- a/services.py +++ b/services.py @@ -56,5 +56,14 @@ class PrepareReleaseService(): self.__write_and_commit_version(bump_version, commit_message=f'Version bump') class TagAndPushReleaseService(): - pass + + def __init__(self, git_repository: GitRepository): + self.git_repository = git_repository + + def tag_and_push_release(self, release_version: Version): + annotation = 'v' + release_version.get_version_string() + message = 'Release ' + annotation + self.git_repository.tag_annotated(annotation, message, 1) + self.git_repository.push() + From a109759c68957cf701c701191a898908b4489491 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 16:30:43 +0100 Subject: [PATCH 088/243] Return stdout from git_repository functions --- git_repository.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/git_repository.py b/git_repository.py index a448847..e835384 100644 --- a/git_repository.py +++ b/git_repository.py @@ -42,21 +42,28 @@ class GitRepository(): def tag_annotated(self, annotation: str, message: str): self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message) + return self.system_repository.stdout def tag_annotated(self, annotation: str, message: str, count: int): self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') + return self.system_repository.stdout def get_current_branch(self): self.system_repository.run_checked('git', 'branch', '--show-current') + return ''.join(self.system_repository.stdout).rstrip() def add_file(self, file_path: Path): self.system_repository.run_checked('git', 'add', file_path) + return self.system_repository.stdout def commit(self, commit_message: str): self.system_repository.run_checked('git', 'commit', '-m', commit_message) + return self.system_repository.stdout def push(self): self.system_repository.run_checked('git', 'push') + return self.system_repository.stdout def checkout(self, branch: str): self.system_repository.run_checked('git', 'checkout', branch) + return self.system_repository.stdout From b93772d3a1d7df1b9ee660ee2f7f6629f39f30e4 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 16:31:33 +0100 Subject: [PATCH 089/243] Fix naming and arguments in release_mixin --- release_mixin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 44adc92..13b174a 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -41,7 +41,7 @@ class ReleaseMixin(DevopsBuild): self.bump_version = self.release_version.create_bump_version() def prepare_release(self): - prepare_release_service = PrepareReleaseService(self.version_repo, self.config_file, self.main_branch) + prepare_release_service = PrepareReleaseService(self.version_repo, self.git_repo, self.config_file, self.main_branch) if self.release_version is None or self.bump_version is None: raise Exception('prepare_release was called before init_release') @@ -49,6 +49,6 @@ class ReleaseMixin(DevopsBuild): prepare_release_service.write_and_commit_release(self.release_version) prepare_release_service.write_and_commit_bump(self.bump_version) - def tag_and_push(self): + def tag_and_push_release(self): tag_and_push_release_service = TagAndPushReleaseService(self.git_repo) tag_and_push_release_service.tag_and_push_release(self.release_version) From 0cdf4b4c9c8e19264509b01c93f9de4b175cf3a0 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 23 Feb 2023 16:34:22 +0100 Subject: [PATCH 090/243] Update files for in project release testing 1. commit anything and include one release type 2. run `pyb release` in project root 3. check that release commit and version bump commit were added 4. reset with `git reset origin/main` --- build.py | 57 ++++++++++++++++++++++++++++------------------------- config.json | 3 +++ services.py | 2 +- 3 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 config.json diff --git a/build.py b/build.py index af78598..4e5ea5f 100644 --- a/build.py +++ b/build.py @@ -1,10 +1,29 @@ +import sys +import os +from pathlib import Path +from ddadevops import * + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# adding the current directory to +# the sys.path. +sys.path.append(current) + +# now we can import the module in the current +# directory. + from pybuilder.core import task, init from ddadevops import * -from version import * -from release_mixin import * +from release_mixin import ReleaseMixin, create_release_mixin_config -CONFIG_FILE = '' +CONFIG_FILE = Path('config.json') MAIN_BRANCH = 'main' +STAGE = 'test' +PROJECT_ROOT_PATH = '.' +MODULE = 'test' +BUILD_DIR_NAME = "build_dir" class MyBuild(ReleaseMixin): pass @@ -13,33 +32,17 @@ class MyBuild(ReleaseMixin): def initialize(project): project.build_depends_on('ddadevops>=3.1.2') config = create_release_mixin_config(CONFIG_FILE, MAIN_BRANCH) + config.update({'stage': STAGE, + 'module': MODULE, + 'project_root_path': PROJECT_ROOT_PATH, + 'build_dir_name': BUILD_DIR_NAME}) build = MyBuild(project, config) - build.init() + build.initialize_build_dir() @task def release(project): build = get_devops_build(project) - prepare_release(build) - tag_and_push_release(build) - - prepare_version_bump(build) - tag_and_push_version_bump(build) - -def prepare_release(build): - release_version = build.release_version - build.prepare(release_version) - -def tag_and_push_release(build): - release_version = build.release_version - build.tag_and_push(release_version) - -def prepare_version_bump(build): - bump_version = build.bump_version - build.prepare(bump_version) - -def tag_and_push_version_bump(build): - bump_version = build.bump_version - build.tag_and_push(bump_version) - - + build.init_release() + build.prepare_release() + build.tag_and_push_release() diff --git a/config.json b/config.json new file mode 100644 index 0000000..6068f8d --- /dev/null +++ b/config.json @@ -0,0 +1,3 @@ +{ + "version": "123.125.1-SNAPSHOT" +} \ No newline at end of file diff --git a/services.py b/services.py index 0751046..3ff28de 100644 --- a/services.py +++ b/services.py @@ -64,6 +64,6 @@ class TagAndPushReleaseService(): annotation = 'v' + release_version.get_version_string() message = 'Release ' + annotation self.git_repository.tag_annotated(annotation, message, 1) - self.git_repository.push() + # self.git_repository.push() From 4f499bd1f47aa40a8ba54d448a9e1c039d30432e Mon Sep 17 00:00:00 2001 From: jerger Date: Fri, 24 Feb 2023 10:00:11 +0100 Subject: [PATCH 091/243] ignore target --- .gitignore | 1 + .../stale/leiningen.core.classpath.extract-native-dependencies | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 target/default+test/stale/leiningen.core.classpath.extract-native-dependencies diff --git a/.gitignore b/.gitignore index 6b02ccb..58f5cd3 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ __pycache__ .clj-kondo/ .lsp/ +target/ # vs code settings .vscode \ No newline at end of file diff --git a/target/default+test/stale/leiningen.core.classpath.extract-native-dependencies b/target/default+test/stale/leiningen.core.classpath.extract-native-dependencies deleted file mode 100644 index 03ba243..0000000 --- a/target/default+test/stale/leiningen.core.classpath.extract-native-dependencies +++ /dev/null @@ -1 +0,0 @@ -[{:dependencies {args4j {:vsn "2.0.26", :native-prefix nil}, org.clojure/data.json {:vsn "0.2.6", :native-prefix nil}, org.clojure/clojure {:vsn "1.11.1", :native-prefix nil}, org.clojure/core.specs.alpha {:vsn "0.2.62", :native-prefix nil}, org.clojure/spec.alpha {:vsn "0.3.218", :native-prefix nil}, dda/data-test {:vsn "0.1.1", :native-prefix nil}, quoin {:vsn "0.1.2", :native-prefix nil}, org.domaindrivenarchitecture/c4k-common-clj {:vsn "5.0.1", :native-prefix nil}, hickory {:vsn "0.7.1", :native-prefix nil}, org.clojure/google-closure-library {:vsn "0.0-20160609-f42b4a24", :native-prefix nil}, org.clojure/clojurescript {:vsn "1.9.293", :native-prefix nil}, aero {:vsn "1.1.6", :native-prefix nil}, orchestra {:vsn "2021.01.01-1", :native-prefix nil}, org.flatland/ordered {:vsn "1.5.9", :native-prefix nil}, com.google.jsinterop/jsinterop-annotations {:vsn "1.0.0", :native-prefix nil}, org.yaml/snakeyaml {:vsn "1.33", :native-prefix nil}, org.mozilla/rhino {:vsn "1.7R5", :native-prefix nil}, org.clojure/google-closure-library-third-party {:vsn "0.0-20160609-f42b4a24", :native-prefix nil}, pjstadig/humane-test-output {:vsn "0.11.0", :native-prefix nil}, com.google.javascript/closure-compiler-externs {:vsn "v20160911", :native-prefix nil}, clojure-complete {:vsn "0.2.5", :native-prefix nil}, com.google.guava/guava {:vsn "19.0", :native-prefix nil}, viebel/codox-klipse-theme {:vsn "0.0.1", :native-prefix nil}, clj-commons/clj-yaml {:vsn "1.0.26", :native-prefix nil}, prismatic/schema {:vsn "1.1.10", :native-prefix nil}, org.clojure/tools.reader {:vsn "1.3.6", :native-prefix nil}, nrepl {:vsn "0.6.0", :native-prefix nil}, org.jsoup/jsoup {:vsn "1.9.2", :native-prefix nil}, expound {:vsn "0.9.0", :native-prefix nil}, com.google.javascript/closure-compiler-unshaded {:vsn "v20160911", :native-prefix nil}, com.google.protobuf/protobuf-java {:vsn "2.5.0", :native-prefix nil}, com.google.code.findbugs/jsr305 {:vsn "1.3.9", :native-prefix nil}, com.google.code.gson/gson {:vsn "2.2.4", :native-prefix nil}}, :native-path "target/default+test/native"} {:native-path "target/default+test/native", :dependencies {args4j {:vsn "2.0.26", :native-prefix nil, :native? false}, org.clojure/data.json {:vsn "0.2.6", :native-prefix nil, :native? false}, org.clojure/clojure {:vsn "1.11.1", :native-prefix nil, :native? false}, org.clojure/core.specs.alpha {:vsn "0.2.62", :native-prefix nil, :native? false}, org.clojure/spec.alpha {:vsn "0.3.218", :native-prefix nil, :native? false}, dda/data-test {:vsn "0.1.1", :native-prefix nil, :native? false}, quoin {:vsn "0.1.2", :native-prefix nil, :native? false}, org.domaindrivenarchitecture/c4k-common-clj {:vsn "5.0.1", :native-prefix nil, :native? false}, hickory {:vsn "0.7.1", :native-prefix nil, :native? false}, org.clojure/google-closure-library {:vsn "0.0-20160609-f42b4a24", :native-prefix nil, :native? false}, org.clojure/clojurescript {:vsn "1.9.293", :native-prefix nil, :native? false}, aero {:vsn "1.1.6", :native-prefix nil, :native? false}, orchestra {:vsn "2021.01.01-1", :native-prefix nil, :native? false}, org.flatland/ordered {:vsn "1.5.9", :native-prefix nil, :native? false}, com.google.jsinterop/jsinterop-annotations {:vsn "1.0.0", :native-prefix nil, :native? false}, org.yaml/snakeyaml {:vsn "1.33", :native-prefix nil, :native? false}, org.mozilla/rhino {:vsn "1.7R5", :native-prefix nil, :native? false}, org.clojure/google-closure-library-third-party {:vsn "0.0-20160609-f42b4a24", :native-prefix nil, :native? false}, pjstadig/humane-test-output {:vsn "0.11.0", :native-prefix nil, :native? false}, com.google.javascript/closure-compiler-externs {:vsn "v20160911", :native-prefix nil, :native? false}, clojure-complete {:vsn "0.2.5", :native-prefix nil, :native? false}, com.google.guava/guava {:vsn "19.0", :native-prefix nil, :native? false}, viebel/codox-klipse-theme {:vsn "0.0.1", :native-prefix nil, :native? false}, clj-commons/clj-yaml {:vsn "1.0.26", :native-prefix nil, :native? false}, prismatic/schema {:vsn "1.1.10", :native-prefix nil, :native? false}, org.clojure/tools.reader {:vsn "1.3.6", :native-prefix nil, :native? false}, nrepl {:vsn "0.6.0", :native-prefix nil, :native? false}, org.jsoup/jsoup {:vsn "1.9.2", :native-prefix nil, :native? false}, expound {:vsn "0.9.0", :native-prefix nil, :native? false}, com.google.javascript/closure-compiler-unshaded {:vsn "v20160911", :native-prefix nil, :native? false}, com.google.protobuf/protobuf-java {:vsn "2.5.0", :native-prefix nil, :native? false}, com.google.code.findbugs/jsr305 {:vsn "1.3.9", :native-prefix nil, :native? false}, com.google.code.gson/gson {:vsn "2.2.4", :native-prefix nil, :native? false}}}] \ No newline at end of file From 74925a90b737c54664288cb0380754401950d68e Mon Sep 17 00:00:00 2001 From: jerger Date: Fri, 24 Feb 2023 10:14:26 +0100 Subject: [PATCH 092/243] mob --- version.py => domain.py | 29 +++++++++++++++++++++-------- git_repository.py | 2 +- infrastructure.py | 8 ++++++++ release_mixin.py | 3 +-- release_type.py | 7 ------- services.py | 2 +- version_repository.py | 2 +- 7 files changed, 33 insertions(+), 20 deletions(-) rename version.py => domain.py (75%) create mode 100644 infrastructure.py delete mode 100644 release_type.py diff --git a/version.py b/domain.py similarity index 75% rename from version.py rename to domain.py index 3cfca62..c5193a6 100644 --- a/version.py +++ b/domain.py @@ -1,5 +1,12 @@ -from release_type import ReleaseType -from file_handlers import FileHandler +from enum import Enum + +class ReleaseType(Enum): + MAJOR = 0 + MINOR = 1 + PATCH = 2 + SNAPSHOT = 3 + BUMP = None + class Version(): @@ -19,11 +26,11 @@ class Version(): case ReleaseType.PATCH: self.version_list[ReleaseType.PATCH.value] += 1 case ReleaseType.MINOR: - self.version_list[ReleaseType.PATCH.value] = 0 + self.version_list[ReleaseType.PATCH.value] = 0 self.version_list[ReleaseType.MINOR.value] += 1 case ReleaseType.MAJOR: - self.version_list[ReleaseType.PATCH.value] = 0 - self.version_list[ReleaseType.MINOR.value] = 0 + self.version_list[ReleaseType.PATCH.value] = 0 + self.version_list[ReleaseType.MINOR.value] = 0 self.version_list[ReleaseType.MAJOR.value] += 1 case None: raise Exception("Release Type was not set!") @@ -33,15 +40,21 @@ class Version(): if self.is_snapshot: self.version_string += "-SNAPSHOT" return self.version_string - + def create_release_version(self, release_type: ReleaseType): release_version = Version(self.version_list.copy()) release_version.is_snapshot = self.is_snapshot release_version.increment(release_type) - return release_version - + return release_version + def create_bump_version(self): bump_version = Version(self.version_list.copy()) bump_version.is_snapshot = self.is_snapshot bump_version.increment(ReleaseType.BUMP) return bump_version + + +class Release(): + def __init__(self, release_type: ReleaseType, version: Version): + self.release_type = release_type + self.version = version diff --git a/git_repository.py b/git_repository.py index e835384..5761048 100644 --- a/git_repository.py +++ b/git_repository.py @@ -2,7 +2,7 @@ import os import subprocess as sub from pathlib import Path from system_repository import SystemRepository -from release_type import ReleaseType +from domain import ReleaseType class GitRepository(): diff --git a/infrastructure.py b/infrastructure.py new file mode 100644 index 0000000..baa9e74 --- /dev/null +++ b/infrastructure.py @@ -0,0 +1,8 @@ +from domain import Release + +class ReleaseRepository(): + def __init__(self): + pass + + def get_current_release(self) -> Release: + pass \ No newline at end of file diff --git a/release_mixin.py b/release_mixin.py index 13b174a..7e02598 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -2,11 +2,10 @@ import copy from ddadevops import DevopsBuild from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path -from version import Version from version_repository import VersionRepository from services import InitReleaseService, PrepareReleaseService, TagAndPushReleaseService from git_repository import GitRepository -from release_type import ReleaseType +from domain import ReleaseType, Version def create_release_mixin_config(config_file, main_branch) -> dict: config = {} diff --git a/release_type.py b/release_type.py deleted file mode 100644 index e4217a5..0000000 --- a/release_type.py +++ /dev/null @@ -1,7 +0,0 @@ -from enum import Enum -class ReleaseType(Enum): - MAJOR = 0 - MINOR = 1 - PATCH = 2 - SNAPSHOT = 3 - BUMP = None \ No newline at end of file diff --git a/services.py b/services.py index 3ff28de..5453bee 100644 --- a/services.py +++ b/services.py @@ -2,7 +2,7 @@ from pathlib import Path from version_repository import VersionRepository from release_type import ReleaseType from git_repository import GitRepository -from version import Version +from domain import Version class InitReleaseService(): diff --git a/version_repository.py b/version_repository.py index 2c3ec63..16e706f 100644 --- a/version_repository.py +++ b/version_repository.py @@ -1,5 +1,5 @@ from file_handlers import FileHandler -from version import Version +from domain import Version class VersionRepository(): From 0551fe6bfe7bcdb3bb3da28b1372d82549a7fc9c Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 24 Feb 2023 10:31:04 +0100 Subject: [PATCH 093/243] mob --- git_repository.py | 69 ------------ infrastructure.py | 105 +++++++++++++++++- file_handlers.py => infrastructure_api.py | 24 +++- release_mixin.py | 3 +- services.py | 6 +- system_repository.py | 22 ---- test/test_domain.py | 53 +++++++++ test/test_git_repository.py | 64 ----------- ...ersion_class.py => test_infrastructure.py} | 90 +++++++++------ test/test_release_mixin.py | 1 - test/test_services.py | 4 +- version_repository.py | 32 ------ 12 files changed, 240 insertions(+), 233 deletions(-) delete mode 100644 git_repository.py rename file_handlers.py => infrastructure_api.py (90%) delete mode 100644 system_repository.py create mode 100644 test/test_domain.py delete mode 100644 test/test_git_repository.py rename test/{test_version_class.py => test_infrastructure.py} (57%) delete mode 100644 version_repository.py diff --git a/git_repository.py b/git_repository.py deleted file mode 100644 index 5761048..0000000 --- a/git_repository.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -import subprocess as sub -from pathlib import Path -from system_repository import SystemRepository -from domain import ReleaseType - -class GitRepository(): - - def __init__(self): - self.latest_commit = None - self.system_repository = SystemRepository() - - @classmethod - def create_from_commit_string(cls, commit_string): - inst = cls() - inst.latest_commit = commit_string - return inst - - def get_latest_n_commits(self, n: int): - self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', f'-n {n}') - return self.system_repository.stdout - - def get_latest_commit(self): - output = self.get_latest_n_commits(1) - self.latest_commit = " ".join(output) # returns a list of strings otherwise - return self.latest_commit - - def get_release_type_from_latest_commit(self): - if self.latest_commit is None: - self.get_latest_commit() - - if ReleaseType.MAJOR.name in self.latest_commit.upper(): - return ReleaseType.MAJOR - elif ReleaseType.MINOR.name in self.latest_commit.upper(): - return ReleaseType.MINOR - elif ReleaseType.PATCH.name in self.latest_commit.upper(): - return ReleaseType.PATCH - elif ReleaseType.SNAPSHOT.name in self.latest_commit.upper(): - return ReleaseType.SNAPSHOT - else: - return None - - def tag_annotated(self, annotation: str, message: str): - self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message) - return self.system_repository.stdout - - def tag_annotated(self, annotation: str, message: str, count: int): - self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') - return self.system_repository.stdout - - def get_current_branch(self): - self.system_repository.run_checked('git', 'branch', '--show-current') - return ''.join(self.system_repository.stdout).rstrip() - - def add_file(self, file_path: Path): - self.system_repository.run_checked('git', 'add', file_path) - return self.system_repository.stdout - - def commit(self, commit_message: str): - self.system_repository.run_checked('git', 'commit', '-m', commit_message) - return self.system_repository.stdout - - def push(self): - self.system_repository.run_checked('git', 'push') - return self.system_repository.stdout - - def checkout(self, branch: str): - self.system_repository.run_checked('git', 'checkout', branch) - return self.system_repository.stdout diff --git a/infrastructure.py b/infrastructure.py index baa9e74..528194e 100644 --- a/infrastructure.py +++ b/infrastructure.py @@ -1,8 +1,107 @@ -from domain import Release +from domain import Release, Version, ReleaseType +from infrastructure_api import FileHandler, SystemAPI +from pathlib import Path + +class VersionRepository(): + + def __init__(self, file): + self.file = file + self.file_handler = None + + def load_file(self): + self.file_handler = FileHandler.from_file_path(self.file) + return self.file_handler + + def write_file(self, version_string): + if self.file_handler is None: + raise Exception('Version was not created by load_file method.') + else: + self.file_handler.write(version_string) + + def parse_file(self): + version_list, is_snapshot = self.file_handler.parse() + return version_list, is_snapshot + + def get_version(self) -> Version: + + self.file_handler = self.load_file() + version_list, is_snapshot = self.parse_file() + version = Version(version_list) + version.is_snapshot = is_snapshot + + return version class ReleaseRepository(): + def __init__(self, version_repository: VersionRepository): + self.version_repository = version_repository + + def get_current_release(self) -> Release: + pass + +class ReleaseTypeRepository(): def __init__(self): pass - def get_current_release(self) -> Release: - pass \ No newline at end of file +class GitRepository(): + + def __init__(self): + self.latest_commit = None + self.system_repository = SystemAPI() + + @classmethod + def create_from_commit_string(cls, commit_string): + inst = cls() + inst.latest_commit = commit_string + return inst + + def get_latest_n_commits(self, n: int): + self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', f'-n {n}') + return self.system_repository.stdout + + def get_latest_commit(self): + output = self.get_latest_n_commits(1) + self.latest_commit = " ".join(output) # returns a list of strings otherwise + return self.latest_commit + + def get_release_type_from_latest_commit(self): + if self.latest_commit is None: + self.get_latest_commit() + + if ReleaseType.MAJOR.name in self.latest_commit.upper(): + return ReleaseType.MAJOR + elif ReleaseType.MINOR.name in self.latest_commit.upper(): + return ReleaseType.MINOR + elif ReleaseType.PATCH.name in self.latest_commit.upper(): + return ReleaseType.PATCH + elif ReleaseType.SNAPSHOT.name in self.latest_commit.upper(): + return ReleaseType.SNAPSHOT + else: + return None + + def tag_annotated(self, annotation: str, message: str): + self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message) + return self.system_repository.stdout + + def tag_annotated(self, annotation: str, message: str, count: int): + self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') + return self.system_repository.stdout + + def get_current_branch(self): + self.system_repository.run_checked('git', 'branch', '--show-current') + return ''.join(self.system_repository.stdout).rstrip() + + def add_file(self, file_path: Path): + self.system_repository.run_checked('git', 'add', file_path) + return self.system_repository.stdout + + def commit(self, commit_message: str): + self.system_repository.run_checked('git', 'commit', '-m', commit_message) + return self.system_repository.stdout + + def push(self): + self.system_repository.run_checked('git', 'push') + return self.system_repository.stdout + + def checkout(self, branch: str): + self.system_repository.run_checked('git', 'checkout', branch) + return self.system_repository.stdout \ No newline at end of file diff --git a/file_handlers.py b/infrastructure_api.py similarity index 90% rename from file_handlers.py rename to infrastructure_api.py index e3f627d..aacaa08 100644 --- a/file_handlers.py +++ b/infrastructure_api.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod import json import re +import subprocess as sub class FileHandler(ABC): @@ -152,4 +153,25 @@ class ClojureFileHandler(FileHandler): clj_file.seek(0) clj_file.write(version_substitute) clj_file.write(clj_rest) - clj_file.truncate() \ No newline at end of file + clj_file.truncate() + +class SystemAPI(): + + def __init__(self): + self.stdout = [""] + self.stderr = [""] + + def run(self, args): + stream = sub.Popen(args, + stdout=sub.PIPE, + stderr=sub.PIPE, + text=True, + encoding="UTF-8") + self.stdout = stream.stdout.readlines() + self.stderr = stream.stderr.readlines() + + def run_checked(self, *args): + self.run(args) + + if len(self.stderr) > 0: + raise Exception(f"Command failed with: {self.stderr}") diff --git a/release_mixin.py b/release_mixin.py index 7e02598..3d6c992 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -2,9 +2,8 @@ import copy from ddadevops import DevopsBuild from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path -from version_repository import VersionRepository +from infrastructure import VersionRepository, GitRepository from services import InitReleaseService, PrepareReleaseService, TagAndPushReleaseService -from git_repository import GitRepository from domain import ReleaseType, Version def create_release_mixin_config(config_file, main_branch) -> dict: diff --git a/services.py b/services.py index 5453bee..6b46c12 100644 --- a/services.py +++ b/services.py @@ -1,8 +1,6 @@ from pathlib import Path -from version_repository import VersionRepository -from release_type import ReleaseType -from git_repository import GitRepository -from domain import Version +from infrastructure import VersionRepository, GitRepository +from domain import Version, ReleaseType class InitReleaseService(): diff --git a/system_repository.py b/system_repository.py deleted file mode 100644 index 914276f..0000000 --- a/system_repository.py +++ /dev/null @@ -1,22 +0,0 @@ -import subprocess as sub - -class SystemRepository(): - - def __init__(self): - self.stdout = [""] - self.stderr = [""] - - def run(self, args): - stream = sub.Popen(args, - stdout=sub.PIPE, - stderr=sub.PIPE, - text=True, - encoding="UTF-8") - self.stdout = stream.stdout.readlines() - self.stderr = stream.stderr.readlines() - - def run_checked(self, *args): - self.run(args) - - if len(self.stderr) > 0: - raise Exception(f"Command failed with: {self.stderr}") diff --git a/test/test_domain.py b/test/test_domain.py new file mode 100644 index 0000000..68fd7bf --- /dev/null +++ b/test/test_domain.py @@ -0,0 +1,53 @@ +from pathlib import Path +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +# now we can import the module in the parent +# directory. + +from domain import Version, ReleaseType +from infrastructure import VersionRepository + +def test_version(): + version = Version([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([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([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([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([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 diff --git a/test/test_git_repository.py b/test/test_git_repository.py deleted file mode 100644 index a1e9db8..0000000 --- a/test/test_git_repository.py +++ /dev/null @@ -1,64 +0,0 @@ -from pathlib import Path -import sys -import os - -# getting the name of the directory -# where the this file is present. -current = os.path.dirname(os.path.realpath(__file__)) - -# Getting the parent directory name -# where the current directory is present. -parent = os.path.dirname(current) - -# adding the parent directory to -# the sys.path. -sys.path.append(parent) - -# now we can import the module in the parent -# directory. - -from git_repository import GitRepository -from release_type import ReleaseType - -def test_git_repository(): - - # init - commit_string = "Major bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - #test - assert release_type == ReleaseType.MAJOR - - - # init - commit_string = "MINOR bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - #test - assert release_type == ReleaseType.MINOR - - # init - commit_string = "PATCH bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - # test - assert release_type == ReleaseType.PATCH - - # init - commit_string = "SNAPSHOT bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - #test - assert release_type == ReleaseType.SNAPSHOT - - # init - commit_string = "bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - #test - assert release_type == None diff --git a/test/test_version_class.py b/test/test_infrastructure.py similarity index 57% rename from test/test_version_class.py rename to test/test_infrastructure.py index 4f1ac6b..6abf6ed 100644 --- a/test/test_version_class.py +++ b/test/test_infrastructure.py @@ -5,54 +5,63 @@ import os # getting the name of the directory # where the this file is present. current = os.path.dirname(os.path.realpath(__file__)) - + # Getting the parent directory name # where the current directory is present. parent = os.path.dirname(current) - + # adding the parent directory to # the sys.path. sys.path.append(parent) - + # now we can import the module in the parent # directory. -from version import Version -from version_repository import VersionRepository -from release_type import ReleaseType +from infrastructure import GitRepository, VersionRepository, ReleaseRepository +from domain import ReleaseType, Release -def test_version(): - version = Version([1, 2, 3]) +def test_git_repository(): - version.increment(ReleaseType.SNAPSHOT) - assert version.get_version_string() == "1.2.3-SNAPSHOT" - assert version.version_list == [1, 2, 3] - assert version.is_snapshot + # init + commit_string = "Major bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() - version = Version([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 + #test + assert release_type == ReleaseType.MAJOR - version = Version([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([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 + # init + commit_string = "MINOR bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() - version = Version([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 + #test + assert release_type == ReleaseType.MINOR + # init + commit_string = "PATCH bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() + + # test + assert release_type == ReleaseType.PATCH + + # init + commit_string = "SNAPSHOT bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() + + #test + assert release_type == ReleaseType.SNAPSHOT + + # init + commit_string = "bla" + repo = GitRepository.create_from_commit_string(commit_string) + release_type = repo.get_release_type_from_latest_commit() + + #test + assert release_type == None def test_gradle(tmp_path): # init @@ -124,4 +133,19 @@ def test_python(tmp_path): repo.write_file(version.get_version_string()) # check - assert '3.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file + assert '3.1.3-SNAPSHOT' in f.read_text() + +def test_release_repository(tmp_path): + # init + file_name = 'config.json' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + sut = ReleaseRepository(VersionRepository(f)) + current_release = sut.get_current_release() + + assert current_release.version is not None \ No newline at end of file diff --git a/test/test_release_mixin.py b/test/test_release_mixin.py index 67b25a9..d9d387a 100644 --- a/test/test_release_mixin.py +++ b/test/test_release_mixin.py @@ -19,7 +19,6 @@ sys.path.append(parent) # directory. from pybuilder.core import task, init, Project -from version import * from release_mixin import ReleaseMixin, create_release_mixin_config MAIN_BRANCH = 'main' diff --git a/test/test_services.py b/test/test_services.py index f7ec74c..1eef213 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -18,8 +18,8 @@ sys.path.append(parent) # directory. from services import InitReleaseService -from release_type import ReleaseType -from version_repository import VersionRepository +from domain import ReleaseType +from infrastructure import VersionRepository def test_init_release_service(tmp_path): # init diff --git a/version_repository.py b/version_repository.py deleted file mode 100644 index 16e706f..0000000 --- a/version_repository.py +++ /dev/null @@ -1,32 +0,0 @@ -from file_handlers import FileHandler -from domain import Version - -class VersionRepository(): - - def __init__(self, file): - self.file = file - self.file_handler = None - - def load_file(self): - self.file_handler = FileHandler.from_file_path(self.file) - return self.file_handler - - def write_file(self, version_string): - if self.file_handler is None: - raise Exception('Version was not created by load_file method.') - else: - self.file_handler.write(version_string) - - def parse_file(self): - version_list, is_snapshot = self.file_handler.parse() - return version_list, is_snapshot - - def get_version(self): - - self.file_handler = self.load_file() - version_list, is_snapshot = self.parse_file() - version = Version(version_list) - version.is_snapshot = is_snapshot - - return version - From a09eaa7521823619f49e0720bbfdaf4748e39992 Mon Sep 17 00:00:00 2001 From: jerger Date: Fri, 24 Feb 2023 10:46:43 +0100 Subject: [PATCH 094/243] mob --- infrastructure.py | 68 ++++---------------------- infrastructure_api.py | 96 +++++++++++++++++++++++++++++-------- test/test_infrastructure.py | 51 ++++++++++++-------- 3 files changed, 118 insertions(+), 97 deletions(-) diff --git a/infrastructure.py b/infrastructure.py index 528194e..8234a98 100644 --- a/infrastructure.py +++ b/infrastructure.py @@ -1,5 +1,5 @@ from domain import Release, Version, ReleaseType -from infrastructure_api import FileHandler, SystemAPI +from infrastructure_api import FileHandler, SystemAPI, GitApi from pathlib import Path class VersionRepository(): @@ -39,69 +39,19 @@ class ReleaseRepository(): pass class ReleaseTypeRepository(): - def __init__(self): - pass + def __init__(self, git_api, environment_api=None): + self.git_api = git_api -class GitRepository(): + def get_release_type(self): + latest_commit = self.git_api.get_latest_commit() - def __init__(self): - self.latest_commit = None - self.system_repository = SystemAPI() - - @classmethod - def create_from_commit_string(cls, commit_string): - inst = cls() - inst.latest_commit = commit_string - return inst - - def get_latest_n_commits(self, n: int): - self.system_repository.run_checked('git', 'log', '--oneline', '--format="%s %b"', f'-n {n}') - return self.system_repository.stdout - - def get_latest_commit(self): - output = self.get_latest_n_commits(1) - self.latest_commit = " ".join(output) # returns a list of strings otherwise - return self.latest_commit - - def get_release_type_from_latest_commit(self): - if self.latest_commit is None: - self.get_latest_commit() - - if ReleaseType.MAJOR.name in self.latest_commit.upper(): + if ReleaseType.MAJOR.name in latest_commit.upper(): return ReleaseType.MAJOR - elif ReleaseType.MINOR.name in self.latest_commit.upper(): + elif ReleaseType.MINOR.name in latest_commit.upper(): return ReleaseType.MINOR - elif ReleaseType.PATCH.name in self.latest_commit.upper(): + elif ReleaseType.PATCH.name in latest_commit.upper(): return ReleaseType.PATCH - elif ReleaseType.SNAPSHOT.name in self.latest_commit.upper(): + elif ReleaseType.SNAPSHOT.name in latest_commit.upper(): return ReleaseType.SNAPSHOT else: return None - - def tag_annotated(self, annotation: str, message: str): - self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message) - return self.system_repository.stdout - - def tag_annotated(self, annotation: str, message: str, count: int): - self.system_repository.run_checked('git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') - return self.system_repository.stdout - - def get_current_branch(self): - self.system_repository.run_checked('git', 'branch', '--show-current') - return ''.join(self.system_repository.stdout).rstrip() - - def add_file(self, file_path: Path): - self.system_repository.run_checked('git', 'add', file_path) - return self.system_repository.stdout - - def commit(self, commit_message: str): - self.system_repository.run_checked('git', 'commit', '-m', commit_message) - return self.system_repository.stdout - - def push(self): - self.system_repository.run_checked('git', 'push') - return self.system_repository.stdout - - def checkout(self, branch: str): - self.system_repository.run_checked('git', 'checkout', branch) - return self.system_repository.stdout \ No newline at end of file diff --git a/infrastructure_api.py b/infrastructure_api.py index aacaa08..06e2d17 100644 --- a/infrastructure_api.py +++ b/infrastructure_api.py @@ -1,8 +1,10 @@ -from abc import ABC, abstractmethod +from abc import ABC, abstractmethod +from pathlib import Path import json import re import subprocess as sub + class FileHandler(ABC): @classmethod @@ -18,7 +20,8 @@ class FileHandler(ABC): case '.py': file_handler = PythonFileHandler() case _: - raise Exception(f'The file type "{config_file_type}" is not implemented') + raise Exception( + f'The file type "{config_file_type}" is not implemented') file_handler.config_file_path = file_path file_handler.config_file_type = config_file_type @@ -27,11 +30,12 @@ class FileHandler(ABC): @abstractmethod def parse(self) -> tuple[list[int], bool]: pass - + @abstractmethod def write(self, version_string): pass + class JsonFileHandler(FileHandler): def parse(self) -> tuple[list[int], bool]: @@ -48,7 +52,7 @@ class JsonFileHandler(FileHandler): with open(self.config_file_path, 'r+') as json_file: json_data = json.load(json_file) json_data['version'] = version_string - json_file.seek(0) + json_file.seek(0) json.dump(json_data, json_file, indent=4) json_file.truncate() @@ -59,15 +63,16 @@ class GradleFileHandler(FileHandler): with open(self.config_file_path, 'r') as gradle_file: contents = gradle_file.read() version_line = re.search("\nversion = .*", contents) - exception = Exception("Version not found in gradle file") + exception = Exception("Version not found in gradle file") if version_line is None: raise exception - + version_line = version_line.group() - version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + version_string = re.search( + '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) if version_string is None: raise exception - + version_string = version_string.group() is_snapshot = False if '-SNAPSHOT' in version_string: @@ -81,7 +86,8 @@ class GradleFileHandler(FileHandler): def write(self, version_string): with open(self.config_file_path, 'r+') as gradle_file: contents = gradle_file.read() - version_substitute = re.sub('\nversion = "[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?"', f'\nversion = "{version_string}"', contents) + version_substitute = re.sub( + '\nversion = "[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?"', f'\nversion = "{version_string}"', contents) gradle_file.seek(0) gradle_file.write(version_substitute) gradle_file.truncate() @@ -93,15 +99,16 @@ class PythonFileHandler(FileHandler): with open(self.config_file_path, 'r') as python_file: contents = python_file.read() version_line = re.search("\nversion = .*\n", contents) - exception = Exception("Version not found in gradle file") + exception = Exception("Version not found in gradle file") if version_line is None: raise exception - + version_line = version_line.group() - version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + version_string = re.search( + '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) if version_string is None: raise exception - + version_string = version_string.group() is_snapshot = False if '-SNAPSHOT' in version_string: @@ -115,26 +122,29 @@ class PythonFileHandler(FileHandler): def write(self, version_string): with open(self.config_file_path, 'r+') as python_file: contents = python_file.read() - version_substitute = re.sub('\nversion = "[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?"', f'\nversion = "{version_string}"', contents) + version_substitute = re.sub( + '\nversion = "[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?"', f'\nversion = "{version_string}"', contents) python_file.seek(0) python_file.write(version_substitute) python_file.truncate() + class ClojureFileHandler(FileHandler): def parse(self) -> tuple[list[int], bool]: with open(self.config_file_path, 'r') as clj_file: contents = clj_file.read() version_line = re.search("^\\(defproject .*\n", contents) - exception = Exception("Version not found in clj file") + exception = Exception("Version not found in clj file") if version_line is None: raise exception - + version_line = version_line.group() - version_string = re.search('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + version_string = re.search( + '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) if version_string is None: raise exception - + version_string = version_string.group() is_snapshot = False if '-SNAPSHOT' in version_string: @@ -149,12 +159,14 @@ class ClojureFileHandler(FileHandler): with open(self.config_file_path, 'r+') as clj_file: clj_first = clj_file.readline() clj_rest = clj_file.read() - version_substitute = re.sub('[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', f'"{version_string}"\n', clj_first) + version_substitute = re.sub( + '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', f'"{version_string}"\n', clj_first) clj_file.seek(0) clj_file.write(version_substitute) clj_file.write(clj_rest) clj_file.truncate() + class SystemAPI(): def __init__(self): @@ -175,3 +187,49 @@ class SystemAPI(): if len(self.stderr) > 0: raise Exception(f"Command failed with: {self.stderr}") + + +class GitApi(): + + def __init__(self): + self.system_repository = SystemAPI() + + def get_latest_n_commits(self, n: int): + self.system_repository.run_checked( + 'git', 'log', '--oneline', '--format="%s %b"', f'-n {n}') + return self.system_repository.stdout + + def get_latest_commit(self): + output = self.get_latest_n_commits(1) + return " ".join(output) + + def tag_annotated(self, annotation: str, message: str): + self.system_repository.run_checked( + 'git', 'tag', '-a', annotation, '-m', message) + return self.system_repository.stdout + + def tag_annotated(self, annotation: str, message: str, count: int): + self.system_repository.run_checked( + 'git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') + return self.system_repository.stdout + + def get_current_branch(self): + self.system_repository.run_checked('git', 'branch', '--show-current') + return ''.join(self.system_repository.stdout).rstrip() + + def add_file(self, file_path: Path): + self.system_repository.run_checked('git', 'add', file_path) + return self.system_repository.stdout + + def commit(self, commit_message: str): + self.system_repository.run_checked( + 'git', 'commit', '-m', commit_message) + return self.system_repository.stdout + + def push(self): + self.system_repository.run_checked('git', 'push') + return self.system_repository.stdout + + def checkout(self, branch: str): + self.system_repository.run_checked('git', 'checkout', branch) + return self.system_repository.stdout diff --git a/test/test_infrastructure.py b/test/test_infrastructure.py index 6abf6ed..d7d28fd 100644 --- a/test/test_infrastructure.py +++ b/test/test_infrastructure.py @@ -1,3 +1,6 @@ +from domain import ReleaseType, Release +from infrastructure import GitRepository, VersionRepository, ReleaseRepository +from infrastructure_api import GitApi from pathlib import Path import sys import os @@ -5,20 +8,26 @@ import os # getting the name of the directory # where the this file is present. current = os.path.dirname(os.path.realpath(__file__)) - + # Getting the parent directory name # where the current directory is present. parent = os.path.dirname(current) - + # adding the parent directory to # the sys.path. sys.path.append(parent) - + # now we can import the module in the parent # directory. -from infrastructure import GitRepository, VersionRepository, ReleaseRepository -from domain import ReleaseType, Release + +def TestGitApi(GitApi): + def __init__(self, commit_string): + self.commit_string = commit_string + + def get_latest_commit(self): + return self.commit_string + def test_git_repository(): @@ -27,16 +36,15 @@ def test_git_repository(): repo = GitRepository.create_from_commit_string(commit_string) release_type = repo.get_release_type_from_latest_commit() - #test + # test assert release_type == ReleaseType.MAJOR - # init commit_string = "MINOR bla" repo = GitRepository.create_from_commit_string(commit_string) release_type = repo.get_release_type_from_latest_commit() - #test + # test assert release_type == ReleaseType.MINOR # init @@ -52,7 +60,7 @@ def test_git_repository(): repo = GitRepository.create_from_commit_string(commit_string) release_type = repo.get_release_type_from_latest_commit() - #test + # test assert release_type == ReleaseType.SNAPSHOT # init @@ -60,9 +68,10 @@ def test_git_repository(): repo = GitRepository.create_from_commit_string(commit_string) release_type = repo.get_release_type_from_latest_commit() - #test + # test assert release_type == None + def test_gradle(tmp_path): # init file_name = 'config.gradle' @@ -75,12 +84,13 @@ def test_gradle(tmp_path): # test repo = VersionRepository(f) version = repo.get_version() - version = version.create_release_version(ReleaseType.SNAPSHOT) + version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) - + # check assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() + def test_json(tmp_path): # init file_name = 'config.json' @@ -89,16 +99,17 @@ def test_json(tmp_path): f = tmp_path / file_name f.write_text(contents) - + # test repo = VersionRepository(f) version = repo.get_version() - version = version.create_release_version(ReleaseType.SNAPSHOT) + version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) - + # check assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() + def test_clojure(tmp_path): # init file_name = 'config.clj' @@ -113,10 +124,11 @@ def test_clojure(tmp_path): version = repo.get_version() version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) - + # check assert '1.1.3-SNAPSHOT' in f.read_text() + def test_python(tmp_path): # init file_name = 'config.py' @@ -129,12 +141,13 @@ def test_python(tmp_path): # test repo = VersionRepository(f) version = repo.get_version() - version = version.create_release_version(ReleaseType.SNAPSHOT) + version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) - + # check assert '3.1.3-SNAPSHOT' in f.read_text() + def test_release_repository(tmp_path): # init file_name = 'config.json' @@ -148,4 +161,4 @@ def test_release_repository(tmp_path): sut = ReleaseRepository(VersionRepository(f)) current_release = sut.get_current_release() - assert current_release.version is not None \ No newline at end of file + assert current_release.version is not None From c6ef13ca7cf9804f8d181ce84d2b98e085106dcd Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 24 Feb 2023 11:02:11 +0100 Subject: [PATCH 095/243] mob --- infrastructure.py | 15 ++++++++------- test/test_infrastructure.py | 20 +++++++++++++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/infrastructure.py b/infrastructure.py index 8234a98..d1d844a 100644 --- a/infrastructure.py +++ b/infrastructure.py @@ -31,13 +31,6 @@ class VersionRepository(): return version -class ReleaseRepository(): - def __init__(self, version_repository: VersionRepository): - self.version_repository = version_repository - - def get_current_release(self) -> Release: - pass - class ReleaseTypeRepository(): def __init__(self, git_api, environment_api=None): self.git_api = git_api @@ -55,3 +48,11 @@ class ReleaseTypeRepository(): return ReleaseType.SNAPSHOT else: return None + +class ReleaseRepository(): + def __init__(self, version_repository: VersionRepository, release_type_repository: ReleaseTypeRepository): + self.version_repository = version_repository + self.release_type_repository = release_type_repository + + def get_current_release(self) -> Release: + return Release(self.release_type_repository.get_release_type(), self.version_repository.get_version()) diff --git a/test/test_infrastructure.py b/test/test_infrastructure.py index d7d28fd..a767ef5 100644 --- a/test/test_infrastructure.py +++ b/test/test_infrastructure.py @@ -1,6 +1,3 @@ -from domain import ReleaseType, Release -from infrastructure import GitRepository, VersionRepository, ReleaseRepository -from infrastructure_api import GitApi from pathlib import Path import sys import os @@ -20,8 +17,11 @@ sys.path.append(parent) # now we can import the module in the parent # directory. +from domain import ReleaseType, Release +from infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository +from infrastructure_api import GitApi -def TestGitApi(GitApi): +class TestGitApi(GitApi): def __init__(self, commit_string): self.commit_string = commit_string @@ -158,7 +158,13 @@ def test_release_repository(tmp_path): f.write_text(contents) # test - sut = ReleaseRepository(VersionRepository(f)) - current_release = sut.get_current_release() + sut = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(TestGitApi('MINOR test'))) + release = sut.get_current_release() - assert current_release.version is not None + assert release.version is not None + +def test_release_type_repository(): + sut = ReleaseTypeRepository(TestGitApi('MINOR test')) + release_type = sut.get_release_type() + + assert release_type is ReleaseType.MINOR From 975de636ed258c8b7cd852fae650f1be1bdaa550 Mon Sep 17 00:00:00 2001 From: jerger Date: Fri, 24 Feb 2023 11:39:38 +0100 Subject: [PATCH 096/243] mob --- domain.py | 27 ++++++++++++- infrastructure.py | 2 +- services.py | 39 +++++-------------- test/test_infrastructure.py | 69 +++++++++++---------------------- test/test_infrastructure_api.py | 21 ++++++++++ 5 files changed, 79 insertions(+), 79 deletions(-) create mode 100644 test/test_infrastructure_api.py diff --git a/domain.py b/domain.py index c5193a6..7fa79bd 100644 --- a/domain.py +++ b/domain.py @@ -1,5 +1,11 @@ from enum import Enum + +class Config(): + def __init__(self, main_branch): + pass + + class ReleaseType(Enum): MAJOR = 0 MINOR = 1 @@ -10,7 +16,8 @@ class ReleaseType(Enum): class Version(): - def __init__(self, version_list: list): + def __init__(self, id: Path, version_list: list): + self.id = id self.version_list = version_list self.version_string = None self.is_snapshot = None @@ -55,6 +62,22 @@ class Version(): class Release(): - def __init__(self, release_type: ReleaseType, version: Version): + def __init__(self, release_type: ReleaseType, version: Version, current_branch: str): self.release_type = release_type self.version = version + self.current_branch = current_branch + + def release_version(self): + return self.version.create_release_version(self.release_type) + + def bump_version(self): + return self.version.create_bump_version() + + def validate(self, main_branch): + 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 + + def is_valid(self, main_branch): + return self.validate(main_branch).count < 1 diff --git a/infrastructure.py b/infrastructure.py index d1d844a..4bf963a 100644 --- a/infrastructure.py +++ b/infrastructure.py @@ -54,5 +54,5 @@ class ReleaseRepository(): self.version_repository = version_repository self.release_type_repository = release_type_repository - def get_current_release(self) -> Release: + def get_release(self) -> Release: return Release(self.release_type_repository.get_release_type(), self.version_repository.get_version()) diff --git a/services.py b/services.py index 6b46c12..e3e87cc 100644 --- a/services.py +++ b/services.py @@ -1,43 +1,22 @@ from pathlib import Path -from infrastructure import VersionRepository, GitRepository +from infrastructure import ReleaseRepository from domain import Version, ReleaseType + +# Todo: can be removed class InitReleaseService(): - def __init__(self, version_repo: VersionRepository): - if version_repo is None: - raise Exception('VersionRepo was not created. Did you run create_release_version()?') - self.version_repo = version_repo - - def __calculate_release_type(self, commit_string = None): - if commit_string is None: - return GitRepository().get_release_type_from_latest_commit() - else: - return GitRepository.create_from_commit_string(commit_string).get_release_type_from_latest_commit() - + def __init__(self, release_repo: ReleaseRepository): + self.release_repo = release_repo + def get_version(self): - return self.version_repo.get_version() - - def create_release_version(self, commit_string = None): - release_type = self.__calculate_release_type(commit_string) - version = self.get_version().create_release_version(release_type) - return version - - def create_bump_version(self): - version = self.get_version().create_bump_version() - return version + return self.release_repo.get_release().version class PrepareReleaseService(): - def __init__(self, version_repository: VersionRepository, git_repository: GitRepository, config_file: Path, main_branch: str): - self.version_repository = version_repository - self.git_repository = git_repository - self.main_branch = main_branch - self.config_file = config_file - - def run_tests(self): # maybe auto? - raise NotImplementedError + def __init__(self, release_repo: ReleaseRepository): + self.release_repo = release_repo def __write_and_commit_version(self, version: Version, commit_message: str): if self.main_branch != self.git_repository.get_current_branch(): diff --git a/test/test_infrastructure.py b/test/test_infrastructure.py index a767ef5..1e0ff48 100644 --- a/test/test_infrastructure.py +++ b/test/test_infrastructure.py @@ -29,49 +29,6 @@ class TestGitApi(GitApi): return self.commit_string -def test_git_repository(): - - # init - commit_string = "Major bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - # test - assert release_type == ReleaseType.MAJOR - - # init - commit_string = "MINOR bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - # test - assert release_type == ReleaseType.MINOR - - # init - commit_string = "PATCH bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - # test - assert release_type == ReleaseType.PATCH - - # init - commit_string = "SNAPSHOT bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - # test - assert release_type == ReleaseType.SNAPSHOT - - # init - commit_string = "bla" - repo = GitRepository.create_from_commit_string(commit_string) - release_type = repo.get_release_type_from_latest_commit() - - # test - assert release_type == None - - def test_gradle(tmp_path): # init file_name = 'config.gradle' @@ -159,12 +116,32 @@ def test_release_repository(tmp_path): # test sut = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(TestGitApi('MINOR test'))) - release = sut.get_current_release() + release = sut.get_release() + + assert release is not None - assert release.version is not None def test_release_type_repository(): sut = ReleaseTypeRepository(TestGitApi('MINOR test')) release_type = sut.get_release_type() - assert release_type is ReleaseType.MINOR + + sut = ReleaseTypeRepository(TestGitApi('MINOR bla')) + release_type = sut.get_release_type() + assert release_type is ReleaseType.MINOR + + sut = ReleaseTypeRepository(TestGitApi('Major bla')) + release_type = sut.get_release_type() + assert release_type == ReleaseType.MAJOR + + sut = ReleaseTypeRepository(TestGitApi('PATCH bla')) + release_type = sut.get_release_type() + assert release_type == ReleaseType.PATCH + + sut = ReleaseTypeRepository(TestGitApi('SNAPSHOT bla')) + release_type = sut.get_release_type() + assert release_type == ReleaseType.SNAPSHOT + + sut = ReleaseTypeRepository(TestGitApi('bla')) + release_type = sut.get_release_type() + assert release_type == None diff --git a/test/test_infrastructure_api.py b/test/test_infrastructure_api.py new file mode 100644 index 0000000..6d681da --- /dev/null +++ b/test/test_infrastructure_api.py @@ -0,0 +1,21 @@ +from pathlib import Path +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +# now we can import the module in the parent +# directory. + +from infrastructure_api import GitApi + From 54e3f7f41af7d0658fdac68f292e4b9cc3e60fa2 Mon Sep 17 00:00:00 2001 From: jerger Date: Sat, 25 Feb 2023 15:59:41 +0100 Subject: [PATCH 097/243] make devopsBuild ddd --- src/main/python/ddadevops/__init__.py | 3 ++ src/main/python/ddadevops/application.py | 8 ++++ src/main/python/ddadevops/devops_build.py | 50 +++++++---------------- src/main/python/ddadevops/domain.py | 50 +++++++++++++++++++++++ src/test/test_domain.py | 41 +++++++++++++++++++ 5 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 src/main/python/ddadevops/application.py create mode 100644 src/main/python/ddadevops/domain.py create mode 100644 src/test/test_domain.py diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index 8f5924d..3df0197 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -19,4 +19,7 @@ from .devops_terraform_build import DevopsTerraformBuild, create_devops_terrafor from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path +from .domain import Validateable, Build +from .application import BuildService + __version__ = "${version}" diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py new file mode 100644 index 0000000..014f871 --- /dev/null +++ b/src/main/python/ddadevops/application.py @@ -0,0 +1,8 @@ +from .domain import Build +from .python_util import execute + + +class BuildService(): + def initialize_build_dir(self, build: Build): + execute('rm -rf ' + build.build_path(), shell=True) + execute('mkdir -p ' + build.build_path(), shell=True) diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 4c6f403..d48999b 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -1,5 +1,5 @@ -from subprocess import run, CalledProcessError -from .python_util import filter_none +from .domain import Build +from .application import BuildService def create_devops_build_config(stage, project_root_path, module, build_dir_name='target'): @@ -11,48 +11,26 @@ def create_devops_build_config(stage, project_root_path, module, def get_devops_build(project): return project.get_property('devops_build') -def get_tag_from_latest_commit(): - try: - value = run('git describe --abbrev=0 --tags --exact-match', shell=True, - capture_output=True, check=True) - return value.stdout.decode('UTF-8').rstrip() - except CalledProcessError: - return None +# def get_tag_from_latest_commit(): +# try: +# value = run('git describe --abbrev=0 --tags --exact-match', shell=True, +# capture_output=True, check=True) +# return value.stdout.decode('UTF-8').rstrip() +# except CalledProcessError: +# return None class DevopsBuild: def __init__(self, project, config): - #deprecate stage - self.stage = config['stage'] - self.project_root_path = config['project_root_path'] - self.module = config['module'] - self.build_dir_name = config['build_dir_name'] - self.stack = {} - self.project = project + self.build = Build(project, config) + self.build_service = BuildService() project.set_property('devops_build', self) def name(self): - return self.project.name + return self.build.name() def build_path(self): - mylist = [self.project_root_path, - self.build_dir_name, - self.name(), - self.module] - return '/'.join(filter_none(mylist)) + return self.build.build_path() def initialize_build_dir(self): - run('rm -rf ' + self.build_path(), shell=True, check=True) - run('mkdir -p ' + self.build_path(), shell=True, check=True) - - def put(self, key, value): - self.stack[key] = value - - def get(self, key): - return self.stack[key] - - def get_keys(self, keys): - result = {} - for key in keys: - result[key] = self.get(key) - return result + self.build_service.initialize_build_dir(self.build) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py new file mode 100644 index 0000000..5c6c1b7 --- /dev/null +++ b/src/main/python/ddadevops/domain.py @@ -0,0 +1,50 @@ +from typing import List +from .python_util import filter_none + + +class Validateable(): + def validate(self) -> List[str]: + return [] + + def is_valid(self) -> bool: + return len(self.validate()) < 1 + + 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."] + else: + return [] + +class Build(Validateable): + + def __init__(self, project, config): + # deprecate stage + self.stage = config['stage'] + self.project_root_path = config['project_root_path'] + self.module = config['module'] + self.build_dir_name = config['build_dir_name'] + self.stack = {} + self.project = project + + def name(self): + return self.project.name + + def build_path(self): + path = [self.project_root_path, + self.build_dir_name, + self.name(), + self.module] + return '/'.join(filter_none(path)) + + def put(self, key, value): + self.stack[key] = value + + def get(self, key): + return self.stack[key] + + def get_keys(self, keys): + result = {} + for key in keys: + result[key] = self.get(key) + return result diff --git a/src/test/test_domain.py b/src/test/test_domain.py new file mode 100644 index 0000000..861f850 --- /dev/null +++ b/src/test/test_domain.py @@ -0,0 +1,41 @@ +from src.main.python.ddadevops.domain import Validateable + + +class TestValidateable(Validateable): + def __init__(self, value): + self.field = value + + def validate(self): + return self.validate_is_not_empty('field') + + +def test_should_validate_non_empty_strings(): + + sut = TestValidateable("content") + assert sut.is_valid() + + sut = TestValidateable(None) + assert not sut.is_valid() + + sut = TestValidateable('') + assert not sut.is_valid() + + +def test_should_validate_non_empty_others(): + + sut = TestValidateable(1) + assert sut.is_valid() + + sut = TestValidateable(1.0) + assert sut.is_valid() + + sut = TestValidateable(True) + assert sut.is_valid() + + sut = TestValidateable(None) + assert not sut.is_valid() + +def test_validate_with_reason(): + + sut = TestValidateable(None) + assert sut.validate()[0] == "Field 'field' may not be empty." From ce24ab6fc25250cca330e548ff9850e5e5ee6d1a Mon Sep 17 00:00:00 2001 From: jerger Date: Tue, 28 Feb 2023 09:32:26 +0100 Subject: [PATCH 098/243] wip --- src/main/python/ddadevops/__init__.py | 4 +- src/main/python/ddadevops/application.py | 54 +++++++++++++++++-- src/main/python/ddadevops/devops_build.py | 15 +++--- .../python/ddadevops/devops_docker_build.py | 44 ++------------- src/main/python/ddadevops/domain.py | 32 ++++++++--- src/main/python/ddadevops/infrastructure.py | 53 ++++++++++++++++++ src/test/test_domain.py | 2 +- 7 files changed, 145 insertions(+), 59 deletions(-) create mode 100644 src/main/python/ddadevops/infrastructure.py diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index 3df0197..666f143 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -19,7 +19,7 @@ from .devops_terraform_build import DevopsTerraformBuild, create_devops_terrafor from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path -from .domain import Validateable, Build -from .application import BuildService +from .domain import Validateable, Build, DockerBuild +from .application import BuildService, DockerBuildService __version__ = "${version}" diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 014f871..9af97ba 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,8 +1,54 @@ -from .domain import Build -from .python_util import execute +from .domain import Build, DockerBuild +from .infrastructure import FileApi, ResourceApi, DockerApi class BuildService(): + def __init__(self): + self.file_api = FileApi() + def initialize_build_dir(self, build: Build): - execute('rm -rf ' + build.build_path(), shell=True) - execute('mkdir -p ' + build.build_path(), shell=True) + self.file_api.clean_dir(build.build_path()) + +class DockerBuildService(): + def __init__(self): + self.build_service = BuildService() + self.file_api = FileApi() + self.resource_api = ResourceApi() + self.docker_api = DockerApi() + + def __copy_build_resource_file_from_package__(self, build: DockerBuild): + data = self.resource_api.read_resource("src/main/resources/docker/" + build.name) + self.file_api.write_to_file(build.build_path() + '/' + build.name, data) + + def __copy_build_resources_from_package__(self, build: DockerBuild): + self.__copy_build_resource_file_from_package__( + 'image/resources/install_functions.sh') + + def __copy_build_resources_from_dir__(self, build: DockerBuild): + self.file_api.cp_force(build.docker_build_commons_path(), build.build_path()) + + def initialize_build_dir(self, build: DockerBuild): + self.build_service.initialize_build_dir(build) + self.file_api.clean_dir(build.build_path() + '/image/resources') + if build.use_package_common_files: + self.__copy_build_resources_from_package__(build) + else: + self.__copy_build_resources_from_dir__(build) + self.file_api.cp_recursive('image', build.build_path()) + self.file_api.cp_recursive('test', build.build_path()) + + def image(self, build: DockerBuild): + self.docker_api.image(build.name(), build.build_path()) + + def drun(self, build: DockerBuild): + self.docker_api.drun(build.name()) + + def dockerhub_login(self, build: DockerBuild): + self.docker_api.dockerhub_login(build.dockerhub_user, build.dockerhub_password) + + def dockerhub_publish(self, build: DockerBuild): + self.docker_api.dockerhub_publish(build.name(), build.dockerhub_user, build.docker_publish_tag) + + def test(self, build: DockerBuild): + self.docker_api.test(build.name(), build.build_path()) + diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index d48999b..438ccb0 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -11,13 +11,14 @@ def create_devops_build_config(stage, project_root_path, module, def get_devops_build(project): return project.get_property('devops_build') -# def get_tag_from_latest_commit(): -# try: -# value = run('git describe --abbrev=0 --tags --exact-match', shell=True, -# capture_output=True, check=True) -# return value.stdout.decode('UTF-8').rstrip() -# except CalledProcessError: -# return None +# TODO: Remove from here! +def get_tag_from_latest_commit(): + try: + value = run('git describe --abbrev=0 --tags --exact-match', shell=True, + capture_output=True, check=True) + return value.stdout.decode('UTF-8').rstrip() + except CalledProcessError: + return None class DevopsBuild: diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index c85344d..8478129 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -1,7 +1,5 @@ -import sys -from subprocess import run -from pkg_resources import resource_string -from .python_util import filter_none +from .domain import DockerBuild +from .application import DockerBuildService from .devops_build import DevopsBuild, create_devops_build_config def create_devops_docker_build_config(stage, @@ -28,43 +26,11 @@ def create_devops_docker_build_config(stage, class DevopsDockerBuild(DevopsBuild): def __init__(self, project, config): - super().__init__(project, config) - project.build_depends_on('dda-python-terraform') - self.dockerhub_user = config['dockerhub_user'] - self.dockerhub_password = config['dockerhub_password'] - self.use_package_common_files = config['use_package_common_files'] - self.build_commons_path = config['build_commons_path'] - self.docker_build_commons_dir_name = config['docker_build_commons_dir_name'] - self.docker_publish_tag = config['docker_publish_tag'] - - def docker_build_commons_path(self): - mylist = [self.build_commons_path, - self.docker_build_commons_dir_name] - return '/'.join(filter_none(mylist)) + '/' - - def copy_build_resource_file_from_package(self, name): - run('mkdir -p ' + self.build_path() + '/image/resources', shell=True, check=True) - my_data = resource_string( - __name__, "src/main/resources/docker/" + name) - with open(self.build_path() + '/' + name, "w", encoding="utf-8") as output_file: - output_file.write(my_data.decode(sys.stdout.encoding)) - - def copy_build_resources_from_package(self): - self.copy_build_resource_file_from_package( - 'image/resources/install_functions.sh') - - def copy_build_resources_from_dir(self): - run('cp -f ' + self.docker_build_commons_path() + - '* ' + self.build_path(), shell=True, check=True) + self.build = DockerBuild(project, config) + self.docker_build_service = DockerBuildService() def initialize_build_dir(self): - super().initialize_build_dir() - if self.use_package_common_files: - self.copy_build_resources_from_package() - else: - self.copy_build_resources_from_dir() - run('cp -r image ' + self.build_path(), shell=True, check=True) - run('cp -r test ' + self.build_path(), shell=True, check=True) + self.docker_build_service.initialize_build_dir(self.build) def image(self): run('docker build -t ' + self.name() + diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 5c6c1b7..69a3e9e 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -3,18 +3,21 @@ from .python_util import filter_none 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."] + else: + return [] + def validate(self) -> List[str]: return [] def is_valid(self) -> bool: return len(self.validate()) < 1 - 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."] - else: - return [] + + class Build(Validateable): @@ -48,3 +51,20 @@ class Build(Validateable): for key in keys: result[key] = self.get(key) return result + + +class DockerBuild(Build): + def __init__(self, project, config): + super().__init__(project, config) + project.build_depends_on('dda-python-terraform') + self.dockerhub_user = config['dockerhub_user'] + self.dockerhub_password = config['dockerhub_password'] + self.use_package_common_files = config['use_package_common_files'] + self.build_commons_path = config['build_commons_path'] + self.docker_build_commons_dir_name = config['docker_build_commons_dir_name'] + self.docker_publish_tag = config['docker_publish_tag'] + + def docker_build_commons_path(self): + list = [self.build_commons_path, + self.docker_build_commons_dir_name] + return '/'.join(filter_none(list)) + '/' diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py new file mode 100644 index 0000000..1005f14 --- /dev/null +++ b/src/main/python/ddadevops/infrastructure.py @@ -0,0 +1,53 @@ +from pathlib import Path +from sys import stdout +from pkg_resources import resource_string +from .python_util import execute + +class ResourceApi(): + def read_resource(self, path: str) -> bytes: + return resource_string(__name__, path) + +class FileApi(): + def clean_dir(self, directory: str): + execute('rm -rf ' + directory, shell=True) + execute('mkdir -p ' + directory, shell=True) + + def cp_force(self, src: str, target_dir: str): + execute('cp -f ' + src + '* ' + target_dir, shell=True) + + def cp_recursive(self, src: str, target_dir: str): + execute('cp -r ' + src + ' ' + target_dir, shell=True) + + def write_to_file(self, path: Path, data: bytes): + with open(path, "w", encoding=stdout.encoding) as output_file: + output_file.write(data.decode(stdout.encoding)) + +class DockerApi(): + def image(self, name: str, path: Path): + execute('docker build -t ' + name + + ' --file ' + path + '/image/Dockerfile ' + + path + '/image', shell=True) + + def drun(self, name: str): + execute('docker run -it --entrypoint="" ' + + name + ' /bin/bash', shell=True) + + def dockerhub_login(self, username: str, password: str): + execute('docker login --username ' + username + + ' --password ' + password, shell=True) + + def dockerhub_publish(self, name: str, username: str, tag=None): + if tag is not None: + execute('docker tag ' + name + ' ' + username + + '/' + name + ':' + tag, shell=True) + execute('docker push ' + username + + '/' + name + ':' + tag, shell=True) + execute('docker tag ' + name + ' ' + username + + '/' + name + ':latest', shell=True) + execute('docker push ' + username + + '/' + name + ':latest', shell=True) + + def test(self, name: str, path: Path): + execute('docker build -t ' + name + '-test ' + + '--file ' + path + '/test/Dockerfile ' + + path + '/test', shell=True) diff --git a/src/test/test_domain.py b/src/test/test_domain.py index 861f850..3ef12f0 100644 --- a/src/test/test_domain.py +++ b/src/test/test_domain.py @@ -6,7 +6,7 @@ class TestValidateable(Validateable): self.field = value def validate(self): - return self.validate_is_not_empty('field') + return self.__validate_is_not_empty__('field') def test_should_validate_non_empty_strings(): From ef51db14032c663cdf7b505b776b52692d9bc54e Mon Sep 17 00:00:00 2001 From: bom Date: Tue, 28 Feb 2023 13:25:47 +0100 Subject: [PATCH 099/243] Fix some of the tests --- domain.py | 6 +++--- infrastructure.py | 7 ++++--- release_mixin.py | 8 ++++---- services.py | 8 ++++---- test/test_domain.py | 12 ++++++------ test/test_infrastructure.py | 2 +- test/test_services.py | 7 ++++--- 7 files changed, 26 insertions(+), 24 deletions(-) diff --git a/domain.py b/domain.py index 7fa79bd..bcfedeb 100644 --- a/domain.py +++ b/domain.py @@ -1,5 +1,5 @@ from enum import Enum - +from pathlib import Path class Config(): def __init__(self, main_branch): @@ -49,13 +49,13 @@ class Version(): return self.version_string def create_release_version(self, release_type: ReleaseType): - release_version = Version(self.version_list.copy()) + release_version = Version(self.id, self.version_list.copy()) release_version.is_snapshot = self.is_snapshot release_version.increment(release_type) return release_version def create_bump_version(self): - bump_version = Version(self.version_list.copy()) + bump_version = Version(self.id, self.version_list.copy()) bump_version.is_snapshot = self.is_snapshot bump_version.increment(ReleaseType.BUMP) return bump_version diff --git a/infrastructure.py b/infrastructure.py index 4bf963a..3989ed6 100644 --- a/infrastructure.py +++ b/infrastructure.py @@ -26,7 +26,7 @@ class VersionRepository(): self.file_handler = self.load_file() version_list, is_snapshot = self.parse_file() - version = Version(version_list) + version = Version(self.file, version_list) version.is_snapshot = is_snapshot return version @@ -50,9 +50,10 @@ class ReleaseTypeRepository(): return None class ReleaseRepository(): - def __init__(self, version_repository: VersionRepository, release_type_repository: ReleaseTypeRepository): + def __init__(self, version_repository: VersionRepository, release_type_repository: ReleaseTypeRepository, main_branch: str): self.version_repository = version_repository self.release_type_repository = release_type_repository + self.main_branch = main_branch def get_release(self) -> Release: - return Release(self.release_type_repository.get_release_type(), self.version_repository.get_version()) + return Release(self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch) diff --git a/release_mixin.py b/release_mixin.py index 3d6c992..648c32a 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -2,7 +2,7 @@ import copy from ddadevops import DevopsBuild from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path -from infrastructure import VersionRepository, GitRepository +from infrastructure import VersionRepository, GitApi from services import InitReleaseService, PrepareReleaseService, TagAndPushReleaseService from domain import ReleaseType, Version @@ -28,7 +28,7 @@ class ReleaseMixin(DevopsBuild): self.config_file = release_mixin_config['config_file'] self.main_branch = release_mixin_config['main_branch'] self.version_repo = VersionRepository(self.config_file) - self.git_repo = GitRepository() + self.git_api = GitApi() self.release_version = None self.bump_version = None self.commit_string = None @@ -39,7 +39,7 @@ class ReleaseMixin(DevopsBuild): self.bump_version = self.release_version.create_bump_version() def prepare_release(self): - prepare_release_service = PrepareReleaseService(self.version_repo, self.git_repo, self.config_file, self.main_branch) + prepare_release_service = PrepareReleaseService(self.version_repo, self.git_api, self.config_file, self.main_branch) if self.release_version is None or self.bump_version is None: raise Exception('prepare_release was called before init_release') @@ -48,5 +48,5 @@ class ReleaseMixin(DevopsBuild): prepare_release_service.write_and_commit_bump(self.bump_version) def tag_and_push_release(self): - tag_and_push_release_service = TagAndPushReleaseService(self.git_repo) + tag_and_push_release_service = TagAndPushReleaseService(self.git_api) tag_and_push_release_service.tag_and_push_release(self.release_version) diff --git a/services.py b/services.py index e3e87cc..068ee3b 100644 --- a/services.py +++ b/services.py @@ -1,5 +1,5 @@ from pathlib import Path -from infrastructure import ReleaseRepository +from infrastructure import ReleaseRepository, GitApi from domain import Version, ReleaseType @@ -34,13 +34,13 @@ class PrepareReleaseService(): class TagAndPushReleaseService(): - def __init__(self, git_repository: GitRepository): - self.git_repository = git_repository + def __init__(self, git_api: GitApi): + self.git_api = git_api def tag_and_push_release(self, release_version: Version): annotation = 'v' + release_version.get_version_string() message = 'Release ' + annotation - self.git_repository.tag_annotated(annotation, message, 1) + self.git_api.tag_annotated(annotation, message, 1) # self.git_repository.push() diff --git a/test/test_domain.py b/test/test_domain.py index 68fd7bf..412b409 100644 --- a/test/test_domain.py +++ b/test/test_domain.py @@ -20,33 +20,33 @@ sys.path.append(parent) from domain import Version, ReleaseType from infrastructure import VersionRepository -def test_version(): - version = Version([1, 2, 3]) +def test_version(tmp_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([1, 2, 3]) + 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([1, 2, 3]) + 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([1, 2, 3]) + 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([1, 2, 3]) + 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] diff --git a/test/test_infrastructure.py b/test/test_infrastructure.py index 1e0ff48..5ae86ec 100644 --- a/test/test_infrastructure.py +++ b/test/test_infrastructure.py @@ -115,7 +115,7 @@ def test_release_repository(tmp_path): f.write_text(contents) # test - sut = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(TestGitApi('MINOR test'))) + sut = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(TestGitApi('MINOR test')), 'main') release = sut.get_release() assert release is not None diff --git a/test/test_services.py b/test/test_services.py index 1eef213..4815e2c 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -19,7 +19,8 @@ sys.path.append(parent) from services import InitReleaseService from domain import ReleaseType -from infrastructure import VersionRepository +from infrastructure import VersionRepository, ReleaseRepository, ReleaseTypeRepository +from infrastructure_api import GitApi def test_init_release_service(tmp_path): # init @@ -30,9 +31,9 @@ def test_init_release_service(tmp_path): f = tmp_path / file_name f.write_text(contents) - repo = VersionRepository(f) + repo = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(GitApi()), 'main') release_service = InitReleaseService(repo) - version = release_service.create_release_version(commit_string='Release MINOR') + version = release_service.get_version().create_release_version(ReleaseType.MINOR) assert "123.124.0" in version.get_version_string() From 802dd7d57bdd95f695cc37b3cdcbe7e082fd28bb Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 28 Feb 2023 14:55:41 +0100 Subject: [PATCH 100/243] WIP Remove prepare release service --- build.py | 1 - 1 file changed, 1 deletion(-) diff --git a/build.py b/build.py index 4e5ea5f..cfedec4 100644 --- a/build.py +++ b/build.py @@ -43,6 +43,5 @@ def initialize(project): def release(project): build = get_devops_build(project) - build.init_release() build.prepare_release() build.tag_and_push_release() From 39d60f6854ce2225dcdb3f13d01a29275bd8c612 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 28 Feb 2023 14:55:51 +0100 Subject: [PATCH 101/243] WIP Remove prepare release service --- release_mixin.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 648c32a..6064a96 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -2,8 +2,8 @@ import copy from ddadevops import DevopsBuild from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path -from infrastructure import VersionRepository, GitApi -from services import InitReleaseService, PrepareReleaseService, TagAndPushReleaseService +from infrastructure import GitApi, ReleaseRepository, ReleaseTypeRepository, VersionRepository +from services import PrepareReleaseService, TagAndPushReleaseService from domain import ReleaseType, Version def create_release_mixin_config(config_file, main_branch) -> dict: @@ -27,25 +27,23 @@ class ReleaseMixin(DevopsBuild): release_mixin_config = config['ReleaseMixin'] self.config_file = release_mixin_config['config_file'] self.main_branch = release_mixin_config['main_branch'] - self.version_repo = VersionRepository(self.config_file) self.git_api = GitApi() + self.release_type_repo = ReleaseTypeRepository(self.git_api) + self.version_repo = VersionRepository(self.config_file) + self.release_repo = ReleaseRepository(self.version_repo, self.release_type_repo, self.main_branch) self.release_version = None self.bump_version = None self.commit_string = None - def init_release(self): - init_service = InitReleaseService(self.version_repo) - self.release_version = init_service.create_release_version(self.commit_string) - self.bump_version = self.release_version.create_bump_version() - def prepare_release(self): - prepare_release_service = PrepareReleaseService(self.version_repo, self.git_api, self.config_file, self.main_branch) + prepare_release_service = PrepareReleaseService(self.release_repo) + if self.release_version is None or self.bump_version is None: raise Exception('prepare_release was called before init_release') # prepare_release_service.run_tests() # not implemented - prepare_release_service.write_and_commit_release(self.release_version) - prepare_release_service.write_and_commit_bump(self.bump_version) + prepare_release_service.write_and_commit_release() + prepare_release_service.write_and_commit_bump() def tag_and_push_release(self): tag_and_push_release_service = TagAndPushReleaseService(self.git_api) From 9bde301ee404fbfeabac61fcd691064b2dcb06af Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 28 Feb 2023 14:56:07 +0100 Subject: [PATCH 102/243] Use release class Create it out of the given release_repo. Thus simplifying the call tree as we only access the release_repo in the service. --- services.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/services.py b/services.py index 068ee3b..da3c6a5 100644 --- a/services.py +++ b/services.py @@ -1,36 +1,29 @@ from pathlib import Path -from infrastructure import ReleaseRepository, GitApi +from infrastructure import VersionRepository, ReleaseRepository, GitApi from domain import Version, ReleaseType - -# Todo: can be removed -class InitReleaseService(): - - def __init__(self, release_repo: ReleaseRepository): - self.release_repo = release_repo - - def get_version(self): - return self.release_repo.get_release().version - - class PrepareReleaseService(): def __init__(self, release_repo: ReleaseRepository): self.release_repo = release_repo + self.release = release_repo.get_release() + self.git_api = GitApi() + self.main_branch = None def __write_and_commit_version(self, version: Version, commit_message: str): - if self.main_branch != self.git_repository.get_current_branch(): + if self.main_branch != self.git_api.get_current_branch(): raise Exception('Trying to release while not on main branch') - self.version_repository.write_file(version.get_version_string()) - self.git_repository.add_file(self.config_file) - self.git_repository.commit(commit_message) + 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.commit(commit_message) - def write_and_commit_release(self, release_version: Version): - self.__write_and_commit_version(release_version, commit_message=f'Release {release_version.get_version_string()}') + def write_and_commit_release(self): + self.__write_and_commit_version(self.release.release_version(), commit_message=f'Release {self.release.release_version().get_version_string()}') - def write_and_commit_bump(self, bump_version: Version): - self.__write_and_commit_version(bump_version, commit_message=f'Version bump') + def write_and_commit_bump(self): + self.__write_and_commit_version(self.release.bump_version(), commit_message=f'Version bump') + class TagAndPushReleaseService(): @@ -41,6 +34,6 @@ class TagAndPushReleaseService(): annotation = 'v' + release_version.get_version_string() message = 'Release ' + annotation self.git_api.tag_annotated(annotation, message, 1) - # self.git_repository.push() + # self.git_api.push() From 338cafc211e7a6c2f7317f725f8a979ca4e93368 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 14:59:06 +0100 Subject: [PATCH 103/243] Validate correct branch in Release object Avoids throwing exceptions in service and delegates validation to the domain level --- services.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/services.py b/services.py index da3c6a5..741e3e1 100644 --- a/services.py +++ b/services.py @@ -8,12 +8,10 @@ class PrepareReleaseService(): self.release_repo = release_repo self.release = release_repo.get_release() self.git_api = GitApi() - self.main_branch = None def __write_and_commit_version(self, version: Version, commit_message: str): - if self.main_branch != self.git_api.get_current_branch(): - raise Exception('Trying to release while not on main branch') - + self.release.validate(self.release_repo.main_branch) + 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.commit(commit_message) From 28344667dbf31ac673dccdb980821771c5121cee Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 15:03:11 +0100 Subject: [PATCH 104/243] Remove unused fields from ReleaseMixin These are handled through repos and services instead --- release_mixin.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 6064a96..39b9ab7 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -31,16 +31,9 @@ class ReleaseMixin(DevopsBuild): self.release_type_repo = ReleaseTypeRepository(self.git_api) self.version_repo = VersionRepository(self.config_file) self.release_repo = ReleaseRepository(self.version_repo, self.release_type_repo, self.main_branch) - self.release_version = None - self.bump_version = None - self.commit_string = None def prepare_release(self): prepare_release_service = PrepareReleaseService(self.release_repo) - - if self.release_version is None or self.bump_version is None: - raise Exception('prepare_release was called before init_release') - # prepare_release_service.run_tests() # not implemented prepare_release_service.write_and_commit_release() prepare_release_service.write_and_commit_bump() From 9c9676b5b84303a0809faa6a77735c4a0d040205 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 15:04:30 +0100 Subject: [PATCH 105/243] Pass Release instead of Version object --- release_mixin.py | 2 +- services.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 39b9ab7..05f97da 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -40,4 +40,4 @@ class ReleaseMixin(DevopsBuild): def tag_and_push_release(self): tag_and_push_release_service = TagAndPushReleaseService(self.git_api) - tag_and_push_release_service.tag_and_push_release(self.release_version) + tag_and_push_release_service.tag_and_push_release(self.release_repo.get_release()) diff --git a/services.py b/services.py index 741e3e1..296bf98 100644 --- a/services.py +++ b/services.py @@ -1,6 +1,6 @@ from pathlib import Path from infrastructure import VersionRepository, ReleaseRepository, GitApi -from domain import Version, ReleaseType +from domain import Version, ReleaseType, Release class PrepareReleaseService(): @@ -28,8 +28,8 @@ class TagAndPushReleaseService(): def __init__(self, git_api: GitApi): self.git_api = git_api - def tag_and_push_release(self, release_version: Version): - annotation = 'v' + release_version.get_version_string() + def tag_and_push_release(self, release: Release): + annotation = 'v' + release.version.get_version_string() message = 'Release ' + annotation self.git_api.tag_annotated(annotation, message, 1) # self.git_api.push() From 24e8f65aafa8daad5256f339a06a4e544b38a4f7 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 15:07:31 +0100 Subject: [PATCH 106/243] Remove run_test line Will not be implemented, as it is already run in CI beforehand --- release_mixin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/release_mixin.py b/release_mixin.py index 05f97da..08c3a34 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -34,7 +34,6 @@ class ReleaseMixin(DevopsBuild): def prepare_release(self): prepare_release_service = PrepareReleaseService(self.release_repo) - # prepare_release_service.run_tests() # not implemented prepare_release_service.write_and_commit_release() prepare_release_service.write_and_commit_bump() From 8855a2c6b605095adbe1a93a76256bdb0aef72ec Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 15:08:41 +0100 Subject: [PATCH 107/243] Remove unused imports --- infrastructure.py | 3 +-- release_mixin.py | 1 - services.py | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/infrastructure.py b/infrastructure.py index 3989ed6..b367c74 100644 --- a/infrastructure.py +++ b/infrastructure.py @@ -1,6 +1,5 @@ from domain import Release, Version, ReleaseType -from infrastructure_api import FileHandler, SystemAPI, GitApi -from pathlib import Path +from infrastructure_api import FileHandler class VersionRepository(): diff --git a/release_mixin.py b/release_mixin.py index 08c3a34..f35725c 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -4,7 +4,6 @@ from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path from infrastructure import GitApi, ReleaseRepository, ReleaseTypeRepository, VersionRepository from services import PrepareReleaseService, TagAndPushReleaseService -from domain import ReleaseType, Version def create_release_mixin_config(config_file, main_branch) -> dict: config = {} diff --git a/services.py b/services.py index 296bf98..b3b5771 100644 --- a/services.py +++ b/services.py @@ -1,6 +1,5 @@ -from pathlib import Path -from infrastructure import VersionRepository, ReleaseRepository, GitApi -from domain import Version, ReleaseType, Release +from infrastructure import ReleaseRepository, GitApi +from domain import Version, Release class PrepareReleaseService(): From 5700b42098286b40f2ca190040891e1b897a15f8 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 12:47:27 +0100 Subject: [PATCH 108/243] Remove redundant method --- infrastructure_api.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/infrastructure_api.py b/infrastructure_api.py index 06e2d17..2490686 100644 --- a/infrastructure_api.py +++ b/infrastructure_api.py @@ -203,11 +203,6 @@ class GitApi(): output = self.get_latest_n_commits(1) return " ".join(output) - def tag_annotated(self, annotation: str, message: str): - self.system_repository.run_checked( - 'git', 'tag', '-a', annotation, '-m', message) - return self.system_repository.stdout - def tag_annotated(self, annotation: str, message: str, count: int): self.system_repository.run_checked( 'git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') From 455585f78cf7031a95429a4a22eedb4511663c1d Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 13:12:02 +0100 Subject: [PATCH 109/243] Remove Config class If the config class was to replace the config dict, further refactorings upstream would be needed. This is out of scope. --- domain.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/domain.py b/domain.py index bcfedeb..fe0805e 100644 --- a/domain.py +++ b/domain.py @@ -1,11 +1,6 @@ from enum import Enum from pathlib import Path -class Config(): - def __init__(self, main_branch): - pass - - class ReleaseType(Enum): MAJOR = 0 MINOR = 1 @@ -13,7 +8,6 @@ class ReleaseType(Enum): SNAPSHOT = 3 BUMP = None - class Version(): def __init__(self, id: Path, version_list: list): @@ -60,7 +54,6 @@ class Version(): bump_version.increment(ReleaseType.BUMP) return bump_version - class Release(): def __init__(self, release_type: ReleaseType, version: Version, current_branch: str): self.release_type = release_type From 9dc32ac92fd67cf18630633e6f045e33d4026970 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 13:17:26 +0100 Subject: [PATCH 110/243] Refactor Class Name --- test/test_infrastructure.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_infrastructure.py b/test/test_infrastructure.py index 5ae86ec..3aa05c0 100644 --- a/test/test_infrastructure.py +++ b/test/test_infrastructure.py @@ -21,7 +21,7 @@ from domain import ReleaseType, Release from infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository from infrastructure_api import GitApi -class TestGitApi(GitApi): +class MyGitApi(GitApi): def __init__(self, commit_string): self.commit_string = commit_string @@ -115,33 +115,33 @@ def test_release_repository(tmp_path): f.write_text(contents) # test - sut = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(TestGitApi('MINOR test')), 'main') + sut = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(MyGitApi('MINOR test')), 'main') release = sut.get_release() assert release is not None def test_release_type_repository(): - sut = ReleaseTypeRepository(TestGitApi('MINOR test')) + sut = ReleaseTypeRepository(MyGitApi('MINOR test')) release_type = sut.get_release_type() assert release_type is ReleaseType.MINOR - sut = ReleaseTypeRepository(TestGitApi('MINOR bla')) + sut = ReleaseTypeRepository(MyGitApi('MINOR bla')) release_type = sut.get_release_type() assert release_type is ReleaseType.MINOR - sut = ReleaseTypeRepository(TestGitApi('Major bla')) + sut = ReleaseTypeRepository(MyGitApi('Major bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.MAJOR - sut = ReleaseTypeRepository(TestGitApi('PATCH bla')) + sut = ReleaseTypeRepository(MyGitApi('PATCH bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.PATCH - sut = ReleaseTypeRepository(TestGitApi('SNAPSHOT bla')) + sut = ReleaseTypeRepository(MyGitApi('SNAPSHOT bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.SNAPSHOT - sut = ReleaseTypeRepository(TestGitApi('bla')) + sut = ReleaseTypeRepository(MyGitApi('bla')) release_type = sut.get_release_type() assert release_type == None From 0d36a8a66a8712fdb25cfb808a72798f6ea0b3db Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 14:26:34 +0100 Subject: [PATCH 111/243] Fix service test and cleanup services We now create a temporary git repository that creates a release commit. Remove whitespaces, unused imports. --- services.py | 13 +++++-------- test/test_services.py | 39 ++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/services.py b/services.py index b3b5771..dd55cec 100644 --- a/services.py +++ b/services.py @@ -13,17 +13,16 @@ class PrepareReleaseService(): 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.commit(commit_message) + self.git_api.commit(commit_message) def write_and_commit_release(self): - self.__write_and_commit_version(self.release.release_version(), commit_message=f'Release {self.release.release_version().get_version_string()}') + self.__write_and_commit_version(self.release.release_version(), commit_message=f'Release v{self.release.release_version().get_version_string()}') - def write_and_commit_bump(self): - self.__write_and_commit_version(self.release.bump_version(), commit_message=f'Version bump') - + def write_and_commit_bump(self): + self.__write_and_commit_version(self.release.bump_version(), commit_message='Version bump') class TagAndPushReleaseService(): - + def __init__(self, git_api: GitApi): self.git_api = git_api @@ -32,5 +31,3 @@ class TagAndPushReleaseService(): message = 'Release ' + annotation self.git_api.tag_annotated(annotation, message, 1) # self.git_api.push() - - diff --git a/test/test_services.py b/test/test_services.py index 4815e2c..f82ca9b 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -1,3 +1,4 @@ +import pytest as pt from pathlib import Path import sys import os @@ -5,38 +6,50 @@ import os # getting the name of the directory # where the this file is present. current = os.path.dirname(os.path.realpath(__file__)) - + # Getting the parent directory name # where the current directory is present. parent = os.path.dirname(current) - + # adding the parent directory to # the sys.path. sys.path.append(parent) - + # now we can import the module in the parent # directory. -from services import InitReleaseService +from services import PrepareReleaseService from domain import ReleaseType from infrastructure import VersionRepository, ReleaseRepository, ReleaseTypeRepository from infrastructure_api import GitApi -def test_init_release_service(tmp_path): +def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): + monkeypatch.chdir(tmp_path) + +def test_prepare_release_service(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # init file_name = 'config.json' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - + with open(f'test/resources/{file_name}', 'r') as json_file: + contents = json_file.read() f = tmp_path / file_name f.write_text(contents) + change_test_dir(tmp_path, monkeypatch) # change the context of the script execution to tmp_path + + git_api = GitApi() + git_api.init() + git_api.add_file(file_name) + git_api.commit("MINOR release") repo = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(GitApi()), 'main') - release_service = InitReleaseService(repo) - version = release_service.get_version().create_release_version(ReleaseType.MINOR) + prepare_release_service = PrepareReleaseService(repo) + prepare_release_service.main_branch = "main" + prepare_release_service.write_and_commit_release() - assert "123.124.0" in version.get_version_string() + latest_commit = git_api.get_latest_commit() - version = version.create_bump_version() + assert '"Release v123.124.0 "\n' in latest_commit - assert "123.124.1-SNAPSHOT" in version.get_version_string() \ No newline at end of file + prepare_release_service.write_and_commit_bump() + latest_commit = git_api.get_latest_commit() + + assert '"Version bump "\n' in latest_commit From 99877b3fbf1220d1601c6be8e0d719ac3d279e50 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 14:31:15 +0100 Subject: [PATCH 112/243] Implement git init Allows creation of new git repo, may be of use in tests. --- infrastructure_api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infrastructure_api.py b/infrastructure_api.py index 2490686..125fb57 100644 --- a/infrastructure_api.py +++ b/infrastructure_api.py @@ -212,6 +212,9 @@ class GitApi(): self.system_repository.run_checked('git', 'branch', '--show-current') return ''.join(self.system_repository.stdout).rstrip() + def init(self): + self.system_repository.run_checked('git', 'init') + def add_file(self, file_path: Path): self.system_repository.run_checked('git', 'add', file_path) return self.system_repository.stdout From 8eb510d0346bb237aa8f78233c9f3f3842949651 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 16:06:53 +0100 Subject: [PATCH 113/243] Base bump version on release version --- domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain.py b/domain.py index fe0805e..a95e847 100644 --- a/domain.py +++ b/domain.py @@ -64,7 +64,7 @@ class Release(): return self.version.create_release_version(self.release_type) def bump_version(self): - return self.version.create_bump_version() + return self.release_version().create_bump_version() def validate(self, main_branch): result = [] From 63d3823d87a7626ec0542b031c66efd15dc86b8f Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 16:07:13 +0100 Subject: [PATCH 114/243] Implement release class test --- test/test_domain.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/test_domain.py b/test/test_domain.py index 412b409..abef6d1 100644 --- a/test/test_domain.py +++ b/test/test_domain.py @@ -17,12 +17,10 @@ sys.path.append(parent) # now we can import the module in the parent # directory. -from domain import Version, ReleaseType -from infrastructure import VersionRepository +from domain import Version, ReleaseType, Release -def test_version(tmp_path): +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] @@ -51,3 +49,13 @@ def test_version(tmp_path): 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" From 874106e1d44df8c5031df2b7c75a615b38167fb5 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 16:08:18 +0100 Subject: [PATCH 115/243] Disable test_release_mixin --- test/test_release_mixin.py | 53 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/test/test_release_mixin.py b/test/test_release_mixin.py index d9d387a..e85360b 100644 --- a/test/test_release_mixin.py +++ b/test/test_release_mixin.py @@ -1,5 +1,6 @@ import sys import os +import pytest as pt from pathlib import Path from ddadevops import * @@ -40,29 +41,29 @@ def initialize(project, CONFIG_FILE): build = MyBuild(project, config) return build -def test_release_mixin(tmp_path): - - #init - with open(f'test/resources/config.json', 'r') as json_file: - contents = json_file.read() - - CONFIG_FILE = tmp_path / "config.json" - CONFIG_FILE.write_text(contents) - - base_dir = "." - project = Project(base_dir) - - # init - build = initialize(project, CONFIG_FILE) - build.commit_string = "MAJOR bla" - build.init_release() - release_version = build.release_version - - # test - assert "124.0.0" in release_version.get_version_string() - - # init - bump_version = build.bump_version - - # test - assert "124.0.1-SNAPSHOT" in bump_version.get_version_string() \ No newline at end of file +#def test_release_mixin(tmp_path): +# +# #init +# with open(f'test/resources/config.json', 'r') as json_file: +# contents = json_file.read() +# +# CONFIG_FILE = tmp_path / "config.json" +# CONFIG_FILE.write_text(contents) +# +# base_dir = "." +# project = Project(base_dir) +# +# # init +# build = initialize(project, CONFIG_FILE) +# build.commit_string = "MAJOR bla" +# build.init_release() +# release_version = build.release_version +# +# # test +# assert "124.0.0" in release_version.get_version_string() +# +# # init +# bump_version = build.bump_version +# +# # test +# assert "124.0.1-SNAPSHOT" in bump_version.get_version_string() \ No newline at end of file From 21e941cce001eaf975a9419f63b77e781d2ee815 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 16:26:52 +0100 Subject: [PATCH 116/243] Import GitApi from correct module --- release_mixin.py | 3 ++- services.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index f35725c..c488f51 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -2,7 +2,8 @@ import copy from ddadevops import DevopsBuild from ddadevops import execute from ddadevops import gopass_field_from_path, gopass_password_from_path -from infrastructure import GitApi, ReleaseRepository, ReleaseTypeRepository, VersionRepository +from infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository +from infrastructure_api import GitApi from services import PrepareReleaseService, TagAndPushReleaseService def create_release_mixin_config(config_file, main_branch) -> dict: diff --git a/services.py b/services.py index dd55cec..76125a7 100644 --- a/services.py +++ b/services.py @@ -1,4 +1,5 @@ -from infrastructure import ReleaseRepository, GitApi +from infrastructure import ReleaseRepository +from infrastructure_api import GitApi from domain import Version, Release class PrepareReleaseService(): From 58e6473747fcb72198aa6965f8f5e7f5d6d65a62 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 16:37:42 +0100 Subject: [PATCH 117/243] Split tag_and_push_release into two --- release_mixin.py | 3 ++- services.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index c488f51..06648e7 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -39,4 +39,5 @@ class ReleaseMixin(DevopsBuild): def tag_and_push_release(self): tag_and_push_release_service = TagAndPushReleaseService(self.git_api) - tag_and_push_release_service.tag_and_push_release(self.release_repo.get_release()) + tag_and_push_release_service.tag_release(self.release_repo.get_release()) + # tag_and_push_release_service.push_release() diff --git a/services.py b/services.py index 76125a7..42b8543 100644 --- a/services.py +++ b/services.py @@ -27,8 +27,10 @@ class TagAndPushReleaseService(): def __init__(self, git_api: GitApi): self.git_api = git_api - def tag_and_push_release(self, release: Release): + def tag_release(self, release: Release): annotation = 'v' + release.version.get_version_string() message = 'Release ' + annotation self.git_api.tag_annotated(annotation, message, 1) - # self.git_api.push() + + def push_release(self): + self.git_api.push() From 950d76c883d52622ecbf2f5ae4310bd0a753b1e5 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 16:55:49 +0100 Subject: [PATCH 118/243] Add get_latest_tag to GitApi also rename system_repo to system_api --- infrastructure_api.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/infrastructure_api.py b/infrastructure_api.py index 125fb57..bd86de5 100644 --- a/infrastructure_api.py +++ b/infrastructure_api.py @@ -192,42 +192,46 @@ class SystemAPI(): class GitApi(): def __init__(self): - self.system_repository = SystemAPI() + self.system_api = SystemAPI() def get_latest_n_commits(self, n: int): - self.system_repository.run_checked( + self.system_api.run_checked( 'git', 'log', '--oneline', '--format="%s %b"', f'-n {n}') - return self.system_repository.stdout + return self.system_api.stdout def get_latest_commit(self): output = self.get_latest_n_commits(1) return " ".join(output) def tag_annotated(self, annotation: str, message: str, count: int): - self.system_repository.run_checked( + self.system_api.run_checked( 'git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') - return self.system_repository.stdout + return self.system_api.stdout + + def get_latest_tag(self): + self.system_api.run_checked('git', 'describe', '--tags', '--abbrev=0') + return self.system_api.stdout def get_current_branch(self): - self.system_repository.run_checked('git', 'branch', '--show-current') - return ''.join(self.system_repository.stdout).rstrip() + self.system_api.run_checked('git', 'branch', '--show-current') + return ''.join(self.system_api.stdout).rstrip() def init(self): - self.system_repository.run_checked('git', 'init') + self.system_api.run_checked('git', 'init') def add_file(self, file_path: Path): - self.system_repository.run_checked('git', 'add', file_path) - return self.system_repository.stdout + self.system_api.run_checked('git', 'add', file_path) + return self.system_api.stdout def commit(self, commit_message: str): - self.system_repository.run_checked( + self.system_api.run_checked( 'git', 'commit', '-m', commit_message) - return self.system_repository.stdout + return self.system_api.stdout def push(self): - self.system_repository.run_checked('git', 'push') - return self.system_repository.stdout + self.system_api.run_checked('git', 'push') + return self.system_api.stdout def checkout(self, branch: str): - self.system_repository.run_checked('git', 'checkout', branch) - return self.system_repository.stdout + self.system_api.run_checked('git', 'checkout', branch) + return self.system_api.stdout From d5d8b8e7e1cc82bcef335ccc838de7ee1ece3318 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 2 Mar 2023 16:56:09 +0100 Subject: [PATCH 119/243] Add test for TagAndPushReleaseService --- test/test_services.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/test_services.py b/test/test_services.py index f82ca9b..3bcf1b0 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -18,8 +18,7 @@ sys.path.append(parent) # now we can import the module in the parent # directory. -from services import PrepareReleaseService -from domain import ReleaseType +from services import PrepareReleaseService, TagAndPushReleaseService from infrastructure import VersionRepository, ReleaseRepository, ReleaseTypeRepository from infrastructure_api import GitApi @@ -53,3 +52,28 @@ def test_prepare_release_service(tmp_path: Path, monkeypatch: pt.MonkeyPatch): latest_commit = git_api.get_latest_commit() assert '"Version bump "\n' in latest_commit + +def test_tag_and_push_release_service(tmp_path: Path, monkeypatch: pt.MonkeyPatch): + # init + file_name = 'config.json' + with open(f'test/resources/{file_name}', 'r') as json_file: + contents = json_file.read() + f = tmp_path / file_name + f.write_text(contents) + change_test_dir(tmp_path, monkeypatch) # change the context of the script execution to tmp_path + + git_api = GitApi() + git_api.init() + git_api.add_file(file_name) + git_api.commit("MINOR release") + + repo = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(GitApi()), 'main') + + prepare_release_service = PrepareReleaseService(repo) + prepare_release_service.main_branch = "main" + prepare_release_service.write_and_commit_release() + + tag_and_push_release_service = TagAndPushReleaseService(git_api) + tag_and_push_release_service.tag_release(repo.get_release()) + + assert 'v123.124.0\n' in git_api.get_latest_tag() From 81458c6acc6fa058cea79d8ca7dc9538d730153c Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 18:16:03 +0100 Subject: [PATCH 120/243] dd todos from review --- release_mixin.py | 4 ++-- test/test_infrastructure_api.py | 3 +-- test/test_services.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/release_mixin.py b/release_mixin.py index 06648e7..03f3c99 100644 --- a/release_mixin.py +++ b/release_mixin.py @@ -22,13 +22,13 @@ def add_versions(config, release_version, bump_version) -> dict: class ReleaseMixin(DevopsBuild): - def __init__(self, project, config): + def __init__(self, project, config): # todo: create services in init, dont expose repos etc in api super().__init__(project, config) release_mixin_config = config['ReleaseMixin'] self.config_file = release_mixin_config['config_file'] self.main_branch = release_mixin_config['main_branch'] self.git_api = GitApi() - self.release_type_repo = ReleaseTypeRepository(self.git_api) + self.release_type_repo = ReleaseTypeRepository(self.git_api) # maybe get from env? self.version_repo = VersionRepository(self.config_file) self.release_repo = ReleaseRepository(self.version_repo, self.release_type_repo, self.main_branch) diff --git a/test/test_infrastructure_api.py b/test/test_infrastructure_api.py index 6d681da..d914d32 100644 --- a/test/test_infrastructure_api.py +++ b/test/test_infrastructure_api.py @@ -17,5 +17,4 @@ sys.path.append(parent) # now we can import the module in the parent # directory. -from infrastructure_api import GitApi - +from infrastructure_api import GitApi # todo: implement from services example diff --git a/test/test_services.py b/test/test_services.py index 3bcf1b0..4b3926c 100644 --- a/test/test_services.py +++ b/test/test_services.py @@ -25,7 +25,7 @@ from infrastructure_api import GitApi def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): monkeypatch.chdir(tmp_path) -def test_prepare_release_service(tmp_path: Path, monkeypatch: pt.MonkeyPatch): +def test_prepare_release_service(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # todo: maybe use mocks for service api tests # init file_name = 'config.json' with open(f'test/resources/{file_name}', 'r') as json_file: From 4fbbe26714660bdfb5f67b8ccb08c78b965f5364 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 2 Mar 2023 18:19:18 +0100 Subject: [PATCH 121/243] Update arch documentation --- architecture.png | Bin 66829 -> 0 bytes doc/arch.md | 1 + doc/architecture.png.png | Bin 0 -> 112256 bytes 3 files changed, 1 insertion(+) delete mode 100644 architecture.png create mode 100644 doc/arch.md create mode 100644 doc/architecture.png.png diff --git a/architecture.png b/architecture.png deleted file mode 100644 index f169118d4b9211ccb8028a0de39f1da71f2fd8a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66829 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 Ge*AC7Pe-Nz diff --git a/doc/arch.md b/doc/arch.md new file mode 100644 index 0000000..201223d --- /dev/null +++ b/doc/arch.md @@ -0,0 +1 @@ +[![](https://mermaid.ink/img/pako:eNrtV99vmzAQ_lcsPzUSrUjIj8JDpUqb9jSpaqs9TJGQg6_UGxhmTNes6v8-E0MCGIem6-MiBYnz3Xd3vu8ulxccZRRwgAv4VQKP4BMjsSApUp81r54CIolEvDmbup6D6sdEn21KllB0fnWFbiEBUsBX9sx4gMKQcSbD8CwX2Q9l76Ao4w8sDh9YArUtiSR7IhI6pge3HWnl4QuT1zlrYU8sirXgfpvDLeRZwWQmti07DVRb50RIFrGccNk2tEB_A1GwjA_Cmhm2sWvL4yEP4ho-neEMHZQSxsONIDx6tGenD4BTo7oO8tV3NVLaXIBChVBoaVOFPc7MrfixcNDCtRXoRkPU8jsQTyyCVsbGbfQZMwigdQaPbHccg-zn0WflQb2TzEF8jHIt_FCqM5uTrl3HUfeo0wgVeqJQChlGWZoyacBrTS2kMCi2u2mdBOjQly2c8eh7kAPtUyXxpMVG-Ia6PjfENuwkI3TXjw3ymy0VhVTJ3mza4m5l46A6ozBhhZwY92bJ6yi1zPaort1psNSALYUALrv9v29zs2p972Pn4wgfMAJ-CyYhJJzWjO5352h3B6jpNxupOmOwfunWMhKgFEMD6FgPjIVnHXlGxo27OpzJO8baiS2oQ9iRveFtIQXj8ajvZhIRSs_erNydVeYP0QeyZ1Om-QnUqdSHyn06d2xI_4nzYcRpXeWRdWBPr4GFpyLaym3xzLbySBLvLjovi8fDRDqyqt6T-JrTG6Vu3XE6S-g-E5vhu3wNh61hrI7GIU9BakqnQ-GZVEnSf4zh5HSaICrDAfbYbG0du7v6HqmqJ3ZwCkLt4FT9m3qpJGssHyGFNVadhSkRP9d4zV-VHilldrflEQ4eSFKAg8ucKg_1X6-e9DOt2m0vVLv89yxTSlKU-hUHL_gZB-fT6cWlt_Td5dzz1ACdL30Hb5Xcny4uZjN_7k2ni5k39y5fHfxnBzG7cD135fnzxdJfrObu6vUveFPSvA?type=png)](https://mermaid.live/edit#pako:eNrtV99vmzAQ_lcsPzUSrUjIj8JDpUqb9jSpaqs9TJGQg6_UGxhmTNes6v8-E0MCGIem6-MiBYnz3Xd3vu8ulxccZRRwgAv4VQKP4BMjsSApUp81r54CIolEvDmbup6D6sdEn21KllB0fnWFbiEBUsBX9sx4gMKQcSbD8CwX2Q9l76Ao4w8sDh9YArUtiSR7IhI6pge3HWnl4QuT1zlrYU8sirXgfpvDLeRZwWQmti07DVRb50RIFrGccNk2tEB_A1GwjA_Cmhm2sWvL4yEP4ho-neEMHZQSxsONIDx6tGenD4BTo7oO8tV3NVLaXIBChVBoaVOFPc7MrfixcNDCtRXoRkPU8jsQTyyCVsbGbfQZMwigdQaPbHccg-zn0WflQb2TzEF8jHIt_FCqM5uTrl3HUfeo0wgVeqJQChlGWZoyacBrTS2kMCi2u2mdBOjQly2c8eh7kAPtUyXxpMVG-Ia6PjfENuwkI3TXjw3ymy0VhVTJ3mza4m5l46A6ozBhhZwY92bJ6yi1zPaort1psNSALYUALrv9v29zs2p972Pn4wgfMAJ-CyYhJJzWjO5352h3B6jpNxupOmOwfunWMhKgFEMD6FgPjIVnHXlGxo27OpzJO8baiS2oQ9iRveFtIQXj8ajvZhIRSs_erNydVeYP0QeyZ1Om-QnUqdSHyn06d2xI_4nzYcRpXeWRdWBPr4GFpyLaym3xzLbySBLvLjovi8fDRDqyqt6T-JrTG6Vu3XE6S-g-E5vhu3wNh61hrI7GIU9BakqnQ-GZVEnSf4zh5HSaICrDAfbYbG0du7v6HqmqJ3ZwCkLt4FT9m3qpJGssHyGFNVadhSkRP9d4zV-VHilldrflEQ4eSFKAg8ucKg_1X6-e9DOt2m0vVLv89yxTSlKU-hUHL_gZB-fT6cWlt_Td5dzz1ACdL30Hb5Xcny4uZjN_7k2ni5k39y5fHfxnBzG7cD135fnzxdJfrObu6vUveFPSvA) \ No newline at end of file diff --git a/doc/architecture.png.png b/doc/architecture.png.png new file mode 100644 index 0000000000000000000000000000000000000000..cef9e94942292c52776003e4850d7b62092a9797 GIT binary patch literal 112256 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 rM;NnK>^x-Z|BY_{zx|~$b$p`!`Oo2kUe Date: Fri, 3 Mar 2023 13:31:51 +0100 Subject: [PATCH 122/243] Add mock API --- .../release-mixin/test/mock_domain.py | 47 +++++++++++++++++ .../release-mixin/test/mock_infrastructure.py | 46 +++++++++++++++++ .../test/mock_infrastructure_api.py | 51 +++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 src/main/python/ddadevops/release-mixin/test/mock_domain.py create mode 100644 src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py create mode 100644 src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py diff --git a/src/main/python/ddadevops/release-mixin/test/mock_domain.py b/src/main/python/ddadevops/release-mixin/test/mock_domain.py new file mode 100644 index 0000000..1031c3e --- /dev/null +++ b/src/main/python/ddadevops/release-mixin/test/mock_domain.py @@ -0,0 +1,47 @@ +import os +import sys + +current = os.path.dirname(os.path.realpath(__file__)) +parent = os.path.dirname(current) +sys.path.append(parent) + +from domain import ReleaseType + +class MockVersion(): + + def __init__(self, id = None, version_list = None): + self.id = None + self.version_list = None + self.version_string = None + self.is_snapshot = None + + def increment(self, release_type: ReleaseType): + pass + + def get_version_string(self) -> str: + return "" + + def create_release_version(self, release_type: ReleaseType): + return MockVersion() + + def create_bump_version(self): + return MockVersion() + +class MockRelease(): + def __init__(self, release_type, version, current_branch): + self.release_type = release_type + self.version = version + self.current_branch = current_branch + + def release_version(self): + return MockVersion() + + def bump_version(self): + return MockVersion() + + def validate(self, main_branch): + self.validate_count =+ 1 + return self.is_valid + + def is_valid(self, main_branch): + return True diff --git a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py new file mode 100644 index 0000000..3908fec --- /dev/null +++ b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py @@ -0,0 +1,46 @@ +import os +import sys +from mock_domain import MockRelease, MockVersion +from mock_infrastructure_api import MockGitApi + +current = os.path.dirname(os.path.realpath(__file__)) +parent = os.path.dirname(current) +sys.path.append(parent) + +from domain import ReleaseType + +class MockVersionRepository(): + + def __init__(self): + self.file = None + self.file_handler = None + + def load_file(self): + pass + + def write_file(self, version_string): + self.write_file_count =+ 1 + pass + + def parse_file(self): + pass + + def get_version(self) -> MockVersion: + return MockVersion() + +class MockReleaseTypeRepository(): + def __init__(self, mock_git_api: MockGitApi): + self.git_api = mock_git_api + + def get_release_type(self): + return ReleaseType.MINOR + +class MockReleaseRepository(): + def __init__(self, version_repository: MockVersionRepository, release_type_repository: MockReleaseTypeRepository, main_branch: str): + self.version_repository = version_repository + self.release_type_repository = release_type_repository + self.main_branch = main_branch + + def get_release(self) -> MockRelease: + self.get_release_count =+ 1 + return MockRelease(self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch) \ No newline at end of file diff --git a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py new file mode 100644 index 0000000..3791fa6 --- /dev/null +++ b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py @@ -0,0 +1,51 @@ +class MockSystemAPI(): + + def __init__(self): + self.stdout = [""] + self.stderr = [""] + + def run(self, args): + pass + + def run_checked(self, *args): + self.run(args) + pass + +class MockGitApi(): + + def __init__(self): + self.system_api = MockSystemAPI() + + def get_latest_n_commits(self, n: int): + return " " + + def get_latest_commit(self): + return " " + + def tag_annotated(self, annotation: str, message: str, count: int): + self.tag_annotated_count =+ 1 + return " " + + def get_latest_tag(self): + return " " + + def get_current_branch(self): + return " " + + def init(self): + pass + + def add_file(self, file_path): + self.add_file_count =+ 1 + return " " + + def commit(self, commit_message: str): + self.commit_count =+ 1 + return commit_message + + def push(self): + self.push_count =+ 1 + return " " + + def checkout(self, branch: str): + return " " From b63e6b47fcb0d9bf8fd85f34f77b95c0601a931b Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 3 Mar 2023 13:35:24 +0100 Subject: [PATCH 123/243] Implement basic test for infrastructure api --- .../test/test_infrastructure_api.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py index d914d32..32a2d06 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py @@ -1,4 +1,5 @@ from pathlib import Path +import pytest as pt import sys import os @@ -17,4 +18,25 @@ sys.path.append(parent) # now we can import the module in the parent # directory. -from infrastructure_api import GitApi # todo: implement from services example +from infrastructure_api import GitApi + +def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): + monkeypatch.chdir(tmp_path) + +def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): + # init + file_name = 'config.json' + with open(f'test/resources/{file_name}', 'r') as json_file: + contents = json_file.read() + f = tmp_path / file_name + f.write_text(contents) + change_test_dir(tmp_path, monkeypatch) # change the context of the script execution to tmp_path + + git_api = GitApi() + git_api.init() + git_api.add_file(file_name) + git_api.commit("MINOR release") + + # test + latest_commit = git_api.get_latest_commit() + assert "MINOR release" in latest_commit From 15992d3500359621f0978a0db88030a80feeebff Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 3 Mar 2023 13:36:39 +0100 Subject: [PATCH 124/243] WIP Implement service test --- .../release-mixin/test/test_services.py | 74 ++++++------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/test/test_services.py b/src/main/python/ddadevops/release-mixin/test/test_services.py index 4b3926c..51fa89b 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_services.py +++ b/src/main/python/ddadevops/release-mixin/test/test_services.py @@ -19,61 +19,33 @@ sys.path.append(parent) # directory. from services import PrepareReleaseService, TagAndPushReleaseService -from infrastructure import VersionRepository, ReleaseRepository, ReleaseTypeRepository -from infrastructure_api import GitApi +from mock_infrastructure import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository +from mock_infrastructure_api import MockGitApi -def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): - monkeypatch.chdir(tmp_path) - -def test_prepare_release_service(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # todo: maybe use mocks for service api tests +def test_prepare_release_service(): # todo: maybe use mocks for service api tests # init - file_name = 'config.json' - with open(f'test/resources/{file_name}', 'r') as json_file: - contents = json_file.read() - f = tmp_path / file_name - f.write_text(contents) - change_test_dir(tmp_path, monkeypatch) # change the context of the script execution to tmp_path - - git_api = GitApi() - git_api.init() - git_api.add_file(file_name) - git_api.commit("MINOR release") - - repo = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(GitApi()), 'main') - prepare_release_service = PrepareReleaseService(repo) - prepare_release_service.main_branch = "main" + mock_release_repo = MockReleaseRepository(MockVersionRepository(), MockReleaseTypeRepository(MockGitApi()), 'main') + prepare_release_service = PrepareReleaseService(mock_release_repo) + prepare_release_service.git_api = MockGitApi() prepare_release_service.write_and_commit_release() - latest_commit = git_api.get_latest_commit() + #test + assert prepare_release_service.release_repo.get_release_count == 1 + assert prepare_release_service.release.validate_count == 1 + assert prepare_release_service.release_repo.version_repository.write_file_count == 1 + assert prepare_release_service.git_api.add_file_count == 1 + assert prepare_release_service.git_api.commit_count == 1 - assert '"Release v123.124.0 "\n' in latest_commit - - prepare_release_service.write_and_commit_bump() - latest_commit = git_api.get_latest_commit() - - assert '"Version bump "\n' in latest_commit - -def test_tag_and_push_release_service(tmp_path: Path, monkeypatch: pt.MonkeyPatch): + prepare_release_service.write_and_commit_release() #todo: add asserts + +def test_tag_and_push_release_service(): # init - file_name = 'config.json' - with open(f'test/resources/{file_name}', 'r') as json_file: - contents = json_file.read() - f = tmp_path / file_name - f.write_text(contents) - change_test_dir(tmp_path, monkeypatch) # change the context of the script execution to tmp_path + mock_release_repo = MockReleaseRepository(MockVersionRepository(), MockReleaseTypeRepository(MockGitApi()), 'main') + tag_and_push_release_service = TagAndPushReleaseService(MockGitApi()) + tag_and_push_release_service.tag_release(mock_release_repo.get_release()) + tag_and_push_release_service.push_release() - git_api = GitApi() - git_api.init() - git_api.add_file(file_name) - git_api.commit("MINOR release") - - repo = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(GitApi()), 'main') - - prepare_release_service = PrepareReleaseService(repo) - prepare_release_service.main_branch = "main" - prepare_release_service.write_and_commit_release() - - tag_and_push_release_service = TagAndPushReleaseService(git_api) - tag_and_push_release_service.tag_release(repo.get_release()) - - assert 'v123.124.0\n' in git_api.get_latest_tag() + #test + assert tag_and_push_release_service.git_api.tag_annotated_count == 1 + assert tag_and_push_release_service.git_api.push_count == 1 + From 3f441482379f74efcffeabd6754d779e2f11383d Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 3 Mar 2023 14:54:48 +0100 Subject: [PATCH 125/243] Implement write_and_commit_bump() test --- .../ddadevops/release-mixin/test/test_services.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/test/test_services.py b/src/main/python/ddadevops/release-mixin/test/test_services.py index 51fa89b..25c56a1 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_services.py +++ b/src/main/python/ddadevops/release-mixin/test/test_services.py @@ -36,7 +36,14 @@ def test_prepare_release_service(): # todo: maybe use mocks for service api test assert prepare_release_service.git_api.add_file_count == 1 assert prepare_release_service.git_api.commit_count == 1 - prepare_release_service.write_and_commit_release() #todo: add asserts + # init + prepare_release_service.write_and_commit_bump() #todo: add asserts + + # test + assert prepare_release_service.release.validate_count == 2 + assert prepare_release_service.release_repo.version_repository.write_file_count == 2 + assert prepare_release_service.git_api.add_file_count == 2 + assert prepare_release_service.git_api.commit_count == 2 def test_tag_and_push_release_service(): # init @@ -47,5 +54,4 @@ def test_tag_and_push_release_service(): #test assert tag_and_push_release_service.git_api.tag_annotated_count == 1 - assert tag_and_push_release_service.git_api.push_count == 1 - + assert tag_and_push_release_service.git_api.push_count == 1 From c76804ad921ed069b0638729547a1e4ddafc763e Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 3 Mar 2023 14:55:38 +0100 Subject: [PATCH 126/243] Make counters count up --- .../ddadevops/release-mixin/test/mock_domain.py | 7 ++++--- .../release-mixin/test/mock_infrastructure.py | 6 ++++-- .../release-mixin/test/mock_infrastructure_api.py | 14 +++++++++----- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/test/mock_domain.py b/src/main/python/ddadevops/release-mixin/test/mock_domain.py index 1031c3e..b5b4b65 100644 --- a/src/main/python/ddadevops/release-mixin/test/mock_domain.py +++ b/src/main/python/ddadevops/release-mixin/test/mock_domain.py @@ -32,15 +32,16 @@ class MockRelease(): self.release_type = release_type self.version = version self.current_branch = current_branch + self.validate_count = 0 def release_version(self): - return MockVersion() + return self.version.create_release_version(self.release_type) def bump_version(self): - return MockVersion() + return self.release_version().create_bump_version() def validate(self, main_branch): - self.validate_count =+ 1 + self.validate_count += 1 return self.is_valid def is_valid(self, main_branch): diff --git a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py index 3908fec..6e66498 100644 --- a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py +++ b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py @@ -14,12 +14,13 @@ class MockVersionRepository(): def __init__(self): self.file = None self.file_handler = None + self.write_file_count = 0 def load_file(self): pass def write_file(self, version_string): - self.write_file_count =+ 1 + self.write_file_count += 1 pass def parse_file(self): @@ -40,7 +41,8 @@ class MockReleaseRepository(): self.version_repository = version_repository self.release_type_repository = release_type_repository self.main_branch = main_branch + self.get_release_count = 0 def get_release(self) -> MockRelease: - self.get_release_count =+ 1 + self.get_release_count += 1 return MockRelease(self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch) \ No newline at end of file diff --git a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py index 3791fa6..e073b7f 100644 --- a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py @@ -15,6 +15,10 @@ class MockGitApi(): def __init__(self): self.system_api = MockSystemAPI() + self.tag_annotated_count = 0 + self.add_file_count = 0 + self.commit_count = 0 + self.push_count = 0 def get_latest_n_commits(self, n: int): return " " @@ -22,8 +26,8 @@ class MockGitApi(): def get_latest_commit(self): return " " - def tag_annotated(self, annotation: str, message: str, count: int): - self.tag_annotated_count =+ 1 + def tag_annotated(self, annotation: str, message: str, count: int): + self.tag_annotated_count += 1 return " " def get_latest_tag(self): @@ -36,15 +40,15 @@ class MockGitApi(): pass def add_file(self, file_path): - self.add_file_count =+ 1 + self.add_file_count += 1 return " " def commit(self, commit_message: str): - self.commit_count =+ 1 + self.commit_count += 1 return commit_message def push(self): - self.push_count =+ 1 + self.push_count += 1 return " " def checkout(self, branch: str): From 8c0ae19fdc6440dbac158011bb19c17202600582 Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 3 Mar 2023 15:07:02 +0100 Subject: [PATCH 127/243] Clean up whitespaces --- src/main/python/ddadevops/release-mixin/infrastructure.py | 4 ++-- .../ddadevops/release-mixin/test/mock_infrastructure_api.py | 6 +++--- .../ddadevops/release-mixin/test/test_infrastructure_api.py | 2 +- .../python/ddadevops/release-mixin/test/test_services.py | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/infrastructure.py b/src/main/python/ddadevops/release-mixin/infrastructure.py index b367c74..6843e58 100644 --- a/src/main/python/ddadevops/release-mixin/infrastructure.py +++ b/src/main/python/ddadevops/release-mixin/infrastructure.py @@ -5,7 +5,7 @@ class VersionRepository(): def __init__(self, file): self.file = file - self.file_handler = None + self.file_handler = None def load_file(self): self.file_handler = FileHandler.from_file_path(self.file) @@ -21,7 +21,7 @@ class VersionRepository(): version_list, is_snapshot = self.file_handler.parse() return version_list, is_snapshot - def get_version(self) -> Version: + def get_version(self) -> Version: self.file_handler = self.load_file() version_list, is_snapshot = self.parse_file() diff --git a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py index e073b7f..2e8ac63 100644 --- a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py @@ -20,17 +20,17 @@ class MockGitApi(): self.commit_count = 0 self.push_count = 0 - def get_latest_n_commits(self, n: int): + def get_latest_n_commits(self, n: int): return " " def get_latest_commit(self): return " " - def tag_annotated(self, annotation: str, message: str, count: int): + def tag_annotated(self, annotation: str, message: str, count: int): self.tag_annotated_count += 1 return " " - def get_latest_tag(self): + def get_latest_tag(self): return " " def get_current_branch(self): diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py index 32a2d06..993c888 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py @@ -37,6 +37,6 @@ def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): git_api.add_file(file_name) git_api.commit("MINOR release") - # test + # test latest_commit = git_api.get_latest_commit() assert "MINOR release" in latest_commit diff --git a/src/main/python/ddadevops/release-mixin/test/test_services.py b/src/main/python/ddadevops/release-mixin/test/test_services.py index 25c56a1..214bfbf 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_services.py +++ b/src/main/python/ddadevops/release-mixin/test/test_services.py @@ -37,7 +37,7 @@ def test_prepare_release_service(): # todo: maybe use mocks for service api test assert prepare_release_service.git_api.commit_count == 1 # init - prepare_release_service.write_and_commit_bump() #todo: add asserts + prepare_release_service.write_and_commit_bump() # test assert prepare_release_service.release.validate_count == 2 @@ -48,10 +48,10 @@ def test_prepare_release_service(): # todo: maybe use mocks for service api test def test_tag_and_push_release_service(): # init mock_release_repo = MockReleaseRepository(MockVersionRepository(), MockReleaseTypeRepository(MockGitApi()), 'main') - tag_and_push_release_service = TagAndPushReleaseService(MockGitApi()) + tag_and_push_release_service = TagAndPushReleaseService(MockGitApi()) tag_and_push_release_service.tag_release(mock_release_repo.get_release()) tag_and_push_release_service.push_release() #test assert tag_and_push_release_service.git_api.tag_annotated_count == 1 - assert tag_and_push_release_service.git_api.push_count == 1 + assert tag_and_push_release_service.git_api.push_count == 1 From bdc1b5c942c3627777088dc9ff79dffd01d4da5f Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 3 Mar 2023 15:54:11 +0100 Subject: [PATCH 128/243] Add assertion for release count --- src/main/python/ddadevops/release-mixin/test/test_services.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/python/ddadevops/release-mixin/test/test_services.py b/src/main/python/ddadevops/release-mixin/test/test_services.py index 214bfbf..142e8bc 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_services.py +++ b/src/main/python/ddadevops/release-mixin/test/test_services.py @@ -40,6 +40,7 @@ def test_prepare_release_service(): # todo: maybe use mocks for service api test prepare_release_service.write_and_commit_bump() # test + assert prepare_release_service.release_repo.get_release_count == 1 assert prepare_release_service.release.validate_count == 2 assert prepare_release_service.release_repo.version_repository.write_file_count == 2 assert prepare_release_service.git_api.add_file_count == 2 From 68ffbf23e20a7ec20448e4ac450fbb1a54251f02 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 5 Mar 2023 13:01:17 +0100 Subject: [PATCH 129/243] last wip comment :-) --- src/test/python/test_domain.py | 83 ++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/test/python/test_domain.py diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py new file mode 100644 index 0000000..762e249 --- /dev/null +++ b/src/test/python/test_domain.py @@ -0,0 +1,83 @@ +from pybuilder.core import Project +from src.main.python.ddadevops.domain import Validateable, C4kBuild +from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config + + +class TestValidateable(Validateable): + def __init__(self, value): + self.field = value + + def validate(self): + return self.__validate_is_not_empty__("field") + + +def test_should_validate_non_empty_strings(): + sut = TestValidateable("content") + assert sut.is_valid() + + sut = TestValidateable(None) + assert not sut.is_valid() + + sut = TestValidateable("") + assert not sut.is_valid() + + +def test_should_validate_non_empty_others(): + sut = TestValidateable(1) + assert sut.is_valid() + + sut = TestValidateable(1.0) + assert sut.is_valid() + + sut = TestValidateable(True) + assert sut.is_valid() + + sut = TestValidateable(None) + assert not sut.is_valid() + + +def test_validate_with_reason(): + sut = TestValidateable(None) + assert sut.validate()[0] == "Field 'field' may not be empty." + + +def test_c4k_build_should_update_fqdn(tmp_path): + name = "should_update_fqdn" + project = Project(str(tmp_path), name=name) + project_config = { + "stage": "test", + "project_root_path": str(tmp_path), + "module": name, + "build_dir_name": "target", + } + config = {"issuer": "staging"} + auth = { + "jvb-auth-password": "pw1", + "jicofo-auth-password": "pw2", + "jicofo-component-secret": "pw3", + } + add_c4k_mixin_config( + project_config, + name, + config, + auth, + grafana_cloud_user="user", + grafana_cloud_password="password", + ) + sut = C4kBuild(project, project_config) + + assert { + "issuer": "staging", + "mon-cfg": { + "cluster-name": "should_update_fqdn", + "cluster-stage": "test", + "grafana-cloud-url": "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push", + }, + } == sut.c4k_mixin_config + assert {'jicofo-auth-password': 'pw2', + 'jicofo-component-secret': 'pw3', + 'jvb-auth-password': 'pw1', + 'mon-auth': {'grafana-cloud-password': 'password', + 'grafana-cloud-user': 'user'}} == sut.c4k_mixin_auth + + sut.update_runtime_config From 2acda459b293fbfb1c4945dedfeee0533ef8ea8d Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 5 Mar 2023 13:01:50 +0100 Subject: [PATCH 130/243] last wip comment :-) --- src/main/python/ddadevops/__init__.py | 4 +- src/main/python/ddadevops/application.py | 24 ++++++++- src/main/python/ddadevops/c4k_mixin.py | 6 ++- src/main/python/ddadevops/devops_build.py | 34 +++++++----- src/main/python/ddadevops/domain.py | 65 ++++++++++++----------- src/test/python/__init__.py | 0 src/test/{ => python}/test_c4k_mixin.py | 0 src/test/test_domain.py | 41 -------------- 8 files changed, 86 insertions(+), 88 deletions(-) create mode 100644 src/test/python/__init__.py rename src/test/{ => python}/test_c4k_mixin.py (100%) delete mode 100644 src/test/test_domain.py diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index 666f143..b423561 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -19,7 +19,7 @@ from .devops_terraform_build import DevopsTerraformBuild, create_devops_terrafor from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path -from .domain import Validateable, Build, DockerBuild -from .application import BuildService, DockerBuildService +from .domain import Validateable, Build, DockerBuild, C4kBuild +from .application import BuildService, DockerBuildService, C4kBuildService __version__ = "${version}" diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 9af97ba..6853720 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,4 +1,4 @@ -from .domain import Build, DockerBuild +from .domain import Build, DockerBuild, C4kBuild from .infrastructure import FileApi, ResourceApi, DockerApi @@ -52,3 +52,25 @@ class DockerBuildService(): def test(self, build: DockerBuild): self.docker_api.test(build.name(), build.build_path()) +class C4kBuildService(): + def write_c4k_config(self, build: C4kBuild): + fqdn = self.get('fqdn') + build.c4k_mixin_config.update({'fqdn': fqdn}) + with open(self.build_path() + '/out_c4k_config.yaml', 'w', encoding="utf-8") as output_file: + yaml.dump(self.c4k_mixin_config, output_file) + + def write_c4k_auth(self): + with open(self.build_path() + '/out_c4k_auth.yaml', 'w', encoding="utf-8") as output_file: + yaml.dump(self.c4k_mixin_auth, output_file) + chmod(self.build_path() + '/out_c4k_auth.yaml', 0o600) + + def c4k_apply(self, dry_run=False): + cmd = f'c4k-{self.c4k_module_name}-standalone.jar {self.build_path()}/out_c4k_config.yaml {self.build_path()}/out_c4k_auth.yaml > {self.build_path()}/out_{self.c4k_module_name}.yaml' + output = '' + if dry_run: + print(cmd) + else: + output = execute(cmd, True) + print(output) + return output + diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 6f5604c..4fe2542 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -1,5 +1,7 @@ from os import chmod import yaml +from .domain import C4kBuild +from .application import C4kBuildService from .python_util import execute from .devops_build import DevopsBuild from .credential import gopass_field_from_path, gopass_password_from_path @@ -32,7 +34,9 @@ def add_c4k_mixin_config(config, class C4kMixin(DevopsBuild): def __init__(self, project, config): - super().__init__(project, config) + self.build = C4kBuild(project, config) + self.c4k_build_service = C4kBuildService() + self.c4k_mixin_config = config['C4kMixin']['Config'] self.c4k_mixin_auth = config['C4kMixin']['Auth'] self.c4k_module_name = config['C4kMixin']['Name'] diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 438ccb0..5087a90 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -1,31 +1,41 @@ from .domain import Build from .application import BuildService -def create_devops_build_config(stage, project_root_path, module, - build_dir_name='target'): - return {'stage': stage, - 'project_root_path': project_root_path, - 'module': module, - 'build_dir_name': build_dir_name} + +def create_devops_build_config( + stage, project_root_path, module, build_dir_name="target" +): + return { + "stage": stage, + "project_root_path": project_root_path, + "module": module, + "build_dir_name": build_dir_name, + } + def get_devops_build(project): - return project.get_property('devops_build') + return project.get_property("devops_build") + # TODO: Remove from here! def get_tag_from_latest_commit(): try: - value = run('git describe --abbrev=0 --tags --exact-match', shell=True, - capture_output=True, check=True) - return value.stdout.decode('UTF-8').rstrip() + value = run( + "git describe --abbrev=0 --tags --exact-match", + shell=True, + capture_output=True, + check=True, + ) + return value.stdout.decode("UTF-8").rstrip() except CalledProcessError: return None -class DevopsBuild: +class DevopsBuild: def __init__(self, project, config): self.build = Build(project, config) self.build_service = BuildService() - project.set_property('devops_build', self) + project.set_property("devops_build", self) def name(self): return self.build.name() diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 69a3e9e..29b5a77 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -2,31 +2,27 @@ from typing import List from .python_util import filter_none -class Validateable(): +class Validateable: def __validate_is_not_empty__(self, field_name: str) -> List[str]: 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."] else: return [] - + def validate(self) -> List[str]: return [] def is_valid(self) -> bool: return len(self.validate()) < 1 - - class Build(Validateable): - def __init__(self, project, config): - # deprecate stage - self.stage = config['stage'] - self.project_root_path = config['project_root_path'] - self.module = config['module'] - self.build_dir_name = config['build_dir_name'] + self.stage = config["stage"] + self.project_root_path = config["project_root_path"] + self.module = config["module"] + self.build_dir_name = config["build_dir_name"] self.stack = {} self.project = project @@ -34,37 +30,44 @@ class Build(Validateable): return self.project.name def build_path(self): - path = [self.project_root_path, - self.build_dir_name, - self.name(), - self.module] - return '/'.join(filter_none(path)) + path = [self.project_root_path, self.build_dir_name, self.name(), self.module] + return "/".join(filter_none(path)) - def put(self, key, value): + def __put__(self, key, value): self.stack[key] = value - def get(self, key): + def __get__(self, key): return self.stack[key] - def get_keys(self, keys): + def __get_keys__(self, keys): result = {} for key in keys: result[key] = self.get(key) return result -class DockerBuild(Build): +class DockerBuild(Validateable): def __init__(self, project, config): - super().__init__(project, config) - project.build_depends_on('dda-python-terraform') - self.dockerhub_user = config['dockerhub_user'] - self.dockerhub_password = config['dockerhub_password'] - self.use_package_common_files = config['use_package_common_files'] - self.build_commons_path = config['build_commons_path'] - self.docker_build_commons_dir_name = config['docker_build_commons_dir_name'] - self.docker_publish_tag = config['docker_publish_tag'] + project.build_depends_on("dda-python-terraform") + self.build = Build(project, config) + self.dockerhub_user = config["dockerhub_user"] + self.dockerhub_password = config["dockerhub_password"] + self.use_package_common_files = config["use_package_common_files"] + self.build_commons_path = config["build_commons_path"] + self.docker_build_commons_dir_name = config["docker_build_commons_dir_name"] + self.docker_publish_tag = config["docker_publish_tag"] def docker_build_commons_path(self): - list = [self.build_commons_path, - self.docker_build_commons_dir_name] - return '/'.join(filter_none(list)) + '/' + list = [self.build_commons_path, self.docker_build_commons_dir_name] + return "/".join(filter_none(list)) + "/" + + +class C4kBuild(Validateable): + def __init__(self, project, config): + self.build = Build(project, config) + self.c4k_mixin_config = config["C4kMixin"]["Config"] + self.c4k_mixin_auth = config["C4kMixin"]["Auth"] + self.c4k_module_name = config["C4kMixin"]["Name"] + tmp = self.c4k_mixin_config["mon-cfg"] + tmp.update({"cluster-name": self.c4k_module_name, "cluster-stage": self.build.stage}) + self.c4k_mixin_config.update({"mon-cfg": tmp}) diff --git a/src/test/python/__init__.py b/src/test/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/test/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py similarity index 100% rename from src/test/test_c4k_mixin.py rename to src/test/python/test_c4k_mixin.py diff --git a/src/test/test_domain.py b/src/test/test_domain.py deleted file mode 100644 index 3ef12f0..0000000 --- a/src/test/test_domain.py +++ /dev/null @@ -1,41 +0,0 @@ -from src.main.python.ddadevops.domain import Validateable - - -class TestValidateable(Validateable): - def __init__(self, value): - self.field = value - - def validate(self): - return self.__validate_is_not_empty__('field') - - -def test_should_validate_non_empty_strings(): - - sut = TestValidateable("content") - assert sut.is_valid() - - sut = TestValidateable(None) - assert not sut.is_valid() - - sut = TestValidateable('') - assert not sut.is_valid() - - -def test_should_validate_non_empty_others(): - - sut = TestValidateable(1) - assert sut.is_valid() - - sut = TestValidateable(1.0) - assert sut.is_valid() - - sut = TestValidateable(True) - assert sut.is_valid() - - sut = TestValidateable(None) - assert not sut.is_valid() - -def test_validate_with_reason(): - - sut = TestValidateable(None) - assert sut.validate()[0] == "Field 'field' may not be empty." From dfc420f6c208a44138aa9d2d1ed0ccbab277f159 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 5 Mar 2023 13:09:46 +0100 Subject: [PATCH 131/243] BREAKING: c4k_module_name should be filled with builds-module-name --- src/main/python/ddadevops/c4k_mixin.py | 5 ++--- src/main/python/ddadevops/domain.py | 3 +-- src/test/python/test_domain.py | 8 +++----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 4fe2542..1ffee3e 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -7,12 +7,12 @@ from .devops_build import DevopsBuild from .credential import gopass_field_from_path, gopass_password_from_path def add_c4k_mixin_config(config, - c4k_module_name, c4k_config_dict, c4k_auth_dict, grafana_cloud_user=None, grafana_cloud_password=None, grafana_cloud_url='https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push'): + # TODO: Thats meissa specific, we've to move this out if not grafana_cloud_user: grafana_cloud_user = gopass_field_from_path( 'server/meissa/grafana-cloud', 'grafana-cloud-user') @@ -27,8 +27,7 @@ def add_c4k_mixin_config(config, 'grafana-cloud-url': grafana_cloud_url }}) config.update({'C4kMixin': {'Config': c4k_config_dict, - 'Auth': c4k_auth_dict, - 'Name': c4k_module_name}}) + 'Auth': c4k_auth_dict}}) return config diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 29b5a77..659c89d 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -67,7 +67,6 @@ class C4kBuild(Validateable): self.build = Build(project, config) self.c4k_mixin_config = config["C4kMixin"]["Config"] self.c4k_mixin_auth = config["C4kMixin"]["Auth"] - self.c4k_module_name = config["C4kMixin"]["Name"] tmp = self.c4k_mixin_config["mon-cfg"] - tmp.update({"cluster-name": self.c4k_module_name, "cluster-stage": self.build.stage}) + tmp.update({"cluster-name": self.build.module, "cluster-stage": self.build.stage}) self.c4k_mixin_config.update({"mon-cfg": tmp}) diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index 762e249..d2b8bff 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -42,12 +42,11 @@ def test_validate_with_reason(): def test_c4k_build_should_update_fqdn(tmp_path): - name = "should_update_fqdn" - project = Project(str(tmp_path), name=name) + project = Project(str(tmp_path), name='test-project') project_config = { "stage": "test", "project_root_path": str(tmp_path), - "module": name, + "module": 'module', "build_dir_name": "target", } config = {"issuer": "staging"} @@ -58,7 +57,6 @@ def test_c4k_build_should_update_fqdn(tmp_path): } add_c4k_mixin_config( project_config, - name, config, auth, grafana_cloud_user="user", @@ -69,7 +67,7 @@ def test_c4k_build_should_update_fqdn(tmp_path): assert { "issuer": "staging", "mon-cfg": { - "cluster-name": "should_update_fqdn", + "cluster-name": "module", "cluster-stage": "test", "grafana-cloud-url": "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push", }, From 55c9b7f2fd98effb92c72faeec39247594b4bf9b Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 5 Mar 2023 13:39:47 +0100 Subject: [PATCH 132/243] mv work to application --- src/main/python/ddadevops/application.py | 22 ++++++++-------- src/main/python/ddadevops/c4k_mixin.py | 32 +++--------------------- src/main/python/ddadevops/domain.py | 15 +++++++++++ src/test/python/test_domain.py | 4 ++- 4 files changed, 33 insertions(+), 40 deletions(-) diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 6853720..d39bdd8 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -53,19 +53,19 @@ class DockerBuildService(): self.docker_api.test(build.name(), build.build_path()) class C4kBuildService(): - def write_c4k_config(self, build: C4kBuild): - fqdn = self.get('fqdn') - build.c4k_mixin_config.update({'fqdn': fqdn}) - with open(self.build_path() + '/out_c4k_config.yaml', 'w', encoding="utf-8") as output_file: - yaml.dump(self.c4k_mixin_config, output_file) + def write_c4k_config(self, c4k_build: C4kBuild): + with open(c4k_build.build.build_path() + '/out_c4k_config.yaml', 'w', encoding="utf-8") as output_file: + yaml.dump(c4k_build.c4k_mixin_config(), output_file) - def write_c4k_auth(self): - with open(self.build_path() + '/out_c4k_auth.yaml', 'w', encoding="utf-8") as output_file: - yaml.dump(self.c4k_mixin_auth, output_file) - chmod(self.build_path() + '/out_c4k_auth.yaml', 0o600) + def write_c4k_auth(self, c4k_build: C4kBuild): + with open(c4k_build.build.build_path() + '/out_c4k_auth.yaml', 'w', encoding="utf-8") as output_file: + yaml.dump(c4k_build.c4k_mixin_auth, output_file) + chmod(c4k_build.build.build_path() + '/out_c4k_auth.yaml', 0o600) - def c4k_apply(self, dry_run=False): - cmd = f'c4k-{self.c4k_module_name}-standalone.jar {self.build_path()}/out_c4k_config.yaml {self.build_path()}/out_c4k_auth.yaml > {self.build_path()}/out_{self.c4k_module_name}.yaml' + def c4k_apply(self, c4k_build: C4kBuild, ry_run=False): + module = c4k_build.build.module + build_path = c4k_build.build.build_path() + cmd = f'c4k-{module}-standalone.jar {build_path}/out_c4k_config.yaml {build_path}/out_c4k_auth.yaml > {build_path}/out_{module}.yaml' output = '' if dry_run: print(cmd) diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 1ffee3e..c6e9471 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -1,8 +1,5 @@ -from os import chmod -import yaml from .domain import C4kBuild from .application import C4kBuildService -from .python_util import execute from .devops_build import DevopsBuild from .credential import gopass_field_from_path, gopass_password_from_path @@ -12,7 +9,6 @@ def add_c4k_mixin_config(config, grafana_cloud_user=None, grafana_cloud_password=None, grafana_cloud_url='https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push'): - # TODO: Thats meissa specific, we've to move this out if not grafana_cloud_user: grafana_cloud_user = gopass_field_from_path( 'server/meissa/grafana-cloud', 'grafana-cloud-user') @@ -33,34 +29,14 @@ def add_c4k_mixin_config(config, class C4kMixin(DevopsBuild): def __init__(self, project, config): - self.build = C4kBuild(project, config) + self.build = C4kBuild(project, config)(project, config) self.c4k_build_service = C4kBuildService() - self.c4k_mixin_config = config['C4kMixin']['Config'] - self.c4k_mixin_auth = config['C4kMixin']['Auth'] - self.c4k_module_name = config['C4kMixin']['Name'] - tmp = self.c4k_mixin_config['mon-cfg'] - tmp.update({'cluster-name': self.c4k_module_name, - 'cluster-stage': self.stage}) - self.c4k_mixin_config.update({'mon-cfg': tmp}) - def write_c4k_config(self): - fqdn = self.get('fqdn') - self.c4k_mixin_config.update({'fqdn': fqdn}) - with open(self.build_path() + '/out_c4k_config.yaml', 'w', encoding="utf-8") as output_file: - yaml.dump(self.c4k_mixin_config, output_file) + self.build_service.write_c4k_config(self.build) def write_c4k_auth(self): - with open(self.build_path() + '/out_c4k_auth.yaml', 'w', encoding="utf-8") as output_file: - yaml.dump(self.c4k_mixin_auth, output_file) - chmod(self.build_path() + '/out_c4k_auth.yaml', 0o600) + self.build_service.write_c4k_auth(self.build) def c4k_apply(self, dry_run=False): - cmd = f'c4k-{self.c4k_module_name}-standalone.jar {self.build_path()}/out_c4k_config.yaml {self.build_path()}/out_c4k_auth.yaml > {self.build_path()}/out_{self.c4k_module_name}.yaml' - output = '' - if dry_run: - print(cmd) - else: - output = execute(cmd, True) - print(output) - return output + self.build_service.c4k_apply(self.build, dry_run) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 659c89d..684c999 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -33,6 +33,12 @@ class Build(Validateable): path = [self.project_root_path, self.build_dir_name, self.name(), self.module] return "/".join(filter_none(path)) + # TODO: these functions should be located at TerraformBuild later on. + def update_runtime_config(self, fqdn, ipv4, ipv6): + self.__put__('fqdn', fqdn) + self.__put__('ipv4', ipv4) + self.__put__('ipv6', ipv6) + def __put__(self, key, value): self.stack[key] = value @@ -70,3 +76,12 @@ class C4kBuild(Validateable): tmp = self.c4k_mixin_config["mon-cfg"] tmp.update({"cluster-name": self.build.module, "cluster-stage": self.build.stage}) self.c4k_mixin_config.update({"mon-cfg": tmp}) + + def update_runtime_config(self, fqdn, ipv4, ipv6): + self.build.update_runtime_config(fqdn, ipv4, ipv6) + + def config(self): + fqdn = self.build.__get__('fqdn') + self.c4k_mixin_config.update({'fqdn': fqdn}) + return self.c4k_mixin_config + diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index d2b8bff..371b69c 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -63,15 +63,17 @@ def test_c4k_build_should_update_fqdn(tmp_path): grafana_cloud_password="password", ) sut = C4kBuild(project, project_config) + sut.update_runtime_config('test.de', None, None) assert { "issuer": "staging", + 'fqdn': 'test.de', "mon-cfg": { "cluster-name": "module", "cluster-stage": "test", "grafana-cloud-url": "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push", }, - } == sut.c4k_mixin_config + } == sut.config() assert {'jicofo-auth-password': 'pw2', 'jicofo-component-secret': 'pw3', 'jvb-auth-password': 'pw1', From 4bdbccd72253d393741e2a2603289ac9a722f2d0 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 5 Mar 2023 13:48:20 +0100 Subject: [PATCH 133/243] fix c4k_mixin tests --- src/main/python/ddadevops/application.py | 3 +++ src/main/python/ddadevops/c4k_mixin.py | 8 ++++---- src/test/python/test_c4k_mixin.py | 3 +-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index d39bdd8..83ea4ae 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -53,6 +53,9 @@ class DockerBuildService(): self.docker_api.test(build.name(), build.build_path()) class C4kBuildService(): + def __init__(self): + self.file_api = FileApi() + def write_c4k_config(self, c4k_build: C4kBuild): with open(c4k_build.build.build_path() + '/out_c4k_config.yaml', 'w', encoding="utf-8") as output_file: yaml.dump(c4k_build.c4k_mixin_config(), output_file) diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index c6e9471..93b0131 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -29,14 +29,14 @@ def add_c4k_mixin_config(config, class C4kMixin(DevopsBuild): def __init__(self, project, config): - self.build = C4kBuild(project, config)(project, config) + self.build = C4kBuild(project, config) self.c4k_build_service = C4kBuildService() def write_c4k_config(self): - self.build_service.write_c4k_config(self.build) + self.c4k_build_service.write_c4k_config(self.build) def write_c4k_auth(self): - self.build_service.write_c4k_auth(self.build) + self.c4k_build_service.write_c4k_auth(self.build) def c4k_apply(self, dry_run=False): - self.build_service.c4k_apply(self.build, dry_run) + self.c4k_build_service.c4k_apply(self.build, dry_run) diff --git a/src/test/python/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py index eb16d53..9442de3 100644 --- a/src/test/python/test_c4k_mixin.py +++ b/src/test/python/test_c4k_mixin.py @@ -24,10 +24,9 @@ def test_c4k_mixin(tmp_path): config = {'a': 1, 'b': 2} auth = {'c': 3, 'd': 4} - add_c4k_mixin_config(project_config, module_name, config, auth, grafana_cloud_user='user', grafana_cloud_password='password') + add_c4k_mixin_config(project_config, config, auth, grafana_cloud_user='user', grafana_cloud_password='password') assert project_config.get('C4kMixin') is not None - assert project_config.get('C4kMixin').get('Name') is module_name assert project_config.get('C4kMixin').get('Config') is config assert project_config.get('C4kMixin').get('Auth') is auth From 9dbc5f8ab0afc2f36ff5cd8ab7bf8d204c21cf04 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 5 Mar 2023 13:59:40 +0100 Subject: [PATCH 134/243] add doc --- doc/architecture.md | 12 +++++++ src/main/python/ddadevops/application.py | 42 +++++++++++++++--------- 2 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 doc/architecture.md diff --git a/doc/architecture.md b/doc/architecture.md new file mode 100644 index 0000000..577a2c7 --- /dev/null +++ b/doc/architecture.md @@ -0,0 +1,12 @@ +```mermaid +classDiagram + DevopsBuild <|-- DevopsDockerBuild + DevopsBuild <|-- DevopsTerraformBuild + + DevopsBuild <|-- C4kMixin + + DevopsBuild *-- BuildService + DevopsDockerBuild *-- BuildService + DevopsDockerBuild *-- DockerBuildService + C4kMixin *-- C4kBuildService +``` \ No newline at end of file diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 83ea4ae..24bfbcc 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -2,14 +2,15 @@ from .domain import Build, DockerBuild, C4kBuild from .infrastructure import FileApi, ResourceApi, DockerApi -class BuildService(): +class BuildService: def __init__(self): self.file_api = FileApi() def initialize_build_dir(self, build: Build): self.file_api.clean_dir(build.build_path()) -class DockerBuildService(): + +class DockerBuildService: def __init__(self): self.build_service = BuildService() self.file_api = FileApi() @@ -17,25 +18,28 @@ class DockerBuildService(): self.docker_api = DockerApi() def __copy_build_resource_file_from_package__(self, build: DockerBuild): - data = self.resource_api.read_resource("src/main/resources/docker/" + build.name) - self.file_api.write_to_file(build.build_path() + '/' + build.name, data) + data = self.resource_api.read_resource( + "src/main/resources/docker/" + build.name + ) + self.file_api.write_to_file(build.build_path() + "/" + build.name, data) def __copy_build_resources_from_package__(self, build: DockerBuild): self.__copy_build_resource_file_from_package__( - 'image/resources/install_functions.sh') + "image/resources/install_functions.sh" + ) def __copy_build_resources_from_dir__(self, build: DockerBuild): self.file_api.cp_force(build.docker_build_commons_path(), build.build_path()) def initialize_build_dir(self, build: DockerBuild): self.build_service.initialize_build_dir(build) - self.file_api.clean_dir(build.build_path() + '/image/resources') + self.file_api.clean_dir(build.build_path() + "/image/resources") if build.use_package_common_files: self.__copy_build_resources_from_package__(build) else: self.__copy_build_resources_from_dir__(build) - self.file_api.cp_recursive('image', build.build_path()) - self.file_api.cp_recursive('test', build.build_path()) + self.file_api.cp_recursive("image", build.build_path()) + self.file_api.cp_recursive("test", build.build_path()) def image(self, build: DockerBuild): self.docker_api.image(build.name(), build.build_path()) @@ -47,33 +51,39 @@ class DockerBuildService(): self.docker_api.dockerhub_login(build.dockerhub_user, build.dockerhub_password) def dockerhub_publish(self, build: DockerBuild): - self.docker_api.dockerhub_publish(build.name(), build.dockerhub_user, build.docker_publish_tag) + self.docker_api.dockerhub_publish( + build.name(), build.dockerhub_user, build.docker_publish_tag + ) def test(self, build: DockerBuild): self.docker_api.test(build.name(), build.build_path()) -class C4kBuildService(): +# TODO: move infrastructure fktns to infra apis +class C4kBuildService: def __init__(self): self.file_api = FileApi() def write_c4k_config(self, c4k_build: C4kBuild): - with open(c4k_build.build.build_path() + '/out_c4k_config.yaml', 'w', encoding="utf-8") as output_file: + with open( + c4k_build.build.build_path() + "/out_c4k_config.yaml", "w", encoding="utf-8" + ) as output_file: yaml.dump(c4k_build.c4k_mixin_config(), output_file) def write_c4k_auth(self, c4k_build: C4kBuild): - with open(c4k_build.build.build_path() + '/out_c4k_auth.yaml', 'w', encoding="utf-8") as output_file: + with open( + c4k_build.build.build_path() + "/out_c4k_auth.yaml", "w", encoding="utf-8" + ) as output_file: yaml.dump(c4k_build.c4k_mixin_auth, output_file) - chmod(c4k_build.build.build_path() + '/out_c4k_auth.yaml', 0o600) + chmod(c4k_build.build.build_path() + "/out_c4k_auth.yaml", 0o600) def c4k_apply(self, c4k_build: C4kBuild, ry_run=False): module = c4k_build.build.module build_path = c4k_build.build.build_path() - cmd = f'c4k-{module}-standalone.jar {build_path}/out_c4k_config.yaml {build_path}/out_c4k_auth.yaml > {build_path}/out_{module}.yaml' - output = '' + cmd = f"c4k-{module}-standalone.jar {build_path}/out_c4k_config.yaml {build_path}/out_c4k_auth.yaml > {build_path}/out_{module}.yaml" + output = "" if dry_run: print(cmd) else: output = execute(cmd, True) print(output) return output - From dfddd5ee33d2bf5405fcca5e972efb0ad7fdaf4b Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 5 Mar 2023 14:17:36 +0100 Subject: [PATCH 135/243] refactor: only builds & mixins should inherit test are not working fully atmo --- doc/architecture.md | 1 - src/main/python/ddadevops/application.py | 10 +++++++--- src/main/python/ddadevops/c4k_mixin.py | 9 +++++---- src/main/python/ddadevops/devops_docker_build.py | 7 +++++-- src/test/python/test_c4k_mixin.py | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/doc/architecture.md b/doc/architecture.md index 577a2c7..c44c96e 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -6,7 +6,6 @@ classDiagram DevopsBuild <|-- C4kMixin DevopsBuild *-- BuildService - DevopsDockerBuild *-- BuildService DevopsDockerBuild *-- DockerBuildService C4kMixin *-- C4kBuildService ``` \ No newline at end of file diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 24bfbcc..c4e4a24 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,5 +1,11 @@ from .domain import Build, DockerBuild, C4kBuild from .infrastructure import FileApi, ResourceApi, DockerApi +from os import chmod +import yaml +from .python_util import execute +from .credential import gopass_field_from_path, gopass_password_from_path + + class BuildService: @@ -12,7 +18,6 @@ class BuildService: class DockerBuildService: def __init__(self): - self.build_service = BuildService() self.file_api = FileApi() self.resource_api = ResourceApi() self.docker_api = DockerApi() @@ -32,7 +37,6 @@ class DockerBuildService: self.file_api.cp_force(build.docker_build_commons_path(), build.build_path()) def initialize_build_dir(self, build: DockerBuild): - self.build_service.initialize_build_dir(build) self.file_api.clean_dir(build.build_path() + "/image/resources") if build.use_package_common_files: self.__copy_build_resources_from_package__(build) @@ -67,7 +71,7 @@ class C4kBuildService: with open( c4k_build.build.build_path() + "/out_c4k_config.yaml", "w", encoding="utf-8" ) as output_file: - yaml.dump(c4k_build.c4k_mixin_config(), output_file) + yaml.dump(c4k_build.config(), output_file) def write_c4k_auth(self, c4k_build: C4kBuild): with open( diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 93b0131..033eb36 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -29,14 +29,15 @@ def add_c4k_mixin_config(config, class C4kMixin(DevopsBuild): def __init__(self, project, config): - self.build = C4kBuild(project, config) + super().__init__(project, config) + self.c4k_build = C4kBuild(project, config) self.c4k_build_service = C4kBuildService() def write_c4k_config(self): - self.c4k_build_service.write_c4k_config(self.build) + self.c4k_build_service.write_c4k_config(self.c4k_build) def write_c4k_auth(self): - self.c4k_build_service.write_c4k_auth(self.build) + self.c4k_build_service.write_c4k_auth(self.c4k_build) def c4k_apply(self, dry_run=False): - self.c4k_build_service.c4k_apply(self.build, dry_run) + self.c4k_build_service.c4k_apply(self.c4k_build, dry_run) diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index 8478129..538edff 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -26,12 +26,15 @@ def create_devops_docker_build_config(stage, class DevopsDockerBuild(DevopsBuild): def __init__(self, project, config): - self.build = DockerBuild(project, config) + super().__init__(project, config) + self.docker_build = DockerBuild(project, config) self.docker_build_service = DockerBuildService() def initialize_build_dir(self): - self.docker_build_service.initialize_build_dir(self.build) + super().initialize_build_dir() + self.docker_build_service.initialize_build_dir(self.docker_build) + # TODO: use application from here on def image(self): run('docker build -t ' + self.name() + ' --file ' + self.build_path() + '/image/Dockerfile ' diff --git a/src/test/python/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py index 9442de3..646d751 100644 --- a/src/test/python/test_c4k_mixin.py +++ b/src/test/python/test_c4k_mixin.py @@ -34,7 +34,7 @@ def test_c4k_mixin(tmp_path): mixin.initialize_build_dir() assert mixin.build_path() == f'{tmp_path_str}/{build_dir}/{project_name}/{module_name}' - mixin.put('fqdn', 'testing.test') + mixin.build.update_runtime_config('test.de', None, None) mixin.write_c4k_config() assert 'fqdn' in mixin.c4k_mixin_config From 2c2f52837169692ceb4f482139235bf1fdefe390 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Mar 2023 13:13:47 +0100 Subject: [PATCH 136/243] Condense copy logic into helper script --- .../python/ddadevops/release-mixin/test/test_helper.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/python/ddadevops/release-mixin/test/test_helper.py diff --git a/src/main/python/ddadevops/release-mixin/test/test_helper.py b/src/main/python/ddadevops/release-mixin/test/test_helper.py new file mode 100644 index 0000000..cd4bce4 --- /dev/null +++ b/src/main/python/ddadevops/release-mixin/test/test_helper.py @@ -0,0 +1,10 @@ +from pathlib import Path +from infrastructure_api import SystemAPI + +TEST_FILE_NAME = 'config.json' +TEST_FILE_ROOT = Path('test/resources/') +TEST_FILE_PATH = TEST_FILE_ROOT / TEST_FILE_NAME + +def copy_files(source: Path, target: Path): + api = SystemAPI() + api.run_checked('cp', source, target) \ No newline at end of file From 27f2bf2d2ed3fe6be6f8295435eef7d0c9d96cf5 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Mar 2023 13:15:35 +0100 Subject: [PATCH 137/243] Move api tests to api test file Some formatting and missing imports --- .../test/mock_infrastructure_api.py | 9 +- .../test/test_infrastructure_api.py | 84 ++++++++++++++++++- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py index 2e8ac63..f4abed2 100644 --- a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py @@ -13,9 +13,11 @@ class MockSystemAPI(): class MockGitApi(): - def __init__(self): + def __init__(self, commit_string = ""): self.system_api = MockSystemAPI() - self.tag_annotated_count = 0 + self.get_latest_commit_count = 0 + self.commit_string = commit_string + self.tag_annotated_count = 0 self.add_file_count = 0 self.commit_count = 0 self.push_count = 0 @@ -24,7 +26,8 @@ class MockGitApi(): return " " def get_latest_commit(self): - return " " + self.get_latest_commit_count += 1 + return self.commit_string def tag_annotated(self, annotation: str, message: str, count: int): self.tag_annotated_count += 1 diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py index 993c888..fcaad52 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py @@ -18,7 +18,9 @@ sys.path.append(parent) # now we can import the module in the parent # directory. -from infrastructure_api import GitApi +from infrastructure_api import GitApi +from infrastructure import VersionRepository +from domain import ReleaseType def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): monkeypatch.chdir(tmp_path) @@ -30,7 +32,9 @@ def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): contents = json_file.read() f = tmp_path / file_name f.write_text(contents) - change_test_dir(tmp_path, monkeypatch) # change the context of the script execution to tmp_path + + # change the context of the script execution to tmp_path + change_test_dir(tmp_path, monkeypatch) git_api = GitApi() git_api.init() @@ -40,3 +44,79 @@ def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # test latest_commit = git_api.get_latest_commit() assert "MINOR release" in latest_commit + +# file handler tests +def test_gradle(tmp_path): + # init + file_name = 'config.gradle' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + repo = VersionRepository(f) + version = repo.get_version() + version = version.create_release_version(ReleaseType.SNAPSHOT) + repo.write_file(version.get_version_string()) + + # check + assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() + + +def test_json(tmp_path): + # init + file_name = 'config.json' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + repo = VersionRepository(f) + version = repo.get_version() + version = version.create_release_version(ReleaseType.SNAPSHOT) + repo.write_file(version.get_version_string()) + + # check + assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() + + +def test_clojure(tmp_path): + # init + file_name = 'config.clj' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + repo = VersionRepository(f) + version = repo.get_version() + version = version.create_release_version(ReleaseType.SNAPSHOT) + repo.write_file(version.get_version_string()) + + # check + assert '1.1.3-SNAPSHOT' in f.read_text() + + +def test_python(tmp_path): + # init + file_name = 'config.py' + with open(f'test/resources/{file_name}', 'r') as gradle_file: + contents = gradle_file.read() + + f = tmp_path / file_name + f.write_text(contents) + + # test + repo = VersionRepository(f) + version = repo.get_version() + version = version.create_release_version(ReleaseType.SNAPSHOT) + repo.write_file(version.get_version_string()) + + # check + assert '3.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file From f49b44f5e0b960159fa6a2d06d35cf35b04ca97e Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Mar 2023 13:17:20 +0100 Subject: [PATCH 138/243] Add test for version repo using helper scripts --- .../release-mixin/test/test_infrastructure.py | 116 +++--------------- 1 file changed, 20 insertions(+), 96 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py index 3aa05c0..72b8222 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py @@ -1,6 +1,6 @@ -from pathlib import Path import sys import os +import test_helper as th # getting the name of the directory # where the this file is present. @@ -17,131 +17,55 @@ sys.path.append(parent) # now we can import the module in the parent # directory. -from domain import ReleaseType, Release +from domain import ReleaseType from infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository -from infrastructure_api import GitApi - -class MyGitApi(GitApi): - def __init__(self, commit_string): - self.commit_string = commit_string - - def get_latest_commit(self): - return self.commit_string +from mock_infrastructure_api import MockGitApi -def test_gradle(tmp_path): +def test_version_repository(tmp_path): # init - file_name = 'config.gradle' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() + th.copy_files(th.TEST_FILE_PATH, tmp_path) + sut = VersionRepository(th.TEST_FILE_PATH) + version = sut.get_version() - f = tmp_path / file_name - f.write_text(contents) - - # test - repo = VersionRepository(f) - version = repo.get_version() - version = version.create_release_version(ReleaseType.SNAPSHOT) - repo.write_file(version.get_version_string()) - - # check - assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() - - -def test_json(tmp_path): - # init - file_name = 'config.json' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) - - # test - repo = VersionRepository(f) - version = repo.get_version() - version = version.create_release_version(ReleaseType.SNAPSHOT) - repo.write_file(version.get_version_string()) - - # check - assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() - - -def test_clojure(tmp_path): - # init - file_name = 'config.clj' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) - - # test - repo = VersionRepository(f) - version = repo.get_version() - version = version.create_release_version(ReleaseType.SNAPSHOT) - repo.write_file(version.get_version_string()) - - # check - assert '1.1.3-SNAPSHOT' in f.read_text() - - -def test_python(tmp_path): - # init - file_name = 'config.py' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) - - # test - repo = VersionRepository(f) - version = repo.get_version() - version = version.create_release_version(ReleaseType.SNAPSHOT) - repo.write_file(version.get_version_string()) - - # check - assert '3.1.3-SNAPSHOT' in f.read_text() + #test + assert version is not None def test_release_repository(tmp_path): - # init - file_name = 'config.json' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) + # init + th.copy_files( th.TEST_FILE_PATH, tmp_path) + version_repo = VersionRepository(th.TEST_FILE_PATH) + release_type_repo = ReleaseTypeRepository(MockGitApi('MINOR test')) # test - sut = ReleaseRepository(VersionRepository(f), ReleaseTypeRepository(MyGitApi('MINOR test')), 'main') + sut = ReleaseRepository(version_repo, release_type_repo, 'main') release = sut.get_release() assert release is not None def test_release_type_repository(): - sut = ReleaseTypeRepository(MyGitApi('MINOR test')) + sut = ReleaseTypeRepository(MockGitApi('MINOR test')) release_type = sut.get_release_type() assert release_type is ReleaseType.MINOR - sut = ReleaseTypeRepository(MyGitApi('MINOR bla')) + sut = ReleaseTypeRepository(MockGitApi('MINOR bla')) release_type = sut.get_release_type() assert release_type is ReleaseType.MINOR - sut = ReleaseTypeRepository(MyGitApi('Major bla')) + sut = ReleaseTypeRepository(MockGitApi('Major bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.MAJOR - sut = ReleaseTypeRepository(MyGitApi('PATCH bla')) + sut = ReleaseTypeRepository(MockGitApi('PATCH bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.PATCH - sut = ReleaseTypeRepository(MyGitApi('SNAPSHOT bla')) + sut = ReleaseTypeRepository(MockGitApi('SNAPSHOT bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.SNAPSHOT - sut = ReleaseTypeRepository(MyGitApi('bla')) + sut = ReleaseTypeRepository(MockGitApi('bla')) release_type = sut.get_release_type() assert release_type == None From e4996dfb2096cca458268bee2dc8269563ea90e5 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Mar 2023 13:25:25 +0100 Subject: [PATCH 139/243] Turn test helper into class and rename --- .../python/ddadevops/release-mixin/test/helper.py | 13 +++++++++++++ .../ddadevops/release-mixin/test/test_helper.py | 10 ---------- 2 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 src/main/python/ddadevops/release-mixin/test/helper.py delete mode 100644 src/main/python/ddadevops/release-mixin/test/test_helper.py diff --git a/src/main/python/ddadevops/release-mixin/test/helper.py b/src/main/python/ddadevops/release-mixin/test/helper.py new file mode 100644 index 0000000..7b8ccfb --- /dev/null +++ b/src/main/python/ddadevops/release-mixin/test/helper.py @@ -0,0 +1,13 @@ +from pathlib import Path +from infrastructure_api import SystemAPI + + +class Helper(): + def __init__(self, file_name = 'config.json'): + self.TEST_FILE_NAME = file_name + self.TEST_FILE_ROOT = Path('test/resources/') + self.TEST_FILE_PATH = self.TEST_FILE_ROOT / self.TEST_FILE_NAME + + def copy_files(self, source: Path, target: Path): + api = SystemAPI() + api.run_checked('cp', source, target) \ No newline at end of file diff --git a/src/main/python/ddadevops/release-mixin/test/test_helper.py b/src/main/python/ddadevops/release-mixin/test/test_helper.py deleted file mode 100644 index cd4bce4..0000000 --- a/src/main/python/ddadevops/release-mixin/test/test_helper.py +++ /dev/null @@ -1,10 +0,0 @@ -from pathlib import Path -from infrastructure_api import SystemAPI - -TEST_FILE_NAME = 'config.json' -TEST_FILE_ROOT = Path('test/resources/') -TEST_FILE_PATH = TEST_FILE_ROOT / TEST_FILE_NAME - -def copy_files(source: Path, target: Path): - api = SystemAPI() - api.run_checked('cp', source, target) \ No newline at end of file From 21bafab88269007e5135b861fd610d28438d21b5 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Mar 2023 13:44:50 +0100 Subject: [PATCH 140/243] Adapt tests for helper class --- .../release-mixin/test/test_infrastructure.py | 4 +- .../test/test_infrastructure_api.py | 62 +++++++------------ 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py index 72b8222..7e5f701 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py @@ -1,6 +1,6 @@ import sys import os -import test_helper as th +from helper import Helper # getting the name of the directory # where the this file is present. @@ -24,6 +24,7 @@ from mock_infrastructure_api import MockGitApi def test_version_repository(tmp_path): # init + th = Helper() th.copy_files(th.TEST_FILE_PATH, tmp_path) sut = VersionRepository(th.TEST_FILE_PATH) version = sut.get_version() @@ -34,6 +35,7 @@ def test_version_repository(tmp_path): def test_release_repository(tmp_path): # init + th = Helper() th.copy_files( th.TEST_FILE_PATH, tmp_path) version_repo = VersionRepository(th.TEST_FILE_PATH) release_type_repo = ReleaseTypeRepository(MockGitApi('MINOR test')) diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py index fcaad52..e857721 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py @@ -1,5 +1,6 @@ from pathlib import Path import pytest as pt +from helper import Helper import sys import os @@ -27,18 +28,15 @@ def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # init - file_name = 'config.json' - with open(f'test/resources/{file_name}', 'r') as json_file: - contents = json_file.read() - f = tmp_path / file_name - f.write_text(contents) + th = Helper() + th.copy_files(th.TEST_FILE_PATH, tmp_path) # change the context of the script execution to tmp_path change_test_dir(tmp_path, monkeypatch) git_api = GitApi() git_api.init() - git_api.add_file(file_name) + git_api.add_file(th.TEST_FILE_NAME) git_api.commit("MINOR release") # test @@ -48,75 +46,63 @@ def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # file handler tests def test_gradle(tmp_path): # init - file_name = 'config.gradle' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) + th = Helper('config.gradle') + th.copy_files(th.TEST_FILE_PATH, tmp_path) + th.TEST_FILE_PATH = tmp_path / th.TEST_FILE_NAME # test - repo = VersionRepository(f) + repo = VersionRepository(th.TEST_FILE_PATH) version = repo.get_version() version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) # check - assert 'version = "12.4.678-SNAPSHOT"' in f.read_text() + assert 'version = "12.4.678-SNAPSHOT"' in th.TEST_FILE_PATH.read_text() def test_json(tmp_path): # init - file_name = 'config.json' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) + th = Helper('config.json') + th.copy_files(th.TEST_FILE_PATH, tmp_path) + th.TEST_FILE_PATH = tmp_path / th.TEST_FILE_NAME # test - repo = VersionRepository(f) + repo = VersionRepository(th.TEST_FILE_PATH) version = repo.get_version() version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) # check - assert '"version": "123.123.456-SNAPSHOT"' in f.read_text() + assert '"version": "123.123.456-SNAPSHOT"' in th.TEST_FILE_PATH.read_text() def test_clojure(tmp_path): # init - file_name = 'config.clj' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) + th = Helper('config.clj') + th.copy_files(th.TEST_FILE_PATH, tmp_path) + th.TEST_FILE_PATH = tmp_path / th.TEST_FILE_NAME # test - repo = VersionRepository(f) + repo = VersionRepository(th.TEST_FILE_PATH) version = repo.get_version() version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) # check - assert '1.1.3-SNAPSHOT' in f.read_text() + assert '1.1.3-SNAPSHOT' in th.TEST_FILE_PATH.read_text() def test_python(tmp_path): # init - file_name = 'config.py' - with open(f'test/resources/{file_name}', 'r') as gradle_file: - contents = gradle_file.read() - - f = tmp_path / file_name - f.write_text(contents) + th = Helper('config.py') + th.copy_files(th.TEST_FILE_PATH, tmp_path) + th.TEST_FILE_PATH = tmp_path / th.TEST_FILE_NAME # test - repo = VersionRepository(f) + repo = VersionRepository(th.TEST_FILE_PATH) version = repo.get_version() version = version.create_release_version(ReleaseType.SNAPSHOT) repo.write_file(version.get_version_string()) # check - assert '3.1.3-SNAPSHOT' in f.read_text() \ No newline at end of file + assert '3.1.3-SNAPSHOT' in th.TEST_FILE_PATH.read_text() \ No newline at end of file From 4098d00409f34b6eff49b9e092122b859050da46 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Mar 2023 13:48:59 +0100 Subject: [PATCH 141/243] Cleanup redundant comments --- .../ddadevops/release-mixin/test/test_domain.py | 11 ----------- .../release-mixin/test/test_infrastructure.py | 11 ----------- .../release-mixin/test/test_infrastructure_api.py | 11 ----------- .../release-mixin/test/test_release_mixin.py | 11 ----------- 4 files changed, 44 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/test/test_domain.py b/src/main/python/ddadevops/release-mixin/test/test_domain.py index abef6d1..4413856 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_domain.py +++ b/src/main/python/ddadevops/release-mixin/test/test_domain.py @@ -2,21 +2,10 @@ from pathlib import Path import sys import os -# getting the name of the directory -# where the this file is present. current = os.path.dirname(os.path.realpath(__file__)) - -# Getting the parent directory name -# where the current directory is present. parent = os.path.dirname(current) - -# adding the parent directory to -# the sys.path. sys.path.append(parent) -# now we can import the module in the parent -# directory. - from domain import Version, ReleaseType, Release def test_version(tmp_path: Path): diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py index 7e5f701..3b4c8fc 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py @@ -2,21 +2,10 @@ import sys import os from helper import Helper -# getting the name of the directory -# where the this file is present. current = os.path.dirname(os.path.realpath(__file__)) - -# Getting the parent directory name -# where the current directory is present. parent = os.path.dirname(current) - -# adding the parent directory to -# the sys.path. sys.path.append(parent) -# now we can import the module in the parent -# directory. - from domain import ReleaseType from infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository from mock_infrastructure_api import MockGitApi diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py index e857721..5782159 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py @@ -4,21 +4,10 @@ from helper import Helper import sys import os -# getting the name of the directory -# where the this file is present. current = os.path.dirname(os.path.realpath(__file__)) - -# Getting the parent directory name -# where the current directory is present. parent = os.path.dirname(current) - -# adding the parent directory to -# the sys.path. sys.path.append(parent) -# now we can import the module in the parent -# directory. - from infrastructure_api import GitApi from infrastructure import VersionRepository from domain import ReleaseType diff --git a/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py b/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py index e85360b..0c6a5f0 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py +++ b/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py @@ -4,20 +4,9 @@ import pytest as pt from pathlib import Path from ddadevops import * -# getting the name of the directory -# where the this file is present. current = os.path.dirname(os.path.realpath(__file__)) - -# Getting the parent directory name -# where the current directory is present. parent = os.path.dirname(current) - -# adding the parent directory to -# the sys.path. sys.path.append(parent) - -# now we can import the module in the parent -# directory. from pybuilder.core import task, init, Project from release_mixin import ReleaseMixin, create_release_mixin_config From 5d2bbb758c2510fc4cdd91f4defd931913af844d Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Mar 2023 14:26:30 +0100 Subject: [PATCH 142/243] Enable release mixin test --- .../release-mixin/test/test_release_mixin.py | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py b/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py index 0c6a5f0..eaca9a2 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py +++ b/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py @@ -1,15 +1,17 @@ import sys import os import pytest as pt +from helper import Helper from pathlib import Path from ddadevops import * +from pybuilder.core import Project current = os.path.dirname(os.path.realpath(__file__)) parent = os.path.dirname(current) sys.path.append(parent) -from pybuilder.core import task, init, Project from release_mixin import ReleaseMixin, create_release_mixin_config +from infrastructure_api import GitApi MAIN_BRANCH = 'main' STAGE = 'test' @@ -17,6 +19,9 @@ PROJECT_ROOT_PATH = '.' MODULE = 'test' BUILD_DIR_NAME = "build_dir" +def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): + monkeypatch.chdir(tmp_path) + class MyBuild(ReleaseMixin): pass @@ -30,29 +35,24 @@ def initialize(project, CONFIG_FILE): build = MyBuild(project, config) return build -#def test_release_mixin(tmp_path): -# -# #init -# with open(f'test/resources/config.json', 'r') as json_file: -# contents = json_file.read() -# -# CONFIG_FILE = tmp_path / "config.json" -# CONFIG_FILE.write_text(contents) -# -# base_dir = "." -# project = Project(base_dir) -# -# # init -# build = initialize(project, CONFIG_FILE) -# build.commit_string = "MAJOR bla" -# build.init_release() -# release_version = build.release_version -# -# # test -# assert "124.0.0" in release_version.get_version_string() -# -# # init -# bump_version = build.bump_version -# -# # test -# assert "124.0.1-SNAPSHOT" in bump_version.get_version_string() \ No newline at end of file +def test_release_mixin(tmp_path: Path, monkeypatch: pt.MonkeyPatch): + + # init + th = Helper() + th.copy_files(th.TEST_FILE_PATH, tmp_path) + th.TEST_FILE_PATH = tmp_path / th.TEST_FILE_NAME + + change_test_dir(tmp_path, monkeypatch) + project = Project(tmp_path) + + git_api = GitApi() + git_api.init() + git_api.add_file(th.TEST_FILE_NAME) + git_api.commit("MAJOR release") + + build = initialize(project, th.TEST_FILE_PATH) + build.prepare_release() + release_version = build.version_repo.get_version() + + # test + assert "124.0.1-SNAPSHOT" in release_version.get_version_string() From a94a7ea62bbbf36ebfc1f15a6bb770d5cdf2db15 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 9 Mar 2023 14:27:36 +0100 Subject: [PATCH 143/243] Remove unused imports and order imports --- .../python/ddadevops/release-mixin/infrastructure_api.py | 4 ++-- src/main/python/ddadevops/release-mixin/release_mixin.py | 3 --- src/main/python/ddadevops/release-mixin/test/test_domain.py | 2 +- .../ddadevops/release-mixin/test/test_infrastructure_api.py | 6 +++--- .../python/ddadevops/release-mixin/test/test_services.py | 2 -- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main/python/ddadevops/release-mixin/infrastructure_api.py b/src/main/python/ddadevops/release-mixin/infrastructure_api.py index bd86de5..dcb5aa2 100644 --- a/src/main/python/ddadevops/release-mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/infrastructure_api.py @@ -1,8 +1,8 @@ -from abc import ABC, abstractmethod -from pathlib import Path import json import re import subprocess as sub +from abc import ABC, abstractmethod +from pathlib import Path class FileHandler(ABC): diff --git a/src/main/python/ddadevops/release-mixin/release_mixin.py b/src/main/python/ddadevops/release-mixin/release_mixin.py index 03f3c99..fa76f65 100644 --- a/src/main/python/ddadevops/release-mixin/release_mixin.py +++ b/src/main/python/ddadevops/release-mixin/release_mixin.py @@ -1,7 +1,4 @@ -import copy from ddadevops import DevopsBuild -from ddadevops import execute -from ddadevops import gopass_field_from_path, gopass_password_from_path from infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository from infrastructure_api import GitApi from services import PrepareReleaseService, TagAndPushReleaseService diff --git a/src/main/python/ddadevops/release-mixin/test/test_domain.py b/src/main/python/ddadevops/release-mixin/test/test_domain.py index 4413856..d93b78d 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_domain.py +++ b/src/main/python/ddadevops/release-mixin/test/test_domain.py @@ -1,6 +1,6 @@ -from pathlib import Path import sys import os +from pathlib import Path current = os.path.dirname(os.path.realpath(__file__)) parent = os.path.dirname(current) diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py index 5782159..8c603f7 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py +++ b/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py @@ -1,8 +1,8 @@ -from pathlib import Path -import pytest as pt -from helper import Helper import sys import os +import pytest as pt +from pathlib import Path +from helper import Helper current = os.path.dirname(os.path.realpath(__file__)) parent = os.path.dirname(current) diff --git a/src/main/python/ddadevops/release-mixin/test/test_services.py b/src/main/python/ddadevops/release-mixin/test/test_services.py index 142e8bc..a24341c 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_services.py +++ b/src/main/python/ddadevops/release-mixin/test/test_services.py @@ -1,5 +1,3 @@ -import pytest as pt -from pathlib import Path import sys import os From f7668231be6c5ea0eb09c97e2df471658144282d Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Thu, 9 Mar 2023 20:24:11 +0100 Subject: [PATCH 144/243] fix tests --- src/main/python/ddadevops/c4k_mixin.py | 2 +- src/main/python/ddadevops/domain.py | 6 +++--- src/test/python/test_c4k_mixin.py | 6 +++--- src/test/python/test_domain.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 033eb36..45fd185 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -30,7 +30,7 @@ def add_c4k_mixin_config(config, class C4kMixin(DevopsBuild): def __init__(self, project, config): super().__init__(project, config) - self.c4k_build = C4kBuild(project, config) + self.c4k_build = C4kBuild(self.build, project, config) self.c4k_build_service = C4kBuildService() def write_c4k_config(self): diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 684c999..3a5f860 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -48,7 +48,7 @@ class Build(Validateable): def __get_keys__(self, keys): result = {} for key in keys: - result[key] = self.get(key) + result[key] = self.__get__(key) return result @@ -69,8 +69,8 @@ class DockerBuild(Validateable): class C4kBuild(Validateable): - def __init__(self, project, config): - self.build = Build(project, config) + def __init__(self, build: Build, project, config): + self.build = build self.c4k_mixin_config = config["C4kMixin"]["Config"] self.c4k_mixin_auth = config["C4kMixin"]["Auth"] tmp = self.c4k_mixin_config["mon-cfg"] diff --git a/src/test/python/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py index 646d751..bd8d60f 100644 --- a/src/test/python/test_c4k_mixin.py +++ b/src/test/python/test_c4k_mixin.py @@ -37,11 +37,11 @@ def test_c4k_mixin(tmp_path): mixin.build.update_runtime_config('test.de', None, None) mixin.write_c4k_config() - assert 'fqdn' in mixin.c4k_mixin_config - assert 'mon-cfg' in mixin.c4k_mixin_config + assert 'fqdn' in mixin.c4k_build.config() + assert 'mon-cfg' in mixin.c4k_build.config() assert os.path.exists(f'{mixin.build_path()}/out_c4k_config.yaml') mixin.write_c4k_auth() - assert 'mon-auth' in mixin.c4k_mixin_auth + assert 'mon-auth' in mixin.c4k_build.c4k_mixin_auth assert os.path.exists(f'{mixin.build_path()}/out_c4k_auth.yaml') \ No newline at end of file diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index 371b69c..dbbe8d2 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -1,5 +1,5 @@ from pybuilder.core import Project -from src.main.python.ddadevops.domain import Validateable, C4kBuild +from src.main.python.ddadevops.domain import Validateable, C4kBuild, Build from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config @@ -62,7 +62,7 @@ def test_c4k_build_should_update_fqdn(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - sut = C4kBuild(project, project_config) + sut = C4kBuild(Build(project, project_config), project, project_config) sut.update_runtime_config('test.de', None, None) assert { From 408c25028ba600992e94b53e19388d9221d4b4d6 Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 10 Mar 2023 13:37:45 +0100 Subject: [PATCH 145/243] Move tests from release_mixin to project test folder --- src/test/python/release_mixin/__init__.py | 0 .../python/release_mixin}/helper.py | 4 ++-- .../python/release_mixin}/mock_domain.py | 9 +-------- .../release_mixin}/mock_infrastructure.py | 9 +-------- .../release_mixin}/mock_infrastructure_api.py | 0 .../python/release_mixin}/test_domain.py | 9 +-------- .../release_mixin}/test_infrastructure.py | 14 +++---------- .../release_mixin}/test_infrastructure_api.py | 14 ++++--------- .../release_mixin}/test_release_mixin.py | 10 ++-------- .../python/release_mixin}/test_services.py | 20 +------------------ .../test/resources/config.clj | 0 .../test/resources/config.gradle | 0 .../test/resources/config.json | 0 .../test/resources/config.py | 0 14 files changed, 15 insertions(+), 74 deletions(-) create mode 100644 src/test/python/release_mixin/__init__.py rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/helper.py (70%) rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/mock_domain.py (86%) rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/mock_infrastructure.py (88%) rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/mock_infrastructure_api.py (100%) rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/test_domain.py (89%) rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/test_infrastructure.py (86%) rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/test_infrastructure_api.py (91%) rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/test_release_mixin.py (85%) rename src/{main/python/ddadevops/release-mixin/test => test/python/release_mixin}/test_services.py (80%) rename src/{main/python/ddadevops/release-mixin => }/test/resources/config.clj (100%) rename src/{main/python/ddadevops/release-mixin => }/test/resources/config.gradle (100%) rename src/{main/python/ddadevops/release-mixin => }/test/resources/config.json (100%) rename src/{main/python/ddadevops/release-mixin => }/test/resources/config.py (100%) diff --git a/src/test/python/release_mixin/__init__.py b/src/test/python/release_mixin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/python/ddadevops/release-mixin/test/helper.py b/src/test/python/release_mixin/helper.py similarity index 70% rename from src/main/python/ddadevops/release-mixin/test/helper.py rename to src/test/python/release_mixin/helper.py index 7b8ccfb..1c22f30 100644 --- a/src/main/python/ddadevops/release-mixin/test/helper.py +++ b/src/test/python/release_mixin/helper.py @@ -1,11 +1,11 @@ from pathlib import Path -from infrastructure_api import SystemAPI +from src.main.python.ddadevops.release_mixin.infrastructure_api import SystemAPI class Helper(): def __init__(self, file_name = 'config.json'): self.TEST_FILE_NAME = file_name - self.TEST_FILE_ROOT = Path('test/resources/') + self.TEST_FILE_ROOT = Path('src/test/resources/') self.TEST_FILE_PATH = self.TEST_FILE_ROOT / self.TEST_FILE_NAME def copy_files(self, source: Path, target: Path): diff --git a/src/main/python/ddadevops/release-mixin/test/mock_domain.py b/src/test/python/release_mixin/mock_domain.py similarity index 86% rename from src/main/python/ddadevops/release-mixin/test/mock_domain.py rename to src/test/python/release_mixin/mock_domain.py index b5b4b65..14e625c 100644 --- a/src/main/python/ddadevops/release-mixin/test/mock_domain.py +++ b/src/test/python/release_mixin/mock_domain.py @@ -1,11 +1,4 @@ -import os -import sys - -current = os.path.dirname(os.path.realpath(__file__)) -parent = os.path.dirname(current) -sys.path.append(parent) - -from domain import ReleaseType +from src.main.python.ddadevops.release_mixin.domain import ReleaseType class MockVersion(): diff --git a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py b/src/test/python/release_mixin/mock_infrastructure.py similarity index 88% rename from src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py rename to src/test/python/release_mixin/mock_infrastructure.py index 6e66498..8750308 100644 --- a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure.py +++ b/src/test/python/release_mixin/mock_infrastructure.py @@ -1,13 +1,6 @@ -import os -import sys from mock_domain import MockRelease, MockVersion from mock_infrastructure_api import MockGitApi - -current = os.path.dirname(os.path.realpath(__file__)) -parent = os.path.dirname(current) -sys.path.append(parent) - -from domain import ReleaseType +from src.main.python.ddadevops.release_mixin.domain import ReleaseType class MockVersionRepository(): diff --git a/src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py b/src/test/python/release_mixin/mock_infrastructure_api.py similarity index 100% rename from src/main/python/ddadevops/release-mixin/test/mock_infrastructure_api.py rename to src/test/python/release_mixin/mock_infrastructure_api.py diff --git a/src/main/python/ddadevops/release-mixin/test/test_domain.py b/src/test/python/release_mixin/test_domain.py similarity index 89% rename from src/main/python/ddadevops/release-mixin/test/test_domain.py rename to src/test/python/release_mixin/test_domain.py index d93b78d..a76ab9c 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_domain.py +++ b/src/test/python/release_mixin/test_domain.py @@ -1,12 +1,5 @@ -import sys -import os from pathlib import Path - -current = os.path.dirname(os.path.realpath(__file__)) -parent = os.path.dirname(current) -sys.path.append(parent) - -from domain import Version, ReleaseType, Release +from src.main.python.ddadevops.release_mixin.domain import Version, ReleaseType, Release def test_version(tmp_path: Path): version = Version(tmp_path, [1, 2, 3]) diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py b/src/test/python/release_mixin/test_infrastructure.py similarity index 86% rename from src/main/python/ddadevops/release-mixin/test/test_infrastructure.py rename to src/test/python/release_mixin/test_infrastructure.py index 3b4c8fc..f200186 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure.py +++ b/src/test/python/release_mixin/test_infrastructure.py @@ -1,15 +1,7 @@ -import sys -import os -from helper import Helper - -current = os.path.dirname(os.path.realpath(__file__)) -parent = os.path.dirname(current) -sys.path.append(parent) - -from domain import ReleaseType -from infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository +from src.main.python.ddadevops.release_mixin.domain import ReleaseType +from src.main.python.ddadevops.release_mixin.infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository from mock_infrastructure_api import MockGitApi - +from helper import Helper def test_version_repository(tmp_path): # init diff --git a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py b/src/test/python/release_mixin/test_infrastructure_api.py similarity index 91% rename from src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py rename to src/test/python/release_mixin/test_infrastructure_api.py index 8c603f7..2c2b8ae 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_infrastructure_api.py +++ b/src/test/python/release_mixin/test_infrastructure_api.py @@ -1,16 +1,10 @@ -import sys -import os -import pytest as pt from pathlib import Path from helper import Helper +import pytest as pt -current = os.path.dirname(os.path.realpath(__file__)) -parent = os.path.dirname(current) -sys.path.append(parent) - -from infrastructure_api import GitApi -from infrastructure import VersionRepository -from domain import ReleaseType +from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi +from src.main.python.ddadevops.release_mixin.infrastructure import VersionRepository +from src.main.python.ddadevops.release_mixin.domain import ReleaseType def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): monkeypatch.chdir(tmp_path) diff --git a/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py similarity index 85% rename from src/main/python/ddadevops/release-mixin/test/test_release_mixin.py rename to src/test/python/release_mixin/test_release_mixin.py index eaca9a2..ff6663e 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -1,17 +1,11 @@ -import sys -import os import pytest as pt from helper import Helper from pathlib import Path from ddadevops import * from pybuilder.core import Project -current = os.path.dirname(os.path.realpath(__file__)) -parent = os.path.dirname(current) -sys.path.append(parent) - -from release_mixin import ReleaseMixin, create_release_mixin_config -from infrastructure_api import GitApi +from src.main.python.ddadevops.release_mixin.release_mixin import ReleaseMixin, create_release_mixin_config +from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi MAIN_BRANCH = 'main' STAGE = 'test' diff --git a/src/main/python/ddadevops/release-mixin/test/test_services.py b/src/test/python/release_mixin/test_services.py similarity index 80% rename from src/main/python/ddadevops/release-mixin/test/test_services.py rename to src/test/python/release_mixin/test_services.py index a24341c..7323fd5 100644 --- a/src/main/python/ddadevops/release-mixin/test/test_services.py +++ b/src/test/python/release_mixin/test_services.py @@ -1,22 +1,4 @@ -import sys -import os - -# getting the name of the directory -# where the this file is present. -current = os.path.dirname(os.path.realpath(__file__)) - -# Getting the parent directory name -# where the current directory is present. -parent = os.path.dirname(current) - -# adding the parent directory to -# the sys.path. -sys.path.append(parent) - -# now we can import the module in the parent -# directory. - -from services import PrepareReleaseService, TagAndPushReleaseService +from src.main.python.ddadevops.release_mixin.services import PrepareReleaseService, TagAndPushReleaseService from mock_infrastructure import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository from mock_infrastructure_api import MockGitApi diff --git a/src/main/python/ddadevops/release-mixin/test/resources/config.clj b/src/test/resources/config.clj similarity index 100% rename from src/main/python/ddadevops/release-mixin/test/resources/config.clj rename to src/test/resources/config.clj diff --git a/src/main/python/ddadevops/release-mixin/test/resources/config.gradle b/src/test/resources/config.gradle similarity index 100% rename from src/main/python/ddadevops/release-mixin/test/resources/config.gradle rename to src/test/resources/config.gradle diff --git a/src/main/python/ddadevops/release-mixin/test/resources/config.json b/src/test/resources/config.json similarity index 100% rename from src/main/python/ddadevops/release-mixin/test/resources/config.json rename to src/test/resources/config.json diff --git a/src/main/python/ddadevops/release-mixin/test/resources/config.py b/src/test/resources/config.py similarity index 100% rename from src/main/python/ddadevops/release-mixin/test/resources/config.py rename to src/test/resources/config.py From 4575785dfa061fef212e85cebaaa552c1c843252 Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 10 Mar 2023 13:39:31 +0100 Subject: [PATCH 146/243] Rename release-mixin to release_mixin --- .../{release-mixin => release_mixin}/.gitignore | 0 .../{release-mixin => release_mixin}/LICENSE | 0 .../{release-mixin => release_mixin}/README.md | 0 src/main/python/ddadevops/release_mixin/__init__.py | 0 .../{release-mixin => release_mixin}/build.py | 0 .../{release-mixin => release_mixin}/config.json | 0 .../{release-mixin => release_mixin}/doc/arch.md | 0 .../doc/architecture.png.png | Bin .../{release-mixin => release_mixin}/domain.py | 0 .../infrastructure.py | 4 ++-- .../infrastructure_api.py | 0 .../release_mixin.py | 6 +++--- .../{release-mixin => release_mixin}/services.py | 6 +++--- 13 files changed, 8 insertions(+), 8 deletions(-) rename src/main/python/ddadevops/{release-mixin => release_mixin}/.gitignore (100%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/LICENSE (100%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/README.md (100%) create mode 100644 src/main/python/ddadevops/release_mixin/__init__.py rename src/main/python/ddadevops/{release-mixin => release_mixin}/build.py (100%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/config.json (100%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/doc/arch.md (100%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/doc/architecture.png.png (100%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/domain.py (100%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/infrastructure.py (95%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/infrastructure_api.py (100%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/release_mixin.py (89%) rename src/main/python/ddadevops/{release-mixin => release_mixin}/services.py (91%) diff --git a/src/main/python/ddadevops/release-mixin/.gitignore b/src/main/python/ddadevops/release_mixin/.gitignore similarity index 100% rename from src/main/python/ddadevops/release-mixin/.gitignore rename to src/main/python/ddadevops/release_mixin/.gitignore diff --git a/src/main/python/ddadevops/release-mixin/LICENSE b/src/main/python/ddadevops/release_mixin/LICENSE similarity index 100% rename from src/main/python/ddadevops/release-mixin/LICENSE rename to src/main/python/ddadevops/release_mixin/LICENSE diff --git a/src/main/python/ddadevops/release-mixin/README.md b/src/main/python/ddadevops/release_mixin/README.md similarity index 100% rename from src/main/python/ddadevops/release-mixin/README.md rename to src/main/python/ddadevops/release_mixin/README.md diff --git a/src/main/python/ddadevops/release_mixin/__init__.py b/src/main/python/ddadevops/release_mixin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main/python/ddadevops/release-mixin/build.py b/src/main/python/ddadevops/release_mixin/build.py similarity index 100% rename from src/main/python/ddadevops/release-mixin/build.py rename to src/main/python/ddadevops/release_mixin/build.py diff --git a/src/main/python/ddadevops/release-mixin/config.json b/src/main/python/ddadevops/release_mixin/config.json similarity index 100% rename from src/main/python/ddadevops/release-mixin/config.json rename to src/main/python/ddadevops/release_mixin/config.json diff --git a/src/main/python/ddadevops/release-mixin/doc/arch.md b/src/main/python/ddadevops/release_mixin/doc/arch.md similarity index 100% rename from src/main/python/ddadevops/release-mixin/doc/arch.md rename to src/main/python/ddadevops/release_mixin/doc/arch.md diff --git a/src/main/python/ddadevops/release-mixin/doc/architecture.png.png b/src/main/python/ddadevops/release_mixin/doc/architecture.png.png similarity index 100% rename from src/main/python/ddadevops/release-mixin/doc/architecture.png.png rename to src/main/python/ddadevops/release_mixin/doc/architecture.png.png diff --git a/src/main/python/ddadevops/release-mixin/domain.py b/src/main/python/ddadevops/release_mixin/domain.py similarity index 100% rename from src/main/python/ddadevops/release-mixin/domain.py rename to src/main/python/ddadevops/release_mixin/domain.py diff --git a/src/main/python/ddadevops/release-mixin/infrastructure.py b/src/main/python/ddadevops/release_mixin/infrastructure.py similarity index 95% rename from src/main/python/ddadevops/release-mixin/infrastructure.py rename to src/main/python/ddadevops/release_mixin/infrastructure.py index 6843e58..d701879 100644 --- a/src/main/python/ddadevops/release-mixin/infrastructure.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure.py @@ -1,5 +1,5 @@ -from domain import Release, Version, ReleaseType -from infrastructure_api import FileHandler +from .domain import Release, Version, ReleaseType +from .infrastructure_api import FileHandler class VersionRepository(): diff --git a/src/main/python/ddadevops/release-mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py similarity index 100% rename from src/main/python/ddadevops/release-mixin/infrastructure_api.py rename to src/main/python/ddadevops/release_mixin/infrastructure_api.py diff --git a/src/main/python/ddadevops/release-mixin/release_mixin.py b/src/main/python/ddadevops/release_mixin/release_mixin.py similarity index 89% rename from src/main/python/ddadevops/release-mixin/release_mixin.py rename to src/main/python/ddadevops/release_mixin/release_mixin.py index fa76f65..39d002e 100644 --- a/src/main/python/ddadevops/release-mixin/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin/release_mixin.py @@ -1,7 +1,7 @@ from ddadevops import DevopsBuild -from infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository -from infrastructure_api import GitApi -from services import PrepareReleaseService, TagAndPushReleaseService +from .infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository +from .infrastructure_api import GitApi +from .services import PrepareReleaseService, TagAndPushReleaseService def create_release_mixin_config(config_file, main_branch) -> dict: config = {} diff --git a/src/main/python/ddadevops/release-mixin/services.py b/src/main/python/ddadevops/release_mixin/services.py similarity index 91% rename from src/main/python/ddadevops/release-mixin/services.py rename to src/main/python/ddadevops/release_mixin/services.py index 42b8543..11a2433 100644 --- a/src/main/python/ddadevops/release-mixin/services.py +++ b/src/main/python/ddadevops/release_mixin/services.py @@ -1,6 +1,6 @@ -from infrastructure import ReleaseRepository -from infrastructure_api import GitApi -from domain import Version, Release +from .infrastructure import ReleaseRepository +from .infrastructure_api import GitApi +from .domain import Version, Release class PrepareReleaseService(): From c23c5287bdcae32cc6b4593494d8ef24e99d9a13 Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 10 Mar 2023 13:40:09 +0100 Subject: [PATCH 147/243] Remove old project files As this is now in a subdirectory we can have the main project handle these files --- .../python/ddadevops/release_mixin/.gitignore | 31 ------------------- .../python/ddadevops/release_mixin/LICENSE | 9 ------ .../python/ddadevops/release_mixin/README.md | 2 -- 3 files changed, 42 deletions(-) delete mode 100644 src/main/python/ddadevops/release_mixin/.gitignore delete mode 100644 src/main/python/ddadevops/release_mixin/LICENSE delete mode 100644 src/main/python/ddadevops/release_mixin/README.md diff --git a/src/main/python/ddadevops/release_mixin/.gitignore b/src/main/python/ddadevops/release_mixin/.gitignore deleted file mode 100644 index 58f5cd3..0000000 --- a/src/main/python/ddadevops/release_mixin/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -# ---> Go -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work - -__pycache__ - -.clj-kondo/ -.lsp/ -target/ - -# vs code settings -.vscode \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/LICENSE b/src/main/python/ddadevops/release_mixin/LICENSE deleted file mode 100644 index 2071b23..0000000 --- a/src/main/python/ddadevops/release_mixin/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/main/python/ddadevops/release_mixin/README.md b/src/main/python/ddadevops/release_mixin/README.md deleted file mode 100644 index e8c70ca..0000000 --- a/src/main/python/ddadevops/release_mixin/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# terraformDummyRepo - From ce2e2635f545ec94e9fd3c1eed9d0d1e3494e25d Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sat, 11 Mar 2023 17:16:49 +0100 Subject: [PATCH 148/243] * make executabel-name configurable * mv infra code from app to infra --- src/main/python/ddadevops/application.py | 39 +++-------- src/main/python/ddadevops/c4k_mixin.py | 49 +++++++++----- src/main/python/ddadevops/domain.py | 15 ++++- src/main/python/ddadevops/infrastructure.py | 21 +++++- src/test/python/test_c4k_mixin.py | 4 +- src/test/python/test_domain.py | 71 ++++++++++++++++++--- 6 files changed, 137 insertions(+), 62 deletions(-) diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index c4e4a24..a2c9632 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,11 +1,5 @@ from .domain import Build, DockerBuild, C4kBuild -from .infrastructure import FileApi, ResourceApi, DockerApi -from os import chmod -import yaml -from .python_util import execute -from .credential import gopass_field_from_path, gopass_password_from_path - - +from .infrastructure import FileApi, ResourceApi, DockerApi, ExecutionApi class BuildService: @@ -26,7 +20,7 @@ class DockerBuildService: data = self.resource_api.read_resource( "src/main/resources/docker/" + build.name ) - self.file_api.write_to_file(build.build_path() + "/" + build.name, data) + self.file_api.write_data_to_file(build.build_path() + "/" + build.name, data) def __copy_build_resources_from_package__(self, build: DockerBuild): self.__copy_build_resource_file_from_package__( @@ -62,32 +56,19 @@ class DockerBuildService: def test(self, build: DockerBuild): self.docker_api.test(build.name(), build.build_path()) -# TODO: move infrastructure fktns to infra apis + class C4kBuildService: def __init__(self): self.file_api = FileApi() + self.execution_api = ExecutionApi() def write_c4k_config(self, c4k_build: C4kBuild): - with open( - c4k_build.build.build_path() + "/out_c4k_config.yaml", "w", encoding="utf-8" - ) as output_file: - yaml.dump(c4k_build.config(), output_file) + path = c4k_build.build.build_path() + "/out_c4k_config.yaml" + self.file_api.write_yaml_to_file(path, c4k_build.config()) def write_c4k_auth(self, c4k_build: C4kBuild): - with open( - c4k_build.build.build_path() + "/out_c4k_auth.yaml", "w", encoding="utf-8" - ) as output_file: - yaml.dump(c4k_build.c4k_mixin_auth, output_file) - chmod(c4k_build.build.build_path() + "/out_c4k_auth.yaml", 0o600) + path = c4k_build.build.build_path() + "/out_c4k_auth.yaml" + self.file_api.write_yaml_to_file(path, c4k_build.c4k_mixin_auth) - def c4k_apply(self, c4k_build: C4kBuild, ry_run=False): - module = c4k_build.build.module - build_path = c4k_build.build.build_path() - cmd = f"c4k-{module}-standalone.jar {build_path}/out_c4k_config.yaml {build_path}/out_c4k_auth.yaml > {build_path}/out_{module}.yaml" - output = "" - if dry_run: - print(cmd) - else: - output = execute(cmd, True) - print(output) - return output + def c4k_apply(self, c4k_build: C4kBuild, dry_run=False): + return self.execution_api.execute(c4k_build.command(), dry_run) diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 45fd185..333e0db 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -3,27 +3,42 @@ from .application import C4kBuildService from .devops_build import DevopsBuild from .credential import gopass_field_from_path, gopass_password_from_path -def add_c4k_mixin_config(config, - c4k_config_dict, - c4k_auth_dict, - grafana_cloud_user=None, - grafana_cloud_password=None, - grafana_cloud_url='https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push'): + +def add_c4k_mixin_config( + config, + c4k_config_dict, + c4k_auth_dict, + executabel_name=None, + grafana_cloud_user=None, + grafana_cloud_password=None, + grafana_cloud_url="https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push", +): if not grafana_cloud_user: grafana_cloud_user = gopass_field_from_path( - 'server/meissa/grafana-cloud', 'grafana-cloud-user') + "server/meissa/grafana-cloud", "grafana-cloud-user" + ) if not grafana_cloud_password: grafana_cloud_password = gopass_password_from_path( - 'server/meissa/grafana-cloud') - c4k_auth_dict.update({'mon-auth': { - 'grafana-cloud-user': grafana_cloud_user, - 'grafana-cloud-password': grafana_cloud_password - }}) - c4k_config_dict.update({'mon-cfg': { - 'grafana-cloud-url': grafana_cloud_url - }}) - config.update({'C4kMixin': {'Config': c4k_config_dict, - 'Auth': c4k_auth_dict}}) + "server/meissa/grafana-cloud" + ) + c4k_auth_dict.update( + { + "mon-auth": { + "grafana-cloud-user": grafana_cloud_user, + "grafana-cloud-password": grafana_cloud_password, + } + } + ) + c4k_config_dict.update({"mon-cfg": {"grafana-cloud-url": grafana_cloud_url}}) + config.update( + { + "C4kMixin": { + "executabel_name": executabel_name, + "config": c4k_config_dict, + "auth": c4k_auth_dict, + } + } + ) return config diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 3a5f860..bfb0d44 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -71,8 +71,12 @@ class DockerBuild(Validateable): class C4kBuild(Validateable): def __init__(self, build: Build, project, config): self.build = build - self.c4k_mixin_config = config["C4kMixin"]["Config"] - self.c4k_mixin_auth = config["C4kMixin"]["Auth"] + tmp_executabel_name = config["C4kMixin"]["executabel_name"] + if not tmp_executabel_name: + tmp_executabel_name = self.build.module + self.executabel_name = tmp_executabel_name + self.c4k_mixin_config = config["C4kMixin"]["config"] + self.c4k_mixin_auth = config["C4kMixin"]["auth"] tmp = self.c4k_mixin_config["mon-cfg"] tmp.update({"cluster-name": self.build.module, "cluster-stage": self.build.stage}) self.c4k_mixin_config.update({"mon-cfg": tmp}) @@ -85,3 +89,10 @@ class C4kBuild(Validateable): self.c4k_mixin_config.update({'fqdn': fqdn}) return self.c4k_mixin_config + def command(self): + module = self.build.module + build_path = self.build.build_path() + config_path = f"{build_path}/out_c4k_config.yaml" + auth_path = f"{build_path}/out_c4k_auth.yaml" + output_path = f"{build_path}/out_{module}.yaml" + return f"c4k-{self.executabel_name}-standalone.jar {config_path} {auth_path} > {output_path}" diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index 1005f14..ba07c67 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -1,6 +1,8 @@ from pathlib import Path from sys import stdout from pkg_resources import resource_string +from os import chmod +import yaml from .python_util import execute class ResourceApi(): @@ -18,10 +20,15 @@ class FileApi(): def cp_recursive(self, src: str, target_dir: str): execute('cp -r ' + src + ' ' + target_dir, shell=True) - def write_to_file(self, path: Path, data: bytes): - with open(path, "w", encoding=stdout.encoding) as output_file: + def write_data_to_file(self, path: Path, data: bytes): + with open(path, "w", encoding="utf-8") as output_file: output_file.write(data.decode(stdout.encoding)) + def write_yaml_to_file(self, path: Path, data: map): + with open(path, "w", encoding="utf-8") as output_file: + yaml.dump(data, output_file) + chmod(path, 0o600) + class DockerApi(): def image(self, name: str, path: Path): execute('docker build -t ' + name + @@ -51,3 +58,13 @@ class DockerApi(): execute('docker build -t ' + name + '-test ' + '--file ' + path + '/test/Dockerfile ' + path + '/test', shell=True) + +class ExecutionApi(): + def execute(command: str, dry_run=False): + output = "" + if dry_run: + print(command) + else: + output = execute(command, True) + print(output) + return output \ No newline at end of file diff --git a/src/test/python/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py index bd8d60f..eca6d05 100644 --- a/src/test/python/test_c4k_mixin.py +++ b/src/test/python/test_c4k_mixin.py @@ -27,9 +27,7 @@ def test_c4k_mixin(tmp_path): add_c4k_mixin_config(project_config, config, auth, grafana_cloud_user='user', grafana_cloud_password='password') assert project_config.get('C4kMixin') is not None - assert project_config.get('C4kMixin').get('Config') is config - assert project_config.get('C4kMixin').get('Auth') is auth - + mixin = MyC4kMixin(project, project_config) mixin.initialize_build_dir() assert mixin.build_path() == f'{tmp_path_str}/{build_dir}/{project_name}/{module_name}' diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index dbbe8d2..c2baf3b 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -42,11 +42,11 @@ def test_validate_with_reason(): def test_c4k_build_should_update_fqdn(tmp_path): - project = Project(str(tmp_path), name='test-project') + project = Project(str(tmp_path), name="test-project") project_config = { "stage": "test", "project_root_path": str(tmp_path), - "module": 'module', + "module": "module", "build_dir_name": "target", } config = {"issuer": "staging"} @@ -63,21 +63,74 @@ def test_c4k_build_should_update_fqdn(tmp_path): grafana_cloud_password="password", ) sut = C4kBuild(Build(project, project_config), project, project_config) - sut.update_runtime_config('test.de', None, None) + sut.update_runtime_config("test.de", None, None) assert { "issuer": "staging", - 'fqdn': 'test.de', + "fqdn": "test.de", "mon-cfg": { "cluster-name": "module", "cluster-stage": "test", "grafana-cloud-url": "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push", }, } == sut.config() - assert {'jicofo-auth-password': 'pw2', - 'jicofo-component-secret': 'pw3', - 'jvb-auth-password': 'pw1', - 'mon-auth': {'grafana-cloud-password': 'password', - 'grafana-cloud-user': 'user'}} == sut.c4k_mixin_auth + assert { + "jicofo-auth-password": "pw2", + "jicofo-component-secret": "pw3", + "jvb-auth-password": "pw1", + "mon-auth": { + "grafana-cloud-password": "password", + "grafana-cloud-user": "user", + }, + } == sut.c4k_mixin_auth sut.update_runtime_config + + +def test_c4k_build_should_calculate_command(tmp_path): + project = Project(str(tmp_path), name="test-project") + project_config = { + "stage": "test", + "project_root_path": "", + "module": "module", + "build_dir_name": "target", + } + add_c4k_mixin_config( + project_config, + {}, + {}, + grafana_cloud_user="user", + grafana_cloud_password="password", + ) + sut = C4kBuild(Build(project, project_config), project, project_config) + assert ( + "c4k-module-standalone.jar " + + "/target/test-project/module/out_c4k_config.yaml " + + "/target/test-project/module/out_c4k_auth.yaml > " + + "/target/test-project/module/out_module.yaml" + == sut.command() + ) + + project_config = { + "stage": "test", + "project_root_path": "", + "module": "module", + "build_dir_name": "target", + } + add_c4k_mixin_config( + project_config, + {}, + {}, + executabel_name = "executabel_name", + grafana_cloud_user="user", + grafana_cloud_password="password", + ) + sut = C4kBuild(Build(project, project_config), project, project_config) + assert ( + "c4k-executabel_name-standalone.jar " + + "/target/test-project/module/out_c4k_config.yaml " + + "/target/test-project/module/out_c4k_auth.yaml > " + + "/target/test-project/module/out_module.yaml" + == sut.command() + ) + From 3bdc75cdcac27c4701d36a97a1ee3e398610cac3 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 12 Mar 2023 16:55:15 +0100 Subject: [PATCH 149/243] add arch docu --- doc/architecture.md | 11 --- doc/architecture/Build.md | 110 +++++++++++++++++++++++ doc/architecture/BuildCreationAndCall.md | 54 +++++++++++ doc/architecture/Domain.md | 10 +++ 4 files changed, 174 insertions(+), 11 deletions(-) delete mode 100644 doc/architecture.md create mode 100644 doc/architecture/Build.md create mode 100644 doc/architecture/BuildCreationAndCall.md create mode 100644 doc/architecture/Domain.md diff --git a/doc/architecture.md b/doc/architecture.md deleted file mode 100644 index c44c96e..0000000 --- a/doc/architecture.md +++ /dev/null @@ -1,11 +0,0 @@ -```mermaid -classDiagram - DevopsBuild <|-- DevopsDockerBuild - DevopsBuild <|-- DevopsTerraformBuild - - DevopsBuild <|-- C4kMixin - - DevopsBuild *-- BuildService - DevopsDockerBuild *-- DockerBuildService - C4kMixin *-- C4kBuildService -``` \ No newline at end of file diff --git a/doc/architecture/Build.md b/doc/architecture/Build.md new file mode 100644 index 0000000..9ef0024 --- /dev/null +++ b/doc/architecture/Build.md @@ -0,0 +1,110 @@ +# Build Frontend + +```mermaid +classDiagram + class DevopsBuild { + name() + build_path() + initialize_build_dir() + } + + class DevopsTerraformBuild { + terraform_build_commons_path() + project_vars() + copy_build_resource_file_from_package(name) + copy_build_resources_from_package() + copy_build_resources_from_dir() + copy_local_state() + rescue_local_state() + initialize_build_dir() + post_build() + init_client() + write_output(terraform) + read_output_json() + plan() + plan_fail_on_diff() + apply(auto_approve=False) + refresh() + destroy(auto_approve=False) + tf_import(tf_import_name, tf_import_resource,) + print_terraform_command(terraform) + } + + class HetznerMixin { + // HetznerMixin -> HetznerTerraformBuild + project_vars() + copy_build_resources_from_package() + } + + class ExoscaleMixin { + // ExoscaleMixin -> ExoscaleTerraformBuild + project_vars() + copy_build_resources_from_package() + } + + class AwsBackendPropertiesMixin { + def project_vars() + copy_build_resources_from_package() + init_client() + } + + class DigitaloceanBackendPropertiesMixin { + project_vars(self) + copy_build_resources_from_package(self) + init_client(self) + } + + class DevopsDockerBuild { + def initialize_build_dir() + image() + drun() + dockerhub_login() + dockerhub_publish() + test() + } + + class ProvsK3sMixin { + // ProvsK3sMixin -> ProvsK3sBuild + def update_runtime_config(fqdn, ipv4, ipv6=None) + write_provs_config() + provs_apply(dry_run=False) + } + + class C4kMixin { + // C4kMixin -> C4kBuild + def write_c4k_config() + def write_c4k_auth() + c4k_apply(dry_run=False) + } + + DevopsBuild <|-- DevopsDockerBuild + DevopsBuild <|-- DevopsTerraformBuild + DevopsBuild <|-- AwsRdsPgMixin + + DevopsTerraformBuild <|-- AwsBackendPropertiesMixin + DevopsTerraformBuild <|-- DigitaloceanTerraformBuild + DevopsTerraformBuild <|--ExoscaleMixin + DevopsTerraformBuild <|--HetznerMixin + DevopsBuild <|-- ProvsK3sMixin + DigitaloceanTerraformBuild <|-- DigitaloceanBackendPropertiesMixin + AwsBackendPropertiesMixin <|-- AwsMfaMixin + + DevopsBuild <|-- C4kMixin + +``` + +# Build Frontend with application and domain + +```mermaid +classDiagram + DevopsBuild <|-- DevopsDockerBuild + DevopsBuild <|-- DevopsTerraformBuild + + DevopsBuild <|-- C4kMixin + + DevopsBuild *-- BuildService + DevopsDockerBuild *-- DockerBuildService + C4kMixin *-- C4kBuildService +``` + +# Domain \ No newline at end of file diff --git a/doc/architecture/BuildCreationAndCall.md b/doc/architecture/BuildCreationAndCall.md new file mode 100644 index 0000000..3c46bde --- /dev/null +++ b/doc/architecture/BuildCreationAndCall.md @@ -0,0 +1,54 @@ +# Build Frontend with application and domain + +```mermaid +classDiagram + class DevopsBuild { + __init__(project, config) + do_sth(project) + } + + class ProjectRepository { + get_build(project): Build + set_build(project, build) + } + + class Build + + class BuildService { + do_sth(project, build) + } + + DevopsBuild *-- BuildService + BuildService *-- ProjectRepository + DevopsBuild *-- ProjectRepository + +``` + +In case of simple operations we will not need the BuildService in between. + + +## Init Sequence + +```mermaid +sequenceDiagram + MyBuild ->> DevOpsBuild: create_config + MyBuild ->> DevOpsBuild: __init__(project, config) + activate DevOpsBuild + DevOpsBuild ->> Build: __init__ + DevOpsBuild ->> ProjectRepository: set_build(build) + deactivate DevOpsBuild +``` + +## do_sth Sequence + +```mermaid +sequenceDiagram + MyBuild ->> DevOpsBuild: do_sth(project) + activate DevOpsBuild + DevOpsBuild ->> BuildService: do_sth(project) + activate BuildService + BuildService ->> ProjectRepository: get_build + BuildService ->> BuildService: do_some_complicated_stuff(build) + deactivate BuildService + deactivate DevOpsBuild +``` \ No newline at end of file diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md new file mode 100644 index 0000000..133940d --- /dev/null +++ b/doc/architecture/Domain.md @@ -0,0 +1,10 @@ +# Domain + +```mermaid +classDiagram + class Build { + __init__(project, config) + do_sth(project) + } + +``` \ No newline at end of file From d95827e1e00ba8f5c60b6d671e4502bce1cd8bec Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 12 Mar 2023 17:40:10 +0100 Subject: [PATCH 150/243] refactor devops_build to arch --- build.py | 2 +- doc/architecture/Domain.md | 5 ++ src/main/python/ddadevops/__init__.py | 3 +- src/main/python/ddadevops/application.py | 8 -- src/main/python/ddadevops/devops_build.py | 23 ++++-- src/main/python/ddadevops/domain.py | 26 ++++--- src/main/python/ddadevops/infrastructure.py | 84 ++++++++++++++------- src/test/python/test_domain.py | 25 +++--- 8 files changed, 110 insertions(+), 66 deletions(-) diff --git a/build.py b/build.py index bb47bcd..404fcbb 100644 --- a/build.py +++ b/build.py @@ -28,7 +28,7 @@ use_plugin("python.distutils") default_task = "publish" name = "ddadevops" -version = "3.1.3" +version = "3.2.0-dev" summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud" description = __doc__ authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")] diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md index 133940d..8684547 100644 --- a/doc/architecture/Domain.md +++ b/doc/architecture/Domain.md @@ -6,5 +6,10 @@ classDiagram __init__(project, config) do_sth(project) } + + class ProjectRepository { + get_build(project): Build + set_build(project, build) + } ``` \ No newline at end of file diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index b423561..f1411b8 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -20,6 +20,7 @@ from .devops_build import DevopsBuild, create_devops_build_config, get_devops_bu from .credential import gopass_password_from_path, gopass_field_from_path from .domain import Validateable, Build, DockerBuild, C4kBuild -from .application import BuildService, DockerBuildService, C4kBuildService +from .application import DockerBuildService, C4kBuildService +from .infrastructure import ProjectRepository, ResourceApi, FileApi, DockerApi, ExecutionApi __version__ = "${version}" diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index a2c9632..975a0f4 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -2,14 +2,6 @@ from .domain import Build, DockerBuild, C4kBuild from .infrastructure import FileApi, ResourceApi, DockerApi, ExecutionApi -class BuildService: - def __init__(self): - self.file_api = FileApi() - - def initialize_build_dir(self, build: Build): - self.file_api.clean_dir(build.build_path()) - - class DockerBuildService: def __init__(self): self.file_api = FileApi() diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 5087a90..219b0e2 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -1,5 +1,6 @@ +import deprecation from .domain import Build -from .application import BuildService +from .infrastructure import ProjectRepository, FileApi def create_devops_build_config( @@ -12,7 +13,8 @@ def create_devops_build_config( "build_dir_name": build_dir_name, } - +@deprecation.deprecated(deprecated_in="3.2") +# Do not expose build to outside def get_devops_build(project): return project.get_property("devops_build") @@ -33,15 +35,20 @@ def get_tag_from_latest_commit(): class DevopsBuild: def __init__(self, project, config): - self.build = Build(project, config) - self.build_service = BuildService() - project.set_property("devops_build", self) + self.project = project + self.file_api = FileApi() + self.repo = ProjectRepository() + config.update({"name": project.name}) + build = Build(config) + self.repo.set_build(self.project, build) def name(self): - return self.build.name() + build = self.repo.get_build(self.project) + return build.name def build_path(self): - return self.build.build_path() + build = self.repo.get_build(self.project) + return build.build_path() def initialize_build_dir(self): - self.build_service.initialize_build_dir(self.build) + self.file_api.clean_dir(build.build_path()) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index bfb0d44..921eeaf 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -1,3 +1,4 @@ +import deprecation from typing import List from .python_util import filter_none @@ -18,26 +19,29 @@ class Validateable: class Build(Validateable): - def __init__(self, project, config): + def __init__(self, config): self.stage = config["stage"] + self.name = config["name"] self.project_root_path = config["project_root_path"] self.module = config["module"] self.build_dir_name = config["build_dir_name"] + # Deprecated - no longer use generic stack ... self.stack = {} - self.project = project + @deprecation.deprecated(deprecated_in="3.2") + # use .name instead def name(self): - return self.project.name + return self.name 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] return "/".join(filter_none(path)) # TODO: these functions should be located at TerraformBuild later on. def update_runtime_config(self, fqdn, ipv4, ipv6): - self.__put__('fqdn', fqdn) - self.__put__('ipv4', ipv4) - self.__put__('ipv6', ipv6) + self.__put__("fqdn", fqdn) + self.__put__("ipv4", ipv4) + self.__put__("ipv6", ipv6) def __put__(self, key, value): self.stack[key] = value @@ -78,15 +82,17 @@ class C4kBuild(Validateable): self.c4k_mixin_config = config["C4kMixin"]["config"] self.c4k_mixin_auth = config["C4kMixin"]["auth"] tmp = self.c4k_mixin_config["mon-cfg"] - tmp.update({"cluster-name": self.build.module, "cluster-stage": self.build.stage}) + tmp.update( + {"cluster-name": self.build.module, "cluster-stage": self.build.stage} + ) self.c4k_mixin_config.update({"mon-cfg": tmp}) def update_runtime_config(self, fqdn, ipv4, ipv6): self.build.update_runtime_config(fqdn, ipv4, ipv6) def config(self): - fqdn = self.build.__get__('fqdn') - self.c4k_mixin_config.update({'fqdn': fqdn}) + fqdn = self.build.__get__("fqdn") + self.c4k_mixin_config.update({"fqdn": fqdn}) return self.c4k_mixin_config def command(self): diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index ba07c67..aef9d73 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -3,22 +3,33 @@ from sys import stdout from pkg_resources import resource_string from os import chmod import yaml +from .domain import Build from .python_util import execute -class ResourceApi(): + +class ProjectRepository: + def get_build(self, project) -> Build: + return project.get_property("devops_build") + + def set_build(self, project, build: Build): + project.set_property("devops_build", build) + + +class ResourceApi: def read_resource(self, path: str) -> bytes: return resource_string(__name__, path) -class FileApi(): + +class FileApi: def clean_dir(self, directory: str): - execute('rm -rf ' + directory, shell=True) - execute('mkdir -p ' + directory, shell=True) + execute("rm -rf " + directory, shell=True) + execute("mkdir -p " + directory, shell=True) def cp_force(self, src: str, target_dir: str): - execute('cp -f ' + src + '* ' + target_dir, shell=True) + execute("cp -f " + src + "* " + target_dir, shell=True) def cp_recursive(self, src: str, target_dir: str): - execute('cp -r ' + src + ' ' + target_dir, shell=True) + execute("cp -r " + src + " " + target_dir, shell=True) def write_data_to_file(self, path: Path, data: bytes): with open(path, "w", encoding="utf-8") as output_file: @@ -29,37 +40,56 @@ class FileApi(): yaml.dump(data, output_file) chmod(path, 0o600) -class DockerApi(): + +class DockerApi: def image(self, name: str, path: Path): - execute('docker build -t ' + name + - ' --file ' + path + '/image/Dockerfile ' - + path + '/image', shell=True) + execute( + "docker build -t " + + name + + " --file " + + path + + "/image/Dockerfile " + + path + + "/image", + shell=True, + ) def drun(self, name: str): - execute('docker run -it --entrypoint="" ' + - name + ' /bin/bash', shell=True) + execute('docker run -it --entrypoint="" ' + name + " /bin/bash", shell=True) def dockerhub_login(self, username: str, password: str): - execute('docker login --username ' + username + - ' --password ' + password, shell=True) + execute( + "docker login --username " + username + " --password " + password, + shell=True, + ) def dockerhub_publish(self, name: str, username: str, tag=None): if tag is not None: - execute('docker tag ' + name + ' ' + username + - '/' + name + ':' + tag, shell=True) - execute('docker push ' + username + - '/' + name + ':' + tag, shell=True) - execute('docker tag ' + name + ' ' + username + - '/' + name + ':latest', shell=True) - execute('docker push ' + username + - '/' + name + ':latest', shell=True) + execute( + "docker tag " + name + " " + username + "/" + name + ":" + tag, + shell=True, + ) + execute("docker push " + username + "/" + name + ":" + tag, shell=True) + execute( + "docker tag " + name + " " + username + "/" + name + ":latest", shell=True + ) + execute("docker push " + username + "/" + name + ":latest", shell=True) def test(self, name: str, path: Path): - execute('docker build -t ' + name + '-test ' + - '--file ' + path + '/test/Dockerfile ' - + path + '/test', shell=True) + execute( + "docker build -t " + + name + + "-test " + + "--file " + + path + + "/test/Dockerfile " + + path + + "/test", + shell=True, + ) -class ExecutionApi(): + +class ExecutionApi: def execute(command: str, dry_run=False): output = "" if dry_run: @@ -67,4 +97,4 @@ class ExecutionApi(): else: output = execute(command, True) print(output) - return output \ No newline at end of file + return output diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index c2baf3b..32f45be 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -42,9 +42,10 @@ def test_validate_with_reason(): def test_c4k_build_should_update_fqdn(tmp_path): - project = Project(str(tmp_path), name="test-project") + project = Project(str(tmp_path), name="name") project_config = { "stage": "test", + "name": "name", "project_root_path": str(tmp_path), "module": "module", "build_dir_name": "target", @@ -62,7 +63,7 @@ def test_c4k_build_should_update_fqdn(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - sut = C4kBuild(Build(project, project_config), project, project_config) + sut = C4kBuild(Build(project_config), project, project_config) sut.update_runtime_config("test.de", None, None) assert { @@ -88,9 +89,10 @@ def test_c4k_build_should_update_fqdn(tmp_path): def test_c4k_build_should_calculate_command(tmp_path): - project = Project(str(tmp_path), name="test-project") + project = Project(str(tmp_path), name="name") project_config = { "stage": "test", + "name": "name", "project_root_path": "", "module": "module", "build_dir_name": "target", @@ -102,17 +104,18 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - sut = C4kBuild(Build(project, project_config), project, project_config) + sut = C4kBuild(Build(project_config), project, project_config) assert ( "c4k-module-standalone.jar " - + "/target/test-project/module/out_c4k_config.yaml " - + "/target/test-project/module/out_c4k_auth.yaml > " - + "/target/test-project/module/out_module.yaml" + + "/target/name/module/out_c4k_config.yaml " + + "/target/name/module/out_c4k_auth.yaml > " + + "/target/name/module/out_module.yaml" == sut.command() ) project_config = { "stage": "test", + "name": "name", "project_root_path": "", "module": "module", "build_dir_name": "target", @@ -125,12 +128,12 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - sut = C4kBuild(Build(project, project_config), project, project_config) + sut = C4kBuild(Build(project_config), project, project_config) assert ( "c4k-executabel_name-standalone.jar " - + "/target/test-project/module/out_c4k_config.yaml " - + "/target/test-project/module/out_c4k_auth.yaml > " - + "/target/test-project/module/out_module.yaml" + + "/target/name/module/out_c4k_config.yaml " + + "/target/name/module/out_c4k_auth.yaml > " + + "/target/name/module/out_module.yaml" == sut.command() ) From eec739247058f8262534018cbaa5492884b82915 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 12 Mar 2023 17:57:31 +0100 Subject: [PATCH 151/243] refactor devops_docker_build to arch --- src/main/python/ddadevops/application.py | 27 ++++--- .../python/ddadevops/devops_docker_build.py | 81 +++++++++---------- src/main/python/ddadevops/domain.py | 4 +- src/main/python/ddadevops/infrastructure.py | 12 ++- 4 files changed, 64 insertions(+), 60 deletions(-) diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 975a0f4..209315f 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -22,30 +22,31 @@ class DockerBuildService: def __copy_build_resources_from_dir__(self, build: DockerBuild): self.file_api.cp_force(build.docker_build_commons_path(), build.build_path()) - def initialize_build_dir(self, build: DockerBuild): - self.file_api.clean_dir(build.build_path() + "/image/resources") - if build.use_package_common_files: - self.__copy_build_resources_from_package__(build) + def initialize_build_dir(self, build: Build, docker_build: DockerBuild): + build_path = build.build_path() + self.file_api.clean_dir(f"{build_path}/image/resources") + if docker_build.use_package_common_files: + self.__copy_build_resources_from_package__(docker_build) else: - self.__copy_build_resources_from_dir__(build) - self.file_api.cp_recursive("image", build.build_path()) - self.file_api.cp_recursive("test", build.build_path()) + self.__copy_build_resources_from_dir__(docker_build) + self.file_api.cp_recursive("image", build_path) + self.file_api.cp_recursive("test", build_path) - def image(self, build: DockerBuild): + def image(self, build: Build): self.docker_api.image(build.name(), build.build_path()) def drun(self, build: DockerBuild): self.docker_api.drun(build.name()) - def dockerhub_login(self, build: DockerBuild): - self.docker_api.dockerhub_login(build.dockerhub_user, build.dockerhub_password) + def dockerhub_login(self, docker_build: DockerBuild): + self.docker_api.dockerhub_login(docker_build.dockerhub_user, docker_build.dockerhub_password) - def dockerhub_publish(self, build: DockerBuild): + def dockerhub_publish(self, build: Build, docker_build: DockerBuild): self.docker_api.dockerhub_publish( - build.name(), build.dockerhub_user, build.docker_publish_tag + build.name(), docker_build.dockerhub_user, docker_build.docker_publish_tag ) - def test(self, build: DockerBuild): + def test(self, build: Build): self.docker_api.test(build.name(), build.build_path()) diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index 538edff..3371e35 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -2,64 +2,63 @@ from .domain import DockerBuild from .application import DockerBuildService from .devops_build import DevopsBuild, create_devops_build_config -def create_devops_docker_build_config(stage, - project_root_path, - module, - dockerhub_user, - dockerhub_password, - build_dir_name='target', - use_package_common_files=True, - build_commons_path=None, - docker_build_commons_dir_name='docker', - docker_publish_tag=None): - ret = create_devops_build_config( - stage, project_root_path, module, build_dir_name) - ret.update({'dockerhub_user': dockerhub_user, - 'dockerhub_password': dockerhub_password, - 'use_package_common_files': use_package_common_files, - 'docker_build_commons_dir_name': docker_build_commons_dir_name, - 'build_commons_path': build_commons_path, - 'docker_publish_tag': docker_publish_tag, }) + +def create_devops_docker_build_config( + stage, + project_root_path, + module, + dockerhub_user, + dockerhub_password, + build_dir_name="target", + use_package_common_files=True, + build_commons_path=None, + docker_build_commons_dir_name="docker", + docker_publish_tag=None, +): + ret = create_devops_build_config(stage, project_root_path, module, build_dir_name) + ret.update( + { + "dockerhub_user": dockerhub_user, + "dockerhub_password": dockerhub_password, + "use_package_common_files": use_package_common_files, + "docker_build_commons_dir_name": docker_build_commons_dir_name, + "build_commons_path": build_commons_path, + "docker_publish_tag": docker_publish_tag, + } + ) return ret class DevopsDockerBuild(DevopsBuild): - def __init__(self, project, config): super().__init__(project, config) - self.docker_build = DockerBuild(project, config) self.docker_build_service = DockerBuildService() + docker_build = DockerBuild(config) + self.repo.set_docker_build(self.project, docker_build) def initialize_build_dir(self): super().initialize_build_dir() - self.docker_build_service.initialize_build_dir(self.docker_build) + build = self.repo.get_build(self.project) + docker_build = self.repo.get_docker_build(self.project) + self.docker_build_service.initialize_build_dir(build, docker_build) - # TODO: use application from here on def image(self): - run('docker build -t ' + self.name() + - ' --file ' + self.build_path() + '/image/Dockerfile ' - + self.build_path() + '/image', shell=True, check=True) + build = self.repo.get_build(self.project) + self.docker_build_service.image(build) def drun(self): - run('docker run --expose 8080 -it --entrypoint="" ' + - self.name() + ' /bin/bash', shell=True, check=True) + build = self.repo.get_build(self.project) + self.docker_build_service.drun(build) def dockerhub_login(self): - run('docker login --username ' + self.dockerhub_user + - ' --password ' + self.dockerhub_password, shell=True, check=True) + docker_build = self.repo.get_docker_build(self.project) + self.docker_build_service.dockerhub_login(docker_build) def dockerhub_publish(self): - if self.docker_publish_tag is not None: - run('docker tag ' + self.name() + ' ' + self.dockerhub_user + - '/' + self.name() + ':' + self.docker_publish_tag, shell=True, check=True) - run('docker push ' + self.dockerhub_user + - '/' + self.name() + ':' + self.docker_publish_tag, shell=True, check=True) - run('docker tag ' + self.name() + ' ' + self.dockerhub_user + - '/' + self.name() + ':latest', shell=True, check=True) - run('docker push ' + self.dockerhub_user + - '/' + self.name() + ':latest', shell=True, check=True) + build = self.repo.get_build(self.project) + docker_build = self.repo.get_docker_build(self.project) + self.docker_build_service.dockerhub_publish(build, docker_build) def test(self): - run('docker build -t ' + self.name() + '-test ' + - '--file ' + self.build_path() + '/test/Dockerfile ' - + self.build_path() + '/test', shell=True, check=True) + build = self.repo.get_build(self.project) + self.test.dockerhub_publish(build) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 921eeaf..969a15f 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -57,9 +57,7 @@ class Build(Validateable): class DockerBuild(Validateable): - def __init__(self, project, config): - project.build_depends_on("dda-python-terraform") - self.build = Build(project, config) + def __init__(self, config): self.dockerhub_user = config["dockerhub_user"] self.dockerhub_password = config["dockerhub_password"] self.use_package_common_files = config["use_package_common_files"] diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index aef9d73..bfda2b9 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -3,16 +3,22 @@ from sys import stdout from pkg_resources import resource_string from os import chmod import yaml -from .domain import Build +from .domain import Build, DockerBuild from .python_util import execute class ProjectRepository: def get_build(self, project) -> Build: - return project.get_property("devops_build") + return project.get_property("build") def set_build(self, project, build: Build): - project.set_property("devops_build", build) + project.set_property("build", build) + + def get_docker_build(self, project) -> DockerBuild: + return project.get_property("docker_build") + + def set_docker_build(self, project, build: DockerBuild): + project.set_property("docker_build", build) class ResourceApi: From dd85b7b95be62943f9ae23b36c5cb2b7c84aa536 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sun, 12 Mar 2023 19:09:32 +0100 Subject: [PATCH 152/243] refactor devops_c4k_mixin to arch --- doc/architecture/Domain.md | 29 ++++++++++- src/main/python/ddadevops/__init__.py | 4 +- src/main/python/ddadevops/application.py | 17 ------ src/main/python/ddadevops/c4k_mixin.py | 31 ++++++++--- src/main/python/ddadevops/devops_build.py | 4 +- .../python/ddadevops/devops_docker_build.py | 3 +- src/main/python/ddadevops/domain.py | 52 ++++++++++++------- src/main/python/ddadevops/infrastructure.py | 8 ++- src/test/python/test_c4k_mixin.py | 10 ++-- src/test/python/test_domain.py | 33 ++++++++---- 10 files changed, 128 insertions(+), 63 deletions(-) diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md index 8684547..91d84e7 100644 --- a/doc/architecture/Domain.md +++ b/doc/architecture/Domain.md @@ -3,13 +3,38 @@ ```mermaid classDiagram class Build { - __init__(project, config) - do_sth(project) + stage + name + project_root_path + module + build_dir_name } + class C4kBuild { + executabel_name + c4k_mixin_config + c4k_mixin_auth + } + + class DnsRecord { + fqdn + ipv4 + ipv6 + } + + C4kBuild *-- DnsRecord + +``` + +# Infrastructure + +```mermaid +classDiagram class ProjectRepository { get_build(project): Build set_build(project, build) } + + ``` \ No newline at end of file diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index f1411b8..1299557 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -19,8 +19,8 @@ from .devops_terraform_build import DevopsTerraformBuild, create_devops_terrafor from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path -from .domain import Validateable, Build, DockerBuild, C4kBuild -from .application import DockerBuildService, C4kBuildService +from .domain import Validateable, DnsRecord, Build, DockerBuild, C4kBuild +from .application import DockerBuildService from .infrastructure import ProjectRepository, ResourceApi, FileApi, DockerApi, ExecutionApi __version__ = "${version}" diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 209315f..746101b 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -48,20 +48,3 @@ class DockerBuildService: def test(self, build: Build): self.docker_api.test(build.name(), build.build_path()) - - -class C4kBuildService: - def __init__(self): - self.file_api = FileApi() - self.execution_api = ExecutionApi() - - def write_c4k_config(self, c4k_build: C4kBuild): - path = c4k_build.build.build_path() + "/out_c4k_config.yaml" - self.file_api.write_yaml_to_file(path, c4k_build.config()) - - def write_c4k_auth(self, c4k_build: C4kBuild): - path = c4k_build.build.build_path() + "/out_c4k_auth.yaml" - self.file_api.write_yaml_to_file(path, c4k_build.c4k_mixin_auth) - - def c4k_apply(self, c4k_build: C4kBuild, dry_run=False): - return self.execution_api.execute(c4k_build.command(), dry_run) diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 333e0db..b654787 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -1,9 +1,11 @@ -from .domain import C4kBuild -from .application import C4kBuildService +from .domain import C4kBuild, DnsRecord from .devops_build import DevopsBuild from .credential import gopass_field_from_path, gopass_password_from_path +from .infrastructure import ProjectRepository, FileApi, ExecutionApi +@deprecation.deprecated(deprecated_in="3.2") +# create objects direct instead def add_c4k_mixin_config( config, c4k_config_dict, @@ -42,17 +44,32 @@ def add_c4k_mixin_config( return config +#TODO: refactor this to C4kBuild class C4kMixin(DevopsBuild): def __init__(self, project, config): super().__init__(project, config) - self.c4k_build = C4kBuild(self.build, project, config) - self.c4k_build_service = C4kBuildService() + self.execution_api = ExecutionApi() + c4k_build = C4kBuild(config) + self.repo.set_c4k_build(self.project, c4k_build) + + def update_runtime_config(self, dns_record: DnsRecord): + c4k_build = self.repo.get_c4k_build(self.project) + c4k_build.update_runtime_config(dns_record) + self.repo.set_c4k_build(self.project, c4k_build) def write_c4k_config(self): - self.c4k_build_service.write_c4k_config(self.c4k_build) + build = self.repo.get_build(self.project) + c4k_build = self.repo.get_c4k_build(self.project) + path = build.build_path() + "/out_c4k_config.yaml" + self.file_api.write_yaml_to_file(path, c4k_build.config()) def write_c4k_auth(self): - self.c4k_build_service.write_c4k_auth(self.c4k_build) + build = self.repo.get_build(self.project) + c4k_build = self.repo.get_c4k_build(self.project) + path = build.build_path() + "/out_c4k_auth.yaml" + self.file_api.write_yaml_to_file(path, c4k_build.c4k_mixin_auth) def c4k_apply(self, dry_run=False): - self.c4k_build_service.c4k_apply(self.c4k_build, dry_run) + build = self.repo.get_build(self.project) + c4k_build = self.repo.get_c4k_build(self.project) + return self.execution_api.execute(c4k_build.command(build), dry_run) diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 219b0e2..01a1358 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -2,7 +2,8 @@ import deprecation from .domain import Build from .infrastructure import ProjectRepository, FileApi - +@deprecation.deprecated(deprecated_in="3.2") +# create objects direct instead def create_devops_build_config( stage, project_root_path, module, build_dir_name="target" ): @@ -51,4 +52,5 @@ class DevopsBuild: return build.build_path() def initialize_build_dir(self): + build = self.repo.get_build(self.project) self.file_api.clean_dir(build.build_path()) diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index 3371e35..58b9354 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -2,7 +2,8 @@ from .domain import DockerBuild from .application import DockerBuildService from .devops_build import DevopsBuild, create_devops_build_config - +@deprecation.deprecated(deprecated_in="3.2") +# create objects direct instead def create_devops_docker_build_config( stage, project_root_path, diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 969a15f..17e5bd2 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -18,6 +18,20 @@ class Validateable: return len(self.validate()) < 1 +class DnsRecord(Validateable): + def __init__(self, fqdn, ipv4=None, ipv6=None): + self.fqdn = fqdn + self.ipv4 = ipv4 + self.ipv6 = ipv6 + + def validate(self) -> List[str]: + result = [] + result += self.__validate_is_not_empty__("fqdn") + if (not self.ipv4) and (not self.ipv6): + result.append("ipv4 & ipv6 may not both be empty.") + return result + + class Build(Validateable): def __init__(self, config): self.stage = config["stage"] @@ -37,12 +51,6 @@ class Build(Validateable): path = [self.project_root_path, self.build_dir_name, self.name, self.module] return "/".join(filter_none(path)) - # TODO: these functions should be located at TerraformBuild later on. - def update_runtime_config(self, fqdn, ipv4, ipv6): - self.__put__("fqdn", fqdn) - self.__put__("ipv4", ipv4) - self.__put__("ipv6", ipv6) - def __put__(self, key, value): self.stack[key] = value @@ -57,7 +65,7 @@ class Build(Validateable): class DockerBuild(Validateable): - def __init__(self, config): + def __init__(self, config: map): self.dockerhub_user = config["dockerhub_user"] self.dockerhub_password = config["dockerhub_password"] self.use_package_common_files = config["use_package_common_files"] @@ -71,31 +79,37 @@ class DockerBuild(Validateable): class C4kBuild(Validateable): - def __init__(self, build: Build, project, config): - self.build = build + def __init__(self, config: map): tmp_executabel_name = config["C4kMixin"]["executabel_name"] if not tmp_executabel_name: - tmp_executabel_name = self.build.module + tmp_executabel_name = config["module"] self.executabel_name = tmp_executabel_name self.c4k_mixin_config = config["C4kMixin"]["config"] self.c4k_mixin_auth = config["C4kMixin"]["auth"] tmp = self.c4k_mixin_config["mon-cfg"] - tmp.update( - {"cluster-name": self.build.module, "cluster-stage": self.build.stage} - ) + tmp.update({"cluster-name": config["module"], "cluster-stage": config["stage"]}) self.c4k_mixin_config.update({"mon-cfg": tmp}) + self.dns_record = None - def update_runtime_config(self, fqdn, ipv4, ipv6): - self.build.update_runtime_config(fqdn, ipv4, ipv6) + # TODO: these functions should be located at TerraformBuild later on. + def update_runtime_config(self, dns_record: DnsRecord): + self.dns_record = dns_record + + def validate(self) -> List[str]: + result = [] + result += self.__validate_is_not_empty__("fqdn") + if self.dns_record: + result += self.dns_record.validate() + return result def config(self): - fqdn = self.build.__get__("fqdn") + fqdn = self.dns_record.fqdn self.c4k_mixin_config.update({"fqdn": fqdn}) return self.c4k_mixin_config - def command(self): - module = self.build.module - build_path = self.build.build_path() + def command(self, build: Build): + module = build.module + build_path = build.build_path() config_path = f"{build_path}/out_c4k_config.yaml" auth_path = f"{build_path}/out_c4k_auth.yaml" output_path = f"{build_path}/out_{module}.yaml" diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index bfda2b9..9b319e3 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -3,7 +3,7 @@ from sys import stdout from pkg_resources import resource_string from os import chmod import yaml -from .domain import Build, DockerBuild +from .domain import Build, DockerBuild, C4kBuild from .python_util import execute @@ -20,6 +20,12 @@ class ProjectRepository: def set_docker_build(self, project, build: DockerBuild): project.set_property("docker_build", build) + def get_c4k_build(self, project) -> C4kBuild: + return project.get_property("c4k_build") + + def set_c4k_build(self, project, build: C4kBuild): + project.set_property("c4k_build", build) + class ResourceApi: def read_resource(self, path: str) -> bytes: diff --git a/src/test/python/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py index eca6d05..00487af 100644 --- a/src/test/python/test_c4k_mixin.py +++ b/src/test/python/test_c4k_mixin.py @@ -1,5 +1,6 @@ import os from pybuilder.core import Project +from src.main.python.ddadevops.domain import DnsRecord from src.main.python.ddadevops.c4k_mixin import C4kMixin, add_c4k_mixin_config class MyC4kMixin(C4kMixin): @@ -32,14 +33,15 @@ def test_c4k_mixin(tmp_path): mixin.initialize_build_dir() assert mixin.build_path() == f'{tmp_path_str}/{build_dir}/{project_name}/{module_name}' - mixin.build.update_runtime_config('test.de', None, None) + mixin.update_runtime_config(DnsRecord('test.de', ipv6="1::")) + sut = mixin.repo.get_c4k_build(mixin.project) + assert 'fqdn' in sut.config() + assert 'mon-cfg' in sut.config() + assert 'mon-auth' in sut.c4k_mixin_auth mixin.write_c4k_config() - assert 'fqdn' in mixin.c4k_build.config() - assert 'mon-cfg' in mixin.c4k_build.config() assert os.path.exists(f'{mixin.build_path()}/out_c4k_config.yaml') mixin.write_c4k_auth() - assert 'mon-auth' in mixin.c4k_build.c4k_mixin_auth assert os.path.exists(f'{mixin.build_path()}/out_c4k_auth.yaml') \ No newline at end of file diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index 32f45be..d61a0eb 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -1,5 +1,5 @@ from pybuilder.core import Project -from src.main.python.ddadevops.domain import Validateable, C4kBuild, Build +from src.main.python.ddadevops.domain import Validateable, DnsRecord, C4kBuild, Build from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config @@ -41,6 +41,20 @@ def test_validate_with_reason(): assert sut.validate()[0] == "Field 'field' may not be empty." +def test_should_validate_DnsRecord(): + sut = DnsRecord(None) + assert not sut.is_valid() + + sut = DnsRecord('name') + assert not sut.is_valid() + + sut = DnsRecord('name', ipv4='1.2.3.4') + assert sut.is_valid() + + sut = DnsRecord('name', ipv6='1::') + assert sut.is_valid() + + def test_c4k_build_should_update_fqdn(tmp_path): project = Project(str(tmp_path), name="name") project_config = { @@ -63,8 +77,9 @@ def test_c4k_build_should_update_fqdn(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - sut = C4kBuild(Build(project_config), project, project_config) - sut.update_runtime_config("test.de", None, None) + build = Build(project_config) + sut = C4kBuild(project_config) + sut.update_runtime_config(DnsRecord("test.de", ipv6="1::")) assert { "issuer": "staging", @@ -85,8 +100,6 @@ def test_c4k_build_should_update_fqdn(tmp_path): }, } == sut.c4k_mixin_auth - sut.update_runtime_config - def test_c4k_build_should_calculate_command(tmp_path): project = Project(str(tmp_path), name="name") @@ -104,13 +117,14 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - sut = C4kBuild(Build(project_config), project, project_config) + build = Build(project_config) + sut = C4kBuild(project_config) assert ( "c4k-module-standalone.jar " + "/target/name/module/out_c4k_config.yaml " + "/target/name/module/out_c4k_auth.yaml > " + "/target/name/module/out_module.yaml" - == sut.command() + == sut.command(build) ) project_config = { @@ -128,12 +142,13 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - sut = C4kBuild(Build(project_config), project, project_config) + build = Build(project_config) + sut = C4kBuild(project_config) assert ( "c4k-executabel_name-standalone.jar " + "/target/name/module/out_c4k_config.yaml " + "/target/name/module/out_c4k_auth.yaml > " + "/target/name/module/out_module.yaml" - == sut.command() + == sut.command(build) ) From 0717b0b3611355ab4927a7317e871e973dd5d3d0 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 14 Mar 2023 12:04:52 +0100 Subject: [PATCH 153/243] refactoring: rename Build -> Devops --- doc/architecture/Build.md | 16 ---------------- doc/architecture/BuildCreationAndCall.md | 14 +++++++------- doc/architecture/Domain.md | 10 ++++------ src/main/python/ddadevops/__init__.py | 2 +- src/main/python/ddadevops/application.py | 10 +++++----- src/main/python/ddadevops/c4k_mixin.py | 6 +++--- src/main/python/ddadevops/devops_build.py | 12 ++++++------ src/main/python/ddadevops/devops_docker_build.py | 10 +++++----- src/main/python/ddadevops/domain.py | 4 ++-- src/main/python/ddadevops/infrastructure.py | 6 +++--- src/test/python/test_domain.py | 8 ++++---- 11 files changed, 40 insertions(+), 58 deletions(-) diff --git a/doc/architecture/Build.md b/doc/architecture/Build.md index 9ef0024..622fd27 100644 --- a/doc/architecture/Build.md +++ b/doc/architecture/Build.md @@ -92,19 +92,3 @@ classDiagram DevopsBuild <|-- C4kMixin ``` - -# Build Frontend with application and domain - -```mermaid -classDiagram - DevopsBuild <|-- DevopsDockerBuild - DevopsBuild <|-- DevopsTerraformBuild - - DevopsBuild <|-- C4kMixin - - DevopsBuild *-- BuildService - DevopsDockerBuild *-- DockerBuildService - C4kMixin *-- C4kBuildService -``` - -# Domain \ No newline at end of file diff --git a/doc/architecture/BuildCreationAndCall.md b/doc/architecture/BuildCreationAndCall.md index 3c46bde..b7d124b 100644 --- a/doc/architecture/BuildCreationAndCall.md +++ b/doc/architecture/BuildCreationAndCall.md @@ -1,4 +1,4 @@ -# Build Frontend with application and domain +# Devops Frontend with application and domain ```mermaid classDiagram @@ -8,11 +8,11 @@ classDiagram } class ProjectRepository { - get_build(project): Build - set_build(project, build) + get_devops(project): Devops + set_devops(project, build) } - class Build + class Devops class BuildService { do_sth(project, build) @@ -34,8 +34,8 @@ sequenceDiagram MyBuild ->> DevOpsBuild: create_config MyBuild ->> DevOpsBuild: __init__(project, config) activate DevOpsBuild - DevOpsBuild ->> Build: __init__ - DevOpsBuild ->> ProjectRepository: set_build(build) + DevOpsBuild ->> Devops: __init__ + DevOpsBuild ->> ProjectRepository: set_devops(build) deactivate DevOpsBuild ``` @@ -47,7 +47,7 @@ sequenceDiagram activate DevOpsBuild DevOpsBuild ->> BuildService: do_sth(project) activate BuildService - BuildService ->> ProjectRepository: get_build + BuildService ->> ProjectRepository: get_devops BuildService ->> BuildService: do_some_complicated_stuff(build) deactivate BuildService deactivate DevOpsBuild diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md index 91d84e7..36b4f0e 100644 --- a/doc/architecture/Domain.md +++ b/doc/architecture/Domain.md @@ -2,7 +2,7 @@ ```mermaid classDiagram - class Build { + class Devops { stage name project_root_path @@ -31,10 +31,8 @@ classDiagram ```mermaid classDiagram class ProjectRepository { - get_build(project): Build - set_build(project, build) - } - - + get_devops(project): Devops + set_devops(project, build) + } ``` \ No newline at end of file diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index 1299557..09f805e 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -19,7 +19,7 @@ from .devops_terraform_build import DevopsTerraformBuild, create_devops_terrafor from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path -from .domain import Validateable, DnsRecord, Build, DockerBuild, C4kBuild +from .domain import Validateable, DnsRecord, Devops, DockerBuild, C4kBuild from .application import DockerBuildService from .infrastructure import ProjectRepository, ResourceApi, FileApi, DockerApi, ExecutionApi diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 746101b..03d78db 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,4 +1,4 @@ -from .domain import Build, DockerBuild, C4kBuild +from .domain import Devops, DockerBuild, C4kBuild from .infrastructure import FileApi, ResourceApi, DockerApi, ExecutionApi @@ -22,7 +22,7 @@ class DockerBuildService: def __copy_build_resources_from_dir__(self, build: DockerBuild): self.file_api.cp_force(build.docker_build_commons_path(), build.build_path()) - def initialize_build_dir(self, build: Build, docker_build: DockerBuild): + def initialize_build_dir(self, build: Devops, docker_build: DockerBuild): build_path = build.build_path() self.file_api.clean_dir(f"{build_path}/image/resources") if docker_build.use_package_common_files: @@ -32,7 +32,7 @@ class DockerBuildService: self.file_api.cp_recursive("image", build_path) self.file_api.cp_recursive("test", build_path) - def image(self, build: Build): + def image(self, build: Devops): self.docker_api.image(build.name(), build.build_path()) def drun(self, build: DockerBuild): @@ -41,10 +41,10 @@ class DockerBuildService: def dockerhub_login(self, docker_build: DockerBuild): self.docker_api.dockerhub_login(docker_build.dockerhub_user, docker_build.dockerhub_password) - def dockerhub_publish(self, build: Build, docker_build: DockerBuild): + def dockerhub_publish(self, build: Devops, docker_build: DockerBuild): self.docker_api.dockerhub_publish( build.name(), docker_build.dockerhub_user, docker_build.docker_publish_tag ) - def test(self, build: Build): + def test(self, build: Devops): self.docker_api.test(build.name(), build.build_path()) diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index b654787..bfb0592 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -58,18 +58,18 @@ class C4kMixin(DevopsBuild): self.repo.set_c4k_build(self.project, c4k_build) def write_c4k_config(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) c4k_build = self.repo.get_c4k_build(self.project) path = build.build_path() + "/out_c4k_config.yaml" self.file_api.write_yaml_to_file(path, c4k_build.config()) def write_c4k_auth(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) c4k_build = self.repo.get_c4k_build(self.project) path = build.build_path() + "/out_c4k_auth.yaml" self.file_api.write_yaml_to_file(path, c4k_build.c4k_mixin_auth) def c4k_apply(self, dry_run=False): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) c4k_build = self.repo.get_c4k_build(self.project) return self.execution_api.execute(c4k_build.command(build), dry_run) diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 01a1358..97dcbfa 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -1,5 +1,5 @@ import deprecation -from .domain import Build +from .domain import Devops from .infrastructure import ProjectRepository, FileApi @deprecation.deprecated(deprecated_in="3.2") @@ -40,17 +40,17 @@ class DevopsBuild: self.file_api = FileApi() self.repo = ProjectRepository() config.update({"name": project.name}) - build = Build(config) - self.repo.set_build(self.project, build) + build = Devops(config) + self.repo.set_devops(self.project, build) def name(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) return build.name def build_path(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) return build.build_path() def initialize_build_dir(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) self.file_api.clean_dir(build.build_path()) diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index 58b9354..11ca1d6 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -39,16 +39,16 @@ class DevopsDockerBuild(DevopsBuild): def initialize_build_dir(self): super().initialize_build_dir() - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) docker_build = self.repo.get_docker_build(self.project) self.docker_build_service.initialize_build_dir(build, docker_build) def image(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) self.docker_build_service.image(build) def drun(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) self.docker_build_service.drun(build) def dockerhub_login(self): @@ -56,10 +56,10 @@ class DevopsDockerBuild(DevopsBuild): self.docker_build_service.dockerhub_login(docker_build) def dockerhub_publish(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) docker_build = self.repo.get_docker_build(self.project) self.docker_build_service.dockerhub_publish(build, docker_build) def test(self): - build = self.repo.get_build(self.project) + build = self.repo.get_devops(self.project) self.test.dockerhub_publish(build) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 17e5bd2..98c62d5 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -32,7 +32,7 @@ class DnsRecord(Validateable): return result -class Build(Validateable): +class Devops(Validateable): def __init__(self, config): self.stage = config["stage"] self.name = config["name"] @@ -107,7 +107,7 @@ class C4kBuild(Validateable): self.c4k_mixin_config.update({"fqdn": fqdn}) return self.c4k_mixin_config - def command(self, build: Build): + def command(self, build: Devops): module = build.module build_path = build.build_path() config_path = f"{build_path}/out_c4k_config.yaml" diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index 9b319e3..73d1761 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -3,15 +3,15 @@ from sys import stdout from pkg_resources import resource_string from os import chmod import yaml -from .domain import Build, DockerBuild, C4kBuild +from .domain import Devops, DockerBuild, C4kBuild from .python_util import execute class ProjectRepository: - def get_build(self, project) -> Build: + def get_devops(self, project) -> Devops: return project.get_property("build") - def set_build(self, project, build: Build): + def set_devops(self, project, build: Devops): project.set_property("build", build) def get_docker_build(self, project) -> DockerBuild: diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index d61a0eb..360ec7e 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -1,5 +1,5 @@ from pybuilder.core import Project -from src.main.python.ddadevops.domain import Validateable, DnsRecord, C4kBuild, Build +from src.main.python.ddadevops.domain import Validateable, DnsRecord, C4kBuild, Devops from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config @@ -77,7 +77,7 @@ def test_c4k_build_should_update_fqdn(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - build = Build(project_config) + build = Devops(project_config) sut = C4kBuild(project_config) sut.update_runtime_config(DnsRecord("test.de", ipv6="1::")) @@ -117,7 +117,7 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - build = Build(project_config) + build = Devops(project_config) sut = C4kBuild(project_config) assert ( "c4k-module-standalone.jar " @@ -142,7 +142,7 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - build = Build(project_config) + build = Devops(project_config) sut = C4kBuild(project_config) assert ( "c4k-executabel_name-standalone.jar " From 571828f6cd308c50a3766d7574a7bc524096df4a Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 14 Mar 2023 12:07:11 +0100 Subject: [PATCH 154/243] fix tests --- src/main/python/ddadevops/c4k_mixin.py | 1 + src/main/python/ddadevops/devops_docker_build.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index bfb0592..b5469dd 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -1,3 +1,4 @@ +import deprecation from .domain import C4kBuild, DnsRecord from .devops_build import DevopsBuild from .credential import gopass_field_from_path, gopass_password_from_path diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index 11ca1d6..aa800e2 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -1,3 +1,4 @@ +import deprecation from .domain import DockerBuild from .application import DockerBuildService from .devops_build import DevopsBuild, create_devops_build_config From 5ff4a4c9bbe5d519a93c450b88df25d3750f8c73 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 14 Mar 2023 12:22:05 +0100 Subject: [PATCH 155/243] refactoring: rename DockerBuild -> Docker --- doc/architecture/Domain.md | 2 ++ src/main/python/ddadevops/__init__.py | 2 +- src/main/python/ddadevops/application.py | 16 ++++++++-------- src/main/python/ddadevops/devops_docker_build.py | 12 ++++++------ src/main/python/ddadevops/domain.py | 2 +- src/main/python/ddadevops/infrastructure.py | 6 +++--- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md index 36b4f0e..1c6147b 100644 --- a/doc/architecture/Domain.md +++ b/doc/architecture/Domain.md @@ -10,6 +10,8 @@ classDiagram build_dir_name } + class Docker + class C4kBuild { executabel_name c4k_mixin_config diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index 09f805e..5c06c33 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -19,7 +19,7 @@ from .devops_terraform_build import DevopsTerraformBuild, create_devops_terrafor from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path -from .domain import Validateable, DnsRecord, Devops, DockerBuild, C4kBuild +from .domain import Validateable, DnsRecord, Devops, Docker, C4kBuild from .application import DockerBuildService from .infrastructure import ProjectRepository, ResourceApi, FileApi, DockerApi, ExecutionApi diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 03d78db..e284e6e 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,4 +1,4 @@ -from .domain import Devops, DockerBuild, C4kBuild +from .domain import Devops, Docker, C4kBuild from .infrastructure import FileApi, ResourceApi, DockerApi, ExecutionApi @@ -8,21 +8,21 @@ class DockerBuildService: self.resource_api = ResourceApi() self.docker_api = DockerApi() - def __copy_build_resource_file_from_package__(self, build: DockerBuild): + def __copy_build_resource_file_from_package__(self, build: Docker): data = self.resource_api.read_resource( "src/main/resources/docker/" + build.name ) self.file_api.write_data_to_file(build.build_path() + "/" + build.name, data) - def __copy_build_resources_from_package__(self, build: DockerBuild): + def __copy_build_resources_from_package__(self, build: Docker): self.__copy_build_resource_file_from_package__( "image/resources/install_functions.sh" ) - def __copy_build_resources_from_dir__(self, build: DockerBuild): + def __copy_build_resources_from_dir__(self, build: Docker): self.file_api.cp_force(build.docker_build_commons_path(), build.build_path()) - def initialize_build_dir(self, build: Devops, docker_build: DockerBuild): + def initialize_build_dir(self, build: Devops, docker_build: Docker): build_path = build.build_path() self.file_api.clean_dir(f"{build_path}/image/resources") if docker_build.use_package_common_files: @@ -35,13 +35,13 @@ class DockerBuildService: def image(self, build: Devops): self.docker_api.image(build.name(), build.build_path()) - def drun(self, build: DockerBuild): + def drun(self, build: Docker): self.docker_api.drun(build.name()) - def dockerhub_login(self, docker_build: DockerBuild): + def dockerhub_login(self, docker_build: Docker): self.docker_api.dockerhub_login(docker_build.dockerhub_user, docker_build.dockerhub_password) - def dockerhub_publish(self, build: Devops, docker_build: DockerBuild): + def dockerhub_publish(self, build: Devops, docker_build: Docker): self.docker_api.dockerhub_publish( build.name(), docker_build.dockerhub_user, docker_build.docker_publish_tag ) diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index aa800e2..3a5c0a1 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -1,5 +1,5 @@ import deprecation -from .domain import DockerBuild +from .domain import Docker from .application import DockerBuildService from .devops_build import DevopsBuild, create_devops_build_config @@ -35,13 +35,13 @@ class DevopsDockerBuild(DevopsBuild): def __init__(self, project, config): super().__init__(project, config) self.docker_build_service = DockerBuildService() - docker_build = DockerBuild(config) - self.repo.set_docker_build(self.project, docker_build) + docker_build = Docker(config) + self.repo. set_docker(self.project, docker_build) def initialize_build_dir(self): super().initialize_build_dir() build = self.repo.get_devops(self.project) - docker_build = self.repo.get_docker_build(self.project) + docker_build = self.repo. get_docker(self.project) self.docker_build_service.initialize_build_dir(build, docker_build) def image(self): @@ -53,12 +53,12 @@ class DevopsDockerBuild(DevopsBuild): self.docker_build_service.drun(build) def dockerhub_login(self): - docker_build = self.repo.get_docker_build(self.project) + docker_build = self.repo. get_docker(self.project) self.docker_build_service.dockerhub_login(docker_build) def dockerhub_publish(self): build = self.repo.get_devops(self.project) - docker_build = self.repo.get_docker_build(self.project) + docker_build = self.repo. get_docker(self.project) self.docker_build_service.dockerhub_publish(build, docker_build) def test(self): diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 98c62d5..b1dbcaf 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -64,7 +64,7 @@ class Devops(Validateable): return result -class DockerBuild(Validateable): +class Docker(Validateable): def __init__(self, config: map): self.dockerhub_user = config["dockerhub_user"] self.dockerhub_password = config["dockerhub_password"] diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index 73d1761..0b0bb02 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -3,7 +3,7 @@ from sys import stdout from pkg_resources import resource_string from os import chmod import yaml -from .domain import Devops, DockerBuild, C4kBuild +from .domain import Devops, Docker, C4kBuild from .python_util import execute @@ -14,10 +14,10 @@ class ProjectRepository: def set_devops(self, project, build: Devops): project.set_property("build", build) - def get_docker_build(self, project) -> DockerBuild: + def get_docker(self, project) -> Docker: return project.get_property("docker_build") - def set_docker_build(self, project, build: DockerBuild): + def set_docker(self, project, build: Docker): project.set_property("docker_build", build) def get_c4k_build(self, project) -> C4kBuild: From 1bcc8908e9b2b483525b8ca4a2675c4e6d3f416f Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 14 Mar 2023 15:07:47 +0100 Subject: [PATCH 156/243] refactoring: rename C4kBuild -> C4k --- doc/architecture/Build.md | 2 +- doc/architecture/Domain.md | 4 ++-- src/main/python/ddadevops/__init__.py | 2 +- src/main/python/ddadevops/application.py | 2 +- src/main/python/ddadevops/c4k_mixin.py | 16 ++++++++-------- src/main/python/ddadevops/domain.py | 2 +- src/main/python/ddadevops/infrastructure.py | 6 +++--- src/test/python/test_c4k_mixin.py | 2 +- src/test/python/test_domain.py | 8 ++++---- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/doc/architecture/Build.md b/doc/architecture/Build.md index 622fd27..735136b 100644 --- a/doc/architecture/Build.md +++ b/doc/architecture/Build.md @@ -71,7 +71,7 @@ classDiagram } class C4kMixin { - // C4kMixin -> C4kBuild + // C4kMixin -> C4k def write_c4k_config() def write_c4k_auth() c4k_apply(dry_run=False) diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md index 1c6147b..8179192 100644 --- a/doc/architecture/Domain.md +++ b/doc/architecture/Domain.md @@ -12,7 +12,7 @@ classDiagram class Docker - class C4kBuild { + class C4k { executabel_name c4k_mixin_config c4k_mixin_auth @@ -24,7 +24,7 @@ classDiagram ipv6 } - C4kBuild *-- DnsRecord + C4k *-- DnsRecord ``` diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index 5c06c33..c42e550 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -19,7 +19,7 @@ from .devops_terraform_build import DevopsTerraformBuild, create_devops_terrafor from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path -from .domain import Validateable, DnsRecord, Devops, Docker, C4kBuild +from .domain import Validateable, DnsRecord, Devops, Docker, C4k from .application import DockerBuildService from .infrastructure import ProjectRepository, ResourceApi, FileApi, DockerApi, ExecutionApi diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index e284e6e..e2058f6 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,4 +1,4 @@ -from .domain import Devops, Docker, C4kBuild +from .domain import Devops, Docker from .infrastructure import FileApi, ResourceApi, DockerApi, ExecutionApi diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index b5469dd..8999146 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -1,5 +1,5 @@ import deprecation -from .domain import C4kBuild, DnsRecord +from .domain import C4k, DnsRecord from .devops_build import DevopsBuild from .credential import gopass_field_from_path, gopass_password_from_path from .infrastructure import ProjectRepository, FileApi, ExecutionApi @@ -50,27 +50,27 @@ class C4kMixin(DevopsBuild): def __init__(self, project, config): super().__init__(project, config) self.execution_api = ExecutionApi() - c4k_build = C4kBuild(config) - self.repo.set_c4k_build(self.project, c4k_build) + c4k_build = C4k(config) + self.repo.set_c4k(self.project, c4k_build) def update_runtime_config(self, dns_record: DnsRecord): - c4k_build = self.repo.get_c4k_build(self.project) + c4k_build = self.repo.get_c4k(self.project) c4k_build.update_runtime_config(dns_record) - self.repo.set_c4k_build(self.project, c4k_build) + self.repo.set_c4k(self.project, c4k_build) def write_c4k_config(self): build = self.repo.get_devops(self.project) - c4k_build = self.repo.get_c4k_build(self.project) + c4k_build = self.repo.get_c4k(self.project) path = build.build_path() + "/out_c4k_config.yaml" self.file_api.write_yaml_to_file(path, c4k_build.config()) def write_c4k_auth(self): build = self.repo.get_devops(self.project) - c4k_build = self.repo.get_c4k_build(self.project) + c4k_build = self.repo.get_c4k(self.project) path = build.build_path() + "/out_c4k_auth.yaml" self.file_api.write_yaml_to_file(path, c4k_build.c4k_mixin_auth) def c4k_apply(self, dry_run=False): build = self.repo.get_devops(self.project) - c4k_build = self.repo.get_c4k_build(self.project) + c4k_build = self.repo.get_c4k(self.project) return self.execution_api.execute(c4k_build.command(build), dry_run) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index b1dbcaf..8a06bea 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -78,7 +78,7 @@ class Docker(Validateable): return "/".join(filter_none(list)) + "/" -class C4kBuild(Validateable): +class C4k(Validateable): def __init__(self, config: map): tmp_executabel_name = config["C4kMixin"]["executabel_name"] if not tmp_executabel_name: diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index 0b0bb02..9056583 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -3,7 +3,7 @@ from sys import stdout from pkg_resources import resource_string from os import chmod import yaml -from .domain import Devops, Docker, C4kBuild +from .domain import Devops, Docker, C4k from .python_util import execute @@ -20,10 +20,10 @@ class ProjectRepository: def set_docker(self, project, build: Docker): project.set_property("docker_build", build) - def get_c4k_build(self, project) -> C4kBuild: + def get_c4k(self, project) -> C4k: return project.get_property("c4k_build") - def set_c4k_build(self, project, build: C4kBuild): + def set_c4k(self, project, build: C4k): project.set_property("c4k_build", build) diff --git a/src/test/python/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py index 00487af..2f5db38 100644 --- a/src/test/python/test_c4k_mixin.py +++ b/src/test/python/test_c4k_mixin.py @@ -34,7 +34,7 @@ def test_c4k_mixin(tmp_path): assert mixin.build_path() == f'{tmp_path_str}/{build_dir}/{project_name}/{module_name}' mixin.update_runtime_config(DnsRecord('test.de', ipv6="1::")) - sut = mixin.repo.get_c4k_build(mixin.project) + sut = mixin.repo.get_c4k(mixin.project) assert 'fqdn' in sut.config() assert 'mon-cfg' in sut.config() assert 'mon-auth' in sut.c4k_mixin_auth diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index 360ec7e..0f85e8f 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -1,5 +1,5 @@ from pybuilder.core import Project -from src.main.python.ddadevops.domain import Validateable, DnsRecord, C4kBuild, Devops +from src.main.python.ddadevops.domain import Validateable, DnsRecord, C4k, Devops from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config @@ -78,7 +78,7 @@ def test_c4k_build_should_update_fqdn(tmp_path): grafana_cloud_password="password", ) build = Devops(project_config) - sut = C4kBuild(project_config) + sut = C4k(project_config) sut.update_runtime_config(DnsRecord("test.de", ipv6="1::")) assert { @@ -118,7 +118,7 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_password="password", ) build = Devops(project_config) - sut = C4kBuild(project_config) + sut = C4k(project_config) assert ( "c4k-module-standalone.jar " + "/target/name/module/out_c4k_config.yaml " @@ -143,7 +143,7 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_password="password", ) build = Devops(project_config) - sut = C4kBuild(project_config) + sut = C4k(project_config) assert ( "c4k-executabel_name-standalone.jar " + "/target/name/module/out_c4k_config.yaml " From d650f67dbfbb68f63150fe8353e71f8088847192 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 14 Mar 2023 16:11:00 +0100 Subject: [PATCH 157/243] refactor to dedicated parameters --- src/main/python/ddadevops/devops_build.py | 29 +++++++++++-------- src/main/python/ddadevops/domain.py | 14 ++++++---- src/test/python/test_domain.py | 34 +++++++++++++---------- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 97dcbfa..f07868f 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -2,8 +2,8 @@ import deprecation from .domain import Devops from .infrastructure import ProjectRepository, FileApi -@deprecation.deprecated(deprecated_in="3.2") -# create objects direct instead +@deprecation.deprecated(deprecated_in="3.2", +details="create objects direct instead") def create_devops_build_config( stage, project_root_path, module, build_dir_name="target" ): @@ -19,7 +19,7 @@ def create_devops_build_config( def get_devops_build(project): return project.get_property("devops_build") - +@deprecation.deprecated(deprecated_in="3.2") # TODO: Remove from here! def get_tag_from_latest_commit(): try: @@ -35,22 +35,27 @@ def get_tag_from_latest_commit(): class DevopsBuild: - def __init__(self, project, config): + def __init__(self, project, config: map = None, devops: Devops = None): self.project = project self.file_api = FileApi() self.repo = ProjectRepository() config.update({"name": project.name}) - build = Devops(config) - self.repo.set_devops(self.project, build) + if not devops: + devops = Devops(stage = config['stage'], + project_root_path = config['project_root_path'], + module = config['module'], + name =project.name, + build_dir_name=config['build_dir_name']) + self.repo.set_devops(self.project, devops) def name(self): - build = self.repo.get_devops(self.project) - return build.name + devops = self.repo.get_devops(self.project) + return devops.name def build_path(self): - build = self.repo.get_devops(self.project) - return build.build_path() + devops = self.repo.get_devops(self.project) + return devops.build_path() def initialize_build_dir(self): - build = self.repo.get_devops(self.project) - self.file_api.clean_dir(build.build_path()) + devops = self.repo.get_devops(self.project) + self.file_api.clean_dir(devops.build_path()) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 8a06bea..12cafb4 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -33,12 +33,14 @@ class DnsRecord(Validateable): class Devops(Validateable): - def __init__(self, config): - self.stage = config["stage"] - self.name = config["name"] - self.project_root_path = config["project_root_path"] - self.module = config["module"] - self.build_dir_name = config["build_dir_name"] + def __init__(self, stage, project_root_path, module, name=None, build_dir_name="target"): + self.stage = stage + self.name = name + self.project_root_path = project_root_path + self.module = module + if not name: + self.name = module + self.build_dir_name = build_dir_name # Deprecated - no longer use generic stack ... self.stack = {} diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index 0f85e8f..38d9460 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -45,22 +45,28 @@ def test_should_validate_DnsRecord(): sut = DnsRecord(None) assert not sut.is_valid() - sut = DnsRecord('name') + sut = DnsRecord("name") assert not sut.is_valid() - sut = DnsRecord('name', ipv4='1.2.3.4') + sut = DnsRecord("name", ipv4="1.2.3.4") assert sut.is_valid() - sut = DnsRecord('name', ipv6='1::') + sut = DnsRecord("name", ipv6="1::") assert sut.is_valid() -def test_c4k_build_should_update_fqdn(tmp_path): - project = Project(str(tmp_path), name="name") +def test_devops_buildpath(): + sut = Devops( + stage="test", project_root_path="../../..", module="cloud", name="meissa" + ) + assert "../../../target/meissa/cloud" == sut.build_path() + + +def test_c4k_build_should_update_fqdn(): project_config = { "stage": "test", "name": "name", - "project_root_path": str(tmp_path), + "project_root_path": "mypath", "module": "module", "build_dir_name": "target", } @@ -77,7 +83,7 @@ def test_c4k_build_should_update_fqdn(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - build = Devops(project_config) + sut = C4k(project_config) sut.update_runtime_config(DnsRecord("test.de", ipv6="1::")) @@ -101,8 +107,9 @@ def test_c4k_build_should_update_fqdn(tmp_path): } == sut.c4k_mixin_auth -def test_c4k_build_should_calculate_command(tmp_path): - project = Project(str(tmp_path), name="name") +def test_c4k_build_should_calculate_command(): + devops = Devops(stage="test", project_root_path='', + module="module", name="name") project_config = { "stage": "test", "name": "name", @@ -117,14 +124,13 @@ def test_c4k_build_should_calculate_command(tmp_path): grafana_cloud_user="user", grafana_cloud_password="password", ) - build = Devops(project_config) sut = C4k(project_config) assert ( "c4k-module-standalone.jar " + "/target/name/module/out_c4k_config.yaml " + "/target/name/module/out_c4k_auth.yaml > " + "/target/name/module/out_module.yaml" - == sut.command(build) + == sut.command(devops) ) project_config = { @@ -138,17 +144,15 @@ def test_c4k_build_should_calculate_command(tmp_path): project_config, {}, {}, - executabel_name = "executabel_name", + executabel_name="executabel_name", grafana_cloud_user="user", grafana_cloud_password="password", ) - build = Devops(project_config) sut = C4k(project_config) assert ( "c4k-executabel_name-standalone.jar " + "/target/name/module/out_c4k_config.yaml " + "/target/name/module/out_c4k_auth.yaml > " + "/target/name/module/out_module.yaml" - == sut.command(build) + == sut.command(devops) ) - From 7b198c72157d92654aef3e4f2dfcc61af1b2261d Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 14 Mar 2023 16:43:41 +0100 Subject: [PATCH 158/243] add devops_build test --- src/main/python/ddadevops/devops_build.py | 1 - src/test/python/test_devops_build.py | 27 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/test/python/test_devops_build.py diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index f07868f..1d85b5d 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -39,7 +39,6 @@ class DevopsBuild: self.project = project self.file_api = FileApi() self.repo = ProjectRepository() - config.update({"name": project.name}) if not devops: devops = Devops(stage = config['stage'], project_root_path = config['project_root_path'], diff --git a/src/test/python/test_devops_build.py b/src/test/python/test_devops_build.py new file mode 100644 index 0000000..d68749e --- /dev/null +++ b/src/test/python/test_devops_build.py @@ -0,0 +1,27 @@ +import os +from pybuilder.core import Project +from src.main.python.ddadevops.domain import Devops +from src.main.python.ddadevops.devops_build import DevopsBuild + + +class MyDevopsBuild(DevopsBuild): + pass + + +def test_devops_build(tmp_path): + build_dir = "build" + project_name = "testing-project" + module_name = "c4k-test" + tmp_path_str = str(tmp_path) + + project = Project(tmp_path_str, name=project_name) + devops = Devops( + stage="test", + project_root_path=tmp_path_str, + module=module_name, + build_dir_name=build_dir, + ) + + devops_build = DevopsBuild(project, devops=devops) + devops_build.initialize_build_dir() + assert os.path.exists(f"{devops_build.build_path()}") From 4eeaefc1a3ccc5d5ae17e8a60dbbe433b338b1d4 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 14 Mar 2023 18:06:04 +0100 Subject: [PATCH 159/243] refactor to dedicated parameters --- src/main/python/ddadevops/devops_build.py | 18 ++++++++----- .../python/ddadevops/devops_docker_build.py | 24 +++++++++++------ src/main/python/ddadevops/domain.py | 27 +++++++++++++------ 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 1d85b5d..02140db 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -2,8 +2,8 @@ import deprecation from .domain import Devops from .infrastructure import ProjectRepository, FileApi -@deprecation.deprecated(deprecated_in="3.2", -details="create objects direct instead") + +@deprecation.deprecated(deprecated_in="3.2", details="create objects direct instead") def create_devops_build_config( stage, project_root_path, module, build_dir_name="target" ): @@ -14,11 +14,13 @@ def create_devops_build_config( "build_dir_name": build_dir_name, } + @deprecation.deprecated(deprecated_in="3.2") # Do not expose build to outside def get_devops_build(project): return project.get_property("devops_build") + @deprecation.deprecated(deprecated_in="3.2") # TODO: Remove from here! def get_tag_from_latest_commit(): @@ -40,11 +42,13 @@ class DevopsBuild: self.file_api = FileApi() self.repo = ProjectRepository() if not devops: - devops = Devops(stage = config['stage'], - project_root_path = config['project_root_path'], - module = config['module'], - name =project.name, - build_dir_name=config['build_dir_name']) + devops = Devops( + stage=config["stage"], + project_root_path=config["project_root_path"], + module=config["module"], + name=project.name, + build_dir_name=config["build_dir_name"], + ) self.repo.set_devops(self.project, devops) def name(self): diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index 3a5c0a1..bd26788 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -3,8 +3,8 @@ from .domain import Docker from .application import DockerBuildService from .devops_build import DevopsBuild, create_devops_build_config -@deprecation.deprecated(deprecated_in="3.2") -# create objects direct instead + +@deprecation.deprecated(deprecated_in="3.2", details="create objects direct instead") def create_devops_docker_build_config( stage, project_root_path, @@ -32,16 +32,24 @@ def create_devops_docker_build_config( class DevopsDockerBuild(DevopsBuild): - def __init__(self, project, config): + def __init__(self, project, config: map = None, docker: Docker = None): super().__init__(project, config) self.docker_build_service = DockerBuildService() - docker_build = Docker(config) - self.repo. set_docker(self.project, docker_build) + if not docker: + docker = Docker( + dockerhub_user=config["dockerhub_user"], + dockerhub_password=config["dockerhub_password"], + use_package_common_files=config["use_package_common_files"], + build_commons_path=config["build_commons_path"], + docker_build_commons_dir_name=config["docker_build_commons_dir_name"], + docker_publish_tag=config["docker_publish_tag"], + ) + self.repo.set_docker(self.project, docker) def initialize_build_dir(self): super().initialize_build_dir() build = self.repo.get_devops(self.project) - docker_build = self.repo. get_docker(self.project) + docker_build = self.repo.get_docker(self.project) self.docker_build_service.initialize_build_dir(build, docker_build) def image(self): @@ -53,12 +61,12 @@ class DevopsDockerBuild(DevopsBuild): self.docker_build_service.drun(build) def dockerhub_login(self): - docker_build = self.repo. get_docker(self.project) + docker_build = self.repo.get_docker(self.project) self.docker_build_service.dockerhub_login(docker_build) def dockerhub_publish(self): build = self.repo.get_devops(self.project) - docker_build = self.repo. get_docker(self.project) + docker_build = self.repo.get_docker(self.project) self.docker_build_service.dockerhub_publish(build, docker_build) def test(self): diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 12cafb4..5ee3439 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -33,7 +33,9 @@ class DnsRecord(Validateable): class Devops(Validateable): - def __init__(self, stage, project_root_path, module, name=None, build_dir_name="target"): + def __init__( + self, stage, project_root_path, module, name=None, build_dir_name="target" + ): self.stage = stage self.name = name self.project_root_path = project_root_path @@ -67,13 +69,22 @@ class Devops(Validateable): class Docker(Validateable): - def __init__(self, config: map): - self.dockerhub_user = config["dockerhub_user"] - self.dockerhub_password = config["dockerhub_password"] - self.use_package_common_files = config["use_package_common_files"] - self.build_commons_path = config["build_commons_path"] - self.docker_build_commons_dir_name = config["docker_build_commons_dir_name"] - self.docker_publish_tag = config["docker_publish_tag"] + def __init__( + self, + dockerhub_user, + dockerhub_password, + build_dir_name="target", + use_package_common_files=True, + build_commons_path=None, + docker_build_commons_dir_name="docker", + docker_publish_tag=None, + ): + self.dockerhub_user = (dockerhub_user,) + self.dockerhub_password = (dockerhub_password,) + self.use_package_common_files = (use_package_common_files,) + self.build_commons_path = (build_commons_path,) + self.docker_build_commons_dir_name = (docker_build_commons_dir_name,) + self.docker_publish_tag = (docker_publish_tag,) def docker_build_commons_path(self): list = [self.build_commons_path, self.docker_build_commons_dir_name] From 7ba7cce80199380bb3f75ca7376c481fd3f3772e Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 14 Mar 2023 19:11:34 +0100 Subject: [PATCH 160/243] refactor to dedicated parameters --- doc/architecture/Domain.md | 11 +++- src/main/python/ddadevops/application.py | 54 ++++++++++--------- .../python/ddadevops/devops_docker_build.py | 30 +++++------ src/main/python/ddadevops/domain.py | 14 ++--- src/test/python/test_docker_build.py | 24 +++++++++ src/test/python/test_domain.py | 25 +++++++-- 6 files changed, 107 insertions(+), 51 deletions(-) create mode 100644 src/test/python/test_docker_build.py diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md index 8179192..30e348b 100644 --- a/doc/architecture/Domain.md +++ b/doc/architecture/Domain.md @@ -10,7 +10,15 @@ classDiagram build_dir_name } - class Docker + class Docker { + dockerhub_user + dockerhub_password + build_dir_name + use_package_common_files + build_commons_path + docker_build_commons_dir_name + docker_publish_tag + } class C4k { executabel_name @@ -25,6 +33,7 @@ classDiagram } C4k *-- DnsRecord + Docker *-- Devops ``` diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index e2058f6..ae97f07 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -8,43 +8,47 @@ class DockerBuildService: self.resource_api = ResourceApi() self.docker_api = DockerApi() - def __copy_build_resource_file_from_package__(self, build: Docker): - data = self.resource_api.read_resource( - "src/main/resources/docker/" + build.name + def __copy_build_resource_file_from_package__(self, resource_name, docker: Docker): + data = self.resource_api.read_resource(f"../../resources/docker/{resource_name}") + self.file_api.write_data_to_file( + f"{docker.devops.build_path()}/{resource_name}", data ) - self.file_api.write_data_to_file(build.build_path() + "/" + build.name, data) - def __copy_build_resources_from_package__(self, build: Docker): + def __copy_build_resources_from_package__(self, docker: Docker): self.__copy_build_resource_file_from_package__( - "image/resources/install_functions.sh" + "image/resources/install_functions.sh", docker ) - def __copy_build_resources_from_dir__(self, build: Docker): - self.file_api.cp_force(build.docker_build_commons_path(), build.build_path()) + def __copy_build_resources_from_dir__(self, docker: Docker): + self.file_api.cp_force( + docker.docker_build_commons_path(), docker.devops.build_path() + ) - def initialize_build_dir(self, build: Devops, docker_build: Docker): - build_path = build.build_path() + def initialize_build_dir(self, docker: Docker): + build_path = docker.devops.build_path() self.file_api.clean_dir(f"{build_path}/image/resources") - if docker_build.use_package_common_files: - self.__copy_build_resources_from_package__(docker_build) + if docker.use_package_common_files: + self.__copy_build_resources_from_package__(docker) else: - self.__copy_build_resources_from_dir__(docker_build) + self.__copy_build_resources_from_dir__(docker) self.file_api.cp_recursive("image", build_path) self.file_api.cp_recursive("test", build_path) - def image(self, build: Devops): - self.docker_api.image(build.name(), build.build_path()) + def image(self, docker: Docker): + self.docker_api.image(docker.devops.name, docker.devops.build_path()) - def drun(self, build: Docker): - self.docker_api.drun(build.name()) + def drun(self, docker: Devops): + self.docker_api.drun(docker.devops.name) - def dockerhub_login(self, docker_build: Docker): - self.docker_api.dockerhub_login(docker_build.dockerhub_user, docker_build.dockerhub_password) - - def dockerhub_publish(self, build: Devops, docker_build: Docker): - self.docker_api.dockerhub_publish( - build.name(), docker_build.dockerhub_user, docker_build.docker_publish_tag + def dockerhub_login(self, docker: Docker): + self.docker_api.dockerhub_login( + docker.dockerhub_user, docker.dockerhub_password ) - def test(self, build: Devops): - self.docker_api.test(build.name(), build.build_path()) + def dockerhub_publish(self, docker: Docker): + self.docker_api.dockerhub_publish( + docker.devops.name, docker.dockerhub_user, docker.docker_publish_tag + ) + + def test(self, docker: Docker): + self.docker_api.test(docker.devops.name, docker.devops.build_path()) diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index bd26788..5ce1feb 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -33,7 +33,6 @@ def create_devops_docker_build_config( class DevopsDockerBuild(DevopsBuild): def __init__(self, project, config: map = None, docker: Docker = None): - super().__init__(project, config) self.docker_build_service = DockerBuildService() if not docker: docker = Docker( @@ -44,31 +43,32 @@ class DevopsDockerBuild(DevopsBuild): docker_build_commons_dir_name=config["docker_build_commons_dir_name"], docker_publish_tag=config["docker_publish_tag"], ) + super().__init__(project, config=config) + else: + super().__init__(project, devops=docker.devops) self.repo.set_docker(self.project, docker) def initialize_build_dir(self): super().initialize_build_dir() - build = self.repo.get_devops(self.project) - docker_build = self.repo.get_docker(self.project) - self.docker_build_service.initialize_build_dir(build, docker_build) + docker = self.repo.get_docker(self.project) + self.docker_build_service.initialize_build_dir(docker) def image(self): - build = self.repo.get_devops(self.project) - self.docker_build_service.image(build) + docker = self.repo.get_docker(self.project) + self.docker_build_service.image(docker) def drun(self): - build = self.repo.get_devops(self.project) - self.docker_build_service.drun(build) + docker = self.repo.get_docker(self.project) + self.docker_build_service.drun(docker) def dockerhub_login(self): - docker_build = self.repo.get_docker(self.project) - self.docker_build_service.dockerhub_login(docker_build) + docker = self.repo.get_docker(self.project) + self.docker_build_service.dockerhub_login(docker) def dockerhub_publish(self): - build = self.repo.get_devops(self.project) - docker_build = self.repo.get_docker(self.project) - self.docker_build_service.dockerhub_publish(build, docker_build) + docker = self.repo.get_docker(self.project) + self.docker_build_service.dockerhub_publish(docker) def test(self): - build = self.repo.get_devops(self.project) - self.test.dockerhub_publish(build) + docker = self.repo.get_docker(self.project) + self.test.dockerhub_publish(docker) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py index 5ee3439..9542e19 100644 --- a/src/main/python/ddadevops/domain.py +++ b/src/main/python/ddadevops/domain.py @@ -73,18 +73,20 @@ class Docker(Validateable): self, dockerhub_user, dockerhub_password, + devops: Devops, build_dir_name="target", use_package_common_files=True, build_commons_path=None, docker_build_commons_dir_name="docker", docker_publish_tag=None, ): - self.dockerhub_user = (dockerhub_user,) - self.dockerhub_password = (dockerhub_password,) - self.use_package_common_files = (use_package_common_files,) - self.build_commons_path = (build_commons_path,) - self.docker_build_commons_dir_name = (docker_build_commons_dir_name,) - self.docker_publish_tag = (docker_publish_tag,) + self.dockerhub_user = dockerhub_user + self.dockerhub_password = dockerhub_password + self.use_package_common_files = use_package_common_files + self.build_commons_path = build_commons_path + self.docker_build_commons_dir_name = docker_build_commons_dir_name + self.docker_publish_tag = docker_publish_tag + self.devops = devops def docker_build_commons_path(self): list = [self.build_commons_path, self.docker_build_commons_dir_name] diff --git a/src/test/python/test_docker_build.py b/src/test/python/test_docker_build.py new file mode 100644 index 0000000..e1c40b3 --- /dev/null +++ b/src/test/python/test_docker_build.py @@ -0,0 +1,24 @@ +import os +from pybuilder.core import Project +from src.main.python.ddadevops.domain import Docker, Devops +from src.main.python.ddadevops.devops_docker_build import DevopsDockerBuild + + +def test_devops_docker_build(tmp_path): + build_dir = "build" + project_name = "testing-project" + module_name = "docker-test" + tmp_path_str = str(tmp_path) + + project = Project(tmp_path_str, name=project_name) + devops = Devops( + stage="test", + project_root_path=tmp_path_str, + module=module_name, + name=project_name, + ) + docker = Docker(dockerhub_user="user", dockerhub_password="password", devops=devops) + + docker_build = DevopsDockerBuild(project, docker=docker) + # docker_build.initialize_build_dir() + # assert os.path.exists(f"{docker_build.build_path()}") diff --git a/src/test/python/test_domain.py b/src/test/python/test_domain.py index 38d9460..1a5b1f8 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/test_domain.py @@ -1,5 +1,11 @@ from pybuilder.core import Project -from src.main.python.ddadevops.domain import Validateable, DnsRecord, C4k, Devops +from src.main.python.ddadevops.domain import ( + Validateable, + DnsRecord, + C4k, + Devops, + Docker, +) from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config @@ -62,6 +68,18 @@ def test_devops_buildpath(): assert "../../../target/meissa/cloud" == sut.build_path() +def test_devops_build_commons_path(): + devops = Devops( + stage="test", project_root_path="../../..", module="cloud", name="meissa" + ) + sut = Docker( + dockerhub_user="user", + dockerhub_password="password", + devops = devops, + ) + assert "docker/" == sut.docker_build_commons_path() + + def test_c4k_build_should_update_fqdn(): project_config = { "stage": "test", @@ -83,7 +101,7 @@ def test_c4k_build_should_update_fqdn(): grafana_cloud_user="user", grafana_cloud_password="password", ) - + sut = C4k(project_config) sut.update_runtime_config(DnsRecord("test.de", ipv6="1::")) @@ -108,8 +126,7 @@ def test_c4k_build_should_update_fqdn(): def test_c4k_build_should_calculate_command(): - devops = Devops(stage="test", project_root_path='', - module="module", name="name") + devops = Devops(stage="test", project_root_path="", module="module", name="name") project_config = { "stage": "test", "name": "name", From 162815b5ba49866ce1555a416d0ff1890e5a807d Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 17 Mar 2023 09:22:05 +0100 Subject: [PATCH 161/243] Import local files instead of modules of the same name These modules don't exist and caused our tests to fail --- src/test/python/release_mixin/mock_infrastructure.py | 5 +++-- src/test/python/release_mixin/test_infrastructure.py | 4 ++-- src/test/python/release_mixin/test_infrastructure_api.py | 3 ++- src/test/python/release_mixin/test_release_mixin.py | 3 ++- src/test/python/release_mixin/test_services.py | 5 +++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/test/python/release_mixin/mock_infrastructure.py b/src/test/python/release_mixin/mock_infrastructure.py index 8750308..cb0fd5f 100644 --- a/src/test/python/release_mixin/mock_infrastructure.py +++ b/src/test/python/release_mixin/mock_infrastructure.py @@ -1,7 +1,8 @@ -from mock_domain import MockRelease, MockVersion -from mock_infrastructure_api import MockGitApi from src.main.python.ddadevops.release_mixin.domain import ReleaseType +from .mock_domain import MockRelease, MockVersion +from .mock_infrastructure_api import MockGitApi + class MockVersionRepository(): def __init__(self): diff --git a/src/test/python/release_mixin/test_infrastructure.py b/src/test/python/release_mixin/test_infrastructure.py index f200186..2a6242b 100644 --- a/src/test/python/release_mixin/test_infrastructure.py +++ b/src/test/python/release_mixin/test_infrastructure.py @@ -1,7 +1,7 @@ from src.main.python.ddadevops.release_mixin.domain import ReleaseType from src.main.python.ddadevops.release_mixin.infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository -from mock_infrastructure_api import MockGitApi -from helper import Helper +from .mock_infrastructure_api import MockGitApi +from .helper import Helper def test_version_repository(tmp_path): # init diff --git a/src/test/python/release_mixin/test_infrastructure_api.py b/src/test/python/release_mixin/test_infrastructure_api.py index 2c2b8ae..d5b54dc 100644 --- a/src/test/python/release_mixin/test_infrastructure_api.py +++ b/src/test/python/release_mixin/test_infrastructure_api.py @@ -1,11 +1,12 @@ from pathlib import Path -from helper import Helper import pytest as pt from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi from src.main.python.ddadevops.release_mixin.infrastructure import VersionRepository from src.main.python.ddadevops.release_mixin.domain import ReleaseType +from .helper import Helper + def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): monkeypatch.chdir(tmp_path) diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index ff6663e..4bebfa1 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -1,5 +1,4 @@ import pytest as pt -from helper import Helper from pathlib import Path from ddadevops import * from pybuilder.core import Project @@ -7,6 +6,8 @@ from pybuilder.core import Project from src.main.python.ddadevops.release_mixin.release_mixin import ReleaseMixin, create_release_mixin_config from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi +from .helper import Helper + MAIN_BRANCH = 'main' STAGE = 'test' PROJECT_ROOT_PATH = '.' diff --git a/src/test/python/release_mixin/test_services.py b/src/test/python/release_mixin/test_services.py index 7323fd5..8d7526d 100644 --- a/src/test/python/release_mixin/test_services.py +++ b/src/test/python/release_mixin/test_services.py @@ -1,6 +1,7 @@ from src.main.python.ddadevops.release_mixin.services import PrepareReleaseService, TagAndPushReleaseService -from mock_infrastructure import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository -from mock_infrastructure_api import MockGitApi + +from .mock_infrastructure import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository +from .mock_infrastructure_api import MockGitApi def test_prepare_release_service(): # todo: maybe use mocks for service api tests # init From 85250f1c5fe462301a8d564ff7fc5822c0a987ee Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 17 Mar 2023 11:29:25 +0100 Subject: [PATCH 162/243] Change inconsistent api naming --- src/main/python/ddadevops/release_mixin/infrastructure_api.py | 4 ++-- src/test/python/release_mixin/helper.py | 4 ++-- src/test/python/release_mixin/mock_infrastructure_api.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index dcb5aa2..ba6da17 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -167,7 +167,7 @@ class ClojureFileHandler(FileHandler): clj_file.truncate() -class SystemAPI(): +class SystemApi(): def __init__(self): self.stdout = [""] @@ -192,7 +192,7 @@ class SystemAPI(): class GitApi(): def __init__(self): - self.system_api = SystemAPI() + self.system_api = SystemApi() def get_latest_n_commits(self, n: int): self.system_api.run_checked( diff --git a/src/test/python/release_mixin/helper.py b/src/test/python/release_mixin/helper.py index 1c22f30..251703a 100644 --- a/src/test/python/release_mixin/helper.py +++ b/src/test/python/release_mixin/helper.py @@ -1,5 +1,5 @@ from pathlib import Path -from src.main.python.ddadevops.release_mixin.infrastructure_api import SystemAPI +from src.main.python.ddadevops.release_mixin.infrastructure_api import SystemApi class Helper(): @@ -9,5 +9,5 @@ class Helper(): self.TEST_FILE_PATH = self.TEST_FILE_ROOT / self.TEST_FILE_NAME def copy_files(self, source: Path, target: Path): - api = SystemAPI() + api = SystemApi() api.run_checked('cp', source, target) \ No newline at end of file diff --git a/src/test/python/release_mixin/mock_infrastructure_api.py b/src/test/python/release_mixin/mock_infrastructure_api.py index f4abed2..8306f54 100644 --- a/src/test/python/release_mixin/mock_infrastructure_api.py +++ b/src/test/python/release_mixin/mock_infrastructure_api.py @@ -1,4 +1,4 @@ -class MockSystemAPI(): +class MockSystemApi(): def __init__(self): self.stdout = [""] @@ -14,7 +14,7 @@ class MockSystemAPI(): class MockGitApi(): def __init__(self, commit_string = ""): - self.system_api = MockSystemAPI() + self.system_api = MockSystemApi() self.get_latest_commit_count = 0 self.commit_string = commit_string self.tag_annotated_count = 0 From befb5eb653b3c5c2c790622beca9261d0f0bcc2b Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 17 Mar 2023 11:32:01 +0100 Subject: [PATCH 163/243] Add EnvironmentApi --- .../ddadevops/release_mixin/infrastructure_api.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index ba6da17..70e8678 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -3,7 +3,7 @@ import re import subprocess as sub from abc import ABC, abstractmethod from pathlib import Path - +from os import environ class FileHandler(ABC): @@ -235,3 +235,14 @@ class GitApi(): def checkout(self, branch: str): self.system_api.run_checked('git', 'checkout', branch) return self.system_api.stdout + +class EnvironmentApi(): + + def __init__(self): + self.environ = environ + + def get(self, key): + return self.environ[key] + + def set(self, key, value): + self.environ[key] = value \ No newline at end of file From 50faa89f6e93175594f6575cfdd21c8a99586b7f Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 17 Mar 2023 11:52:22 +0100 Subject: [PATCH 164/243] Implement infrastructure for ReleaseTypes from Environment --- .../ddadevops/release_mixin/infrastructure.py | 41 ++++++++++++++-- .../release_mixin/infrastructure_api.py | 2 +- .../ddadevops/release_mixin/release_mixin.py | 2 +- .../release_mixin/mock_infrastructure_api.py | 11 +++++ .../release_mixin/test_infrastructure.py | 47 +++++++++++++++---- 5 files changed, 89 insertions(+), 14 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure.py b/src/main/python/ddadevops/release_mixin/infrastructure.py index d701879..a68977d 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure.py @@ -1,5 +1,5 @@ from .domain import Release, Version, ReleaseType -from .infrastructure_api import FileHandler +from .infrastructure_api import FileHandler, GitApi, EnvironmentApi class VersionRepository(): @@ -31,10 +31,21 @@ class VersionRepository(): return version class ReleaseTypeRepository(): - def __init__(self, git_api, environment_api=None): + def __init__(self, git_api: GitApi, environment_api: EnvironmentApi): self.git_api = git_api + self.environment_api = environment_api - def get_release_type(self): + @classmethod + def from_git(cls, git_api: GitApi): + environment_api = None + return cls(git_api, environment_api) + + @classmethod + def from_environment(cls, environment_api: EnvironmentApi): + git_api = None + return cls(git_api, environment_api) + + def __get_release_type_git(self) -> ReleaseType | None: latest_commit = self.git_api.get_latest_commit() if ReleaseType.MAJOR.name in latest_commit.upper(): @@ -48,6 +59,30 @@ class ReleaseTypeRepository(): else: return None + def __get_release_type_environment(self) -> ReleaseType | None: + release_name = self.environment_api.get('RELEASE_TYPE') + + if release_name is None: + return None + elif ReleaseType.MAJOR.name in release_name.upper(): + return ReleaseType.MAJOR + elif ReleaseType.MINOR.name in release_name.upper(): + return ReleaseType.MINOR + elif ReleaseType.PATCH.name in release_name.upper(): + return ReleaseType.PATCH + elif ReleaseType.SNAPSHOT.name in release_name.upper(): + return ReleaseType.SNAPSHOT + else: + return None + + def get_release_type(self) -> ReleaseType | None: + if self.git_api is not None: + return self.__get_release_type_git() + elif self.environment_api is not None: + return self.__get_release_type_environment() + else: + raise Exception('No valid api passed to ReleaseTypeRepository') + class ReleaseRepository(): def __init__(self, version_repository: VersionRepository, release_type_repository: ReleaseTypeRepository, main_branch: str): self.version_repository = version_repository diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index 70e8678..73a0a20 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -242,7 +242,7 @@ class EnvironmentApi(): self.environ = environ def get(self, key): - return self.environ[key] + return self.environ.get(key) def set(self, key, value): self.environ[key] = value \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/release_mixin.py b/src/main/python/ddadevops/release_mixin/release_mixin.py index 39d002e..07792a9 100644 --- a/src/main/python/ddadevops/release_mixin/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin/release_mixin.py @@ -25,7 +25,7 @@ class ReleaseMixin(DevopsBuild): self.config_file = release_mixin_config['config_file'] self.main_branch = release_mixin_config['main_branch'] self.git_api = GitApi() - self.release_type_repo = ReleaseTypeRepository(self.git_api) # maybe get from env? + self.release_type_repo = ReleaseTypeRepository.from_git(self.git_api) self.version_repo = VersionRepository(self.config_file) self.release_repo = ReleaseRepository(self.version_repo, self.release_type_repo, self.main_branch) diff --git a/src/test/python/release_mixin/mock_infrastructure_api.py b/src/test/python/release_mixin/mock_infrastructure_api.py index 8306f54..843577e 100644 --- a/src/test/python/release_mixin/mock_infrastructure_api.py +++ b/src/test/python/release_mixin/mock_infrastructure_api.py @@ -56,3 +56,14 @@ class MockGitApi(): def checkout(self, branch: str): return " " + +class MockEnvironmentApi(): + + def __init__(self, environ_map): + self.environ = environ_map + + def get(self, name): + return self.environ.get(name) + + def set(self, name, value): + self.environ[name] = value \ No newline at end of file diff --git a/src/test/python/release_mixin/test_infrastructure.py b/src/test/python/release_mixin/test_infrastructure.py index 2a6242b..c0c491a 100644 --- a/src/test/python/release_mixin/test_infrastructure.py +++ b/src/test/python/release_mixin/test_infrastructure.py @@ -1,6 +1,6 @@ from src.main.python.ddadevops.release_mixin.domain import ReleaseType from src.main.python.ddadevops.release_mixin.infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository -from .mock_infrastructure_api import MockGitApi +from .mock_infrastructure_api import MockGitApi, MockEnvironmentApi from .helper import Helper def test_version_repository(tmp_path): @@ -19,7 +19,7 @@ def test_release_repository(tmp_path): th = Helper() th.copy_files( th.TEST_FILE_PATH, tmp_path) version_repo = VersionRepository(th.TEST_FILE_PATH) - release_type_repo = ReleaseTypeRepository(MockGitApi('MINOR test')) + release_type_repo = ReleaseTypeRepository.from_git(MockGitApi('MINOR test')) # test sut = ReleaseRepository(version_repo, release_type_repo, 'main') @@ -28,27 +28,56 @@ def test_release_repository(tmp_path): assert release is not None -def test_release_type_repository(): - sut = ReleaseTypeRepository(MockGitApi('MINOR test')) +def test_release_type_repository_git(): + sut = ReleaseTypeRepository.from_git(MockGitApi('MINOR test')) release_type = sut.get_release_type() assert release_type is ReleaseType.MINOR - sut = ReleaseTypeRepository(MockGitApi('MINOR bla')) + sut = ReleaseTypeRepository.from_git(MockGitApi('MINOR bla')) release_type = sut.get_release_type() assert release_type is ReleaseType.MINOR - sut = ReleaseTypeRepository(MockGitApi('Major bla')) + sut = ReleaseTypeRepository.from_git(MockGitApi('Major bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.MAJOR - sut = ReleaseTypeRepository(MockGitApi('PATCH bla')) + sut = ReleaseTypeRepository.from_git(MockGitApi('PATCH bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.PATCH - sut = ReleaseTypeRepository(MockGitApi('SNAPSHOT bla')) + sut = ReleaseTypeRepository.from_git(MockGitApi('SNAPSHOT bla')) release_type = sut.get_release_type() assert release_type == ReleaseType.SNAPSHOT - sut = ReleaseTypeRepository(MockGitApi('bla')) + sut = ReleaseTypeRepository.from_git(MockGitApi('bla')) release_type = sut.get_release_type() assert release_type == None + +def test_release_type_repository_env(): + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'MINOR test'})) + release_type = sut.get_release_type() + assert release_type is ReleaseType.MINOR + + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'MINOR'})) + release_type = sut.get_release_type() + assert release_type is ReleaseType.MINOR + + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'Major bla'})) + release_type = sut.get_release_type() + assert release_type == ReleaseType.MAJOR + + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'Patch bla'})) + release_type = sut.get_release_type() + assert release_type == ReleaseType.PATCH + + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'Snapshot bla'})) + release_type = sut.get_release_type() + assert release_type == ReleaseType.SNAPSHOT + + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'Random text'})) + release_type = sut.get_release_type() + assert release_type == None + + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'REL_TYPE': 'Not the right variable'})) + release_type = sut.get_release_type() + assert release_type == None \ No newline at end of file From e3e486d8e36029b7abdeb73b01b671fee222865b Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Thu, 16 Mar 2023 13:15:39 +0100 Subject: [PATCH 165/243] adjust build for first dev-package --- build.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/build.py b/build.py index 404fcbb..696332d 100644 --- a/build.py +++ b/build.py @@ -33,7 +33,7 @@ summary = "tools to support builds combining gopass, terraform, dda-pallet, aws description = __doc__ authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")] url = "https://github.com/DomainDrivenArchitecture/dda-devops-build" -requires_python = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3,!=3.4" # CHECK IF NEW VERSION EXISTS +requires_python = ">=3.8" # CHECK IF NEW VERSION EXISTS license = "Apache Software License" @init @@ -43,7 +43,7 @@ def initialize(project): project.set_property("verbose", True) project.get_property("filter_resources_glob").append("main/python/ddadevops/__init__.py") - #project.set_property("dir_source_unittest_python", "src/unittest/python") + project.set_property("dir_source_unittest_python", "src/test/python") project.set_property("copy_resources_target", "$dir_dist/ddadevops") project.get_property("copy_resources_glob").append("LICENSE") @@ -60,12 +60,9 @@ def initialize(project): project.set_property("distutils_classifiers", [ 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.10', 'Operating System :: POSIX :: Linux', 'Operating System :: OS Independent', 'Development Status :: 5 - Production/Stable', From 3058be19233a6f41563346913c579f1fdc5e8440 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 17 Mar 2023 16:04:47 +0100 Subject: [PATCH 166/243] describe dev setup --- doc/dev_setup.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/dev_setup.md b/doc/dev_setup.md index a80a707..2ff1391 100644 --- a/doc/dev_setup.md +++ b/doc/dev_setup.md @@ -1,6 +1,15 @@ + + +# For local development ``` python3 -m venv ~/.venv --upgrade source ~/.venv/bin/activate -pip3 install --upgrade pybuilder deprecation dda_python_terraform boto3 +pip3 install --upgrade -r dev_requirements.txt +pip3 install --upgrade -r requirements.txt +``` + +# For testing a dev version +``` +pyb publish upload pip3 install --upgrade ddadevops --pre ``` \ No newline at end of file From b63993e61e9bd82ca78c37c0692da2c9d4b4b8f4 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 17 Mar 2023 16:05:17 +0100 Subject: [PATCH 167/243] test should work on local generated image --- infrastructure/clojure/test/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/clojure/test/Dockerfile b/infrastructure/clojure/test/Dockerfile index 2971b72..9b42892 100644 --- a/infrastructure/clojure/test/Dockerfile +++ b/infrastructure/clojure/test/Dockerfile @@ -1,4 +1,4 @@ -FROM domaindrivenarchitecture/clojure +FROM clojure RUN apt update RUN apt -yqq --no-install-recommends --yes install curl default-jre-headless From 51304f3b85d1119186ef1008b3344df7b3af8fb3 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 17 Mar 2023 16:05:39 +0100 Subject: [PATCH 168/243] docker_build now works --- build.py | 2 +- infrastructure/clojure/build.py | 44 +++++++----- src/main/python/ddadevops/application.py | 2 +- src/main/python/ddadevops/devops_build.py | 5 +- .../python/ddadevops/devops_docker_build.py | 2 +- src/main/python/ddadevops/infrastructure.py | 67 +++++++++++-------- src/main/python/ddadevops/python_util.py | 5 +- 7 files changed, 72 insertions(+), 55 deletions(-) diff --git a/build.py b/build.py index 696332d..dd24c1a 100644 --- a/build.py +++ b/build.py @@ -28,7 +28,7 @@ use_plugin("python.distutils") default_task = "publish" name = "ddadevops" -version = "3.2.0-dev" +version = "4.0.0-dev9" summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud" description = __doc__ authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")] diff --git a/infrastructure/clojure/build.py b/infrastructure/clojure/build.py index be4a788..786a61a 100644 --- a/infrastructure/clojure/build.py +++ b/infrastructure/clojure/build.py @@ -4,30 +4,39 @@ from pybuilder.core import task, init from ddadevops import * import logging -name = 'clojure' -MODULE = 'docker' -PROJECT_ROOT_PATH = '../..' +name = "clojure" +MODULE = "docker" +PROJECT_ROOT_PATH = "../.." -class MyBuild(DevopsDockerBuild): - pass - @init def initialize(project): - project.build_depends_on('ddadevops>=0.13.0') - stage = 'notused' - dockerhub_user = environ.get('DOCKERHUB_USER') + project.build_depends_on("ddadevops>=4.0.0-dev") + stage = "notused" + dockerhub_user = environ.get("DOCKERHUB_USER") if not dockerhub_user: - dockerhub_user = gopass_field_from_path('meissa/web/docker.com', 'login') - dockerhub_password = environ.get('DOCKERHUB_PASSWORD') + dockerhub_user = gopass_field_from_path("meissa/web/docker.com", "login") + dockerhub_password = environ.get("DOCKERHUB_PASSWORD") if not dockerhub_password: - dockerhub_password = gopass_password_from_path('meissa/web/docker.com') - tag = environ.get('CI_COMMIT_TAG') + dockerhub_password = gopass_password_from_path("meissa/web/docker.com") + tag = environ.get("CI_COMMIT_TAG") if not tag: tag = get_tag_from_latest_commit() - config = create_devops_docker_build_config( - stage, PROJECT_ROOT_PATH, MODULE, dockerhub_user, dockerhub_password, docker_publish_tag=tag) - build = MyBuild(project, config) + + devops = Devops( + stage=stage, + project_root_path=PROJECT_ROOT_PATH, + module=MODULE, + name=name, + ) + docker = Docker( + dockerhub_user=dockerhub_user, + dockerhub_password=dockerhub_password, + docker_publish_tag=tag, + devops=devops, + ) + + build = DevopsDockerBuild(project, docker=docker) build.initialize_build_dir() @@ -36,16 +45,19 @@ def image(project): build = get_devops_build(project) build.image() + @task def drun(project): build = get_devops_build(project) build.drun() + @task def test(project): build = get_devops_build(project) build.test() + @task def publish(project): build = get_devops_build(project) diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index ae97f07..9a7b7a4 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -9,7 +9,7 @@ class DockerBuildService: self.docker_api = DockerApi() def __copy_build_resource_file_from_package__(self, resource_name, docker: Docker): - data = self.resource_api.read_resource(f"../../resources/docker/{resource_name}") + data = self.resource_api.read_resource(f"src/main/resources/docker/{resource_name}") self.file_api.write_data_to_file( f"{docker.devops.build_path()}/{resource_name}", data ) diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 02140db..4f88a0e 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -1,4 +1,5 @@ import deprecation +from subprocess import run, CalledProcessError from .domain import Devops from .infrastructure import ProjectRepository, FileApi @@ -14,9 +15,6 @@ def create_devops_build_config( "build_dir_name": build_dir_name, } - -@deprecation.deprecated(deprecated_in="3.2") -# Do not expose build to outside def get_devops_build(project): return project.get_property("devops_build") @@ -50,6 +48,7 @@ class DevopsBuild: build_dir_name=config["build_dir_name"], ) self.repo.set_devops(self.project, devops) + self.repo.set_build(self.project, self) def name(self): devops = self.repo.get_devops(self.project) diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_docker_build.py index 5ce1feb..9f5c262 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_docker_build.py @@ -71,4 +71,4 @@ class DevopsDockerBuild(DevopsBuild): def test(self): docker = self.repo.get_docker(self.project) - self.test.dockerhub_publish(docker) + self.docker_build_service.test(docker) diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index 9056583..bf1fe49 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -2,22 +2,26 @@ from pathlib import Path from sys import stdout from pkg_resources import resource_string from os import chmod +from subprocess import run import yaml from .domain import Devops, Docker, C4k -from .python_util import execute +from .python_util import execute, execute_live class ProjectRepository: + def set_build(self, project, build): + project.set_property("devops_build", build) + def get_devops(self, project) -> Devops: return project.get_property("build") def set_devops(self, project, build: Devops): project.set_property("build", build) - def get_docker(self, project) -> Docker: + def get_docker(self, project) -> Docker: return project.get_property("docker_build") - def set_docker(self, project, build: Docker): + def set_docker(self, project, build: Docker): project.set_property("docker_build", build) def get_c4k(self, project) -> C4k: @@ -55,49 +59,54 @@ class FileApi: class DockerApi: def image(self, name: str, path: Path): - execute( - "docker build -t " - + name - + " --file " - + path - + "/image/Dockerfile " - + path - + "/image", + run( + f"docker build -t {name} --file {path}/image/Dockerfile {path}/image", shell=True, + check=True, ) def drun(self, name: str): - execute('docker run -it --entrypoint="" ' + name + " /bin/bash", shell=True) + run( + f'docker run -it --entrypoint="{name}" /bin/bash', + shell=True, + check=True, + ) def dockerhub_login(self, username: str, password: str): - execute( - "docker login --username " + username + " --password " + password, + run( + f"docker login --username {username} --password {password}", shell=True, + check=True, ) def dockerhub_publish(self, name: str, username: str, tag=None): if tag is not None: - execute( - "docker tag " + name + " " + username + "/" + name + ":" + tag, + run( + f"docker tag {name} {username} /{name}:{tag}", shell=True, + check=True, ) - execute("docker push " + username + "/" + name + ":" + tag, shell=True) - execute( - "docker tag " + name + " " + username + "/" + name + ":latest", shell=True + run( + f"docker push {username} / {name}:{tag}", + shell=True, + check=True, + ) + run( + f"docker tag {name} {username}/{name} :latest", + shell=True, + check=True, + ) + run( + f"docker push {username}/{name}:latest", + shell=True, + check=True, ) - execute("docker push " + username + "/" + name + ":latest", shell=True) def test(self, name: str, path: Path): - execute( - "docker build -t " - + name - + "-test " - + "--file " - + path - + "/test/Dockerfile " - + path - + "/test", + run( + f"docker build -t {name} -test --file {path}/test/Dockerfile {path}/test", shell=True, + check=True, ) diff --git a/src/main/python/ddadevops/python_util.py b/src/main/python/ddadevops/python_util.py index e90f7e3..3b06fee 100644 --- a/src/main/python/ddadevops/python_util.py +++ b/src/main/python/ddadevops/python_util.py @@ -2,10 +2,7 @@ from subprocess import check_output, Popen, PIPE import sys def execute(cmd, shell=False): - if sys.version_info.major == 3: - output = check_output(cmd, encoding='UTF-8', shell=shell) - else: - output = check_output(cmd, shell=shell) + output = check_output(cmd, encoding='UTF-8', shell=shell) return output.rstrip() def execute_live(cmd): From a4fbeaadd99db44752e36ad494eb05dc8025864a Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 17 Mar 2023 16:16:47 +0100 Subject: [PATCH 169/243] docker_build now works --- build.py | 2 +- src/main/python/ddadevops/infrastructure.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.py b/build.py index dd24c1a..9bc0bc7 100644 --- a/build.py +++ b/build.py @@ -28,7 +28,7 @@ use_plugin("python.distutils") default_task = "publish" name = "ddadevops" -version = "4.0.0-dev9" +version = "4.0.0-dev11" summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud" description = __doc__ authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")] diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index bf1fe49..7e41bcd 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -67,7 +67,7 @@ class DockerApi: def drun(self, name: str): run( - f'docker run -it --entrypoint="{name}" /bin/bash', + f"docker run -it --entrypoint=\"\" {name} /bin/bash", shell=True, check=True, ) @@ -82,17 +82,17 @@ class DockerApi: def dockerhub_publish(self, name: str, username: str, tag=None): if tag is not None: run( - f"docker tag {name} {username} /{name}:{tag}", + f"docker tag {name} {username}/{name}:{tag}", shell=True, check=True, ) run( - f"docker push {username} / {name}:{tag}", + f"docker push {username}/{name}:{tag}", shell=True, check=True, ) run( - f"docker tag {name} {username}/{name} :latest", + f"docker tag {name} {username}/{name}:latest", shell=True, check=True, ) From 325c9d477f62d21356f118f20233458ed4d10fd6 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 17 Mar 2023 17:27:36 +0100 Subject: [PATCH 170/243] rename docker -> image & introduce domain sub-ns --- README.md | 2 +- build.py | 2 +- doc/architecture/Build.md | 4 +- doc/architecture/Domain.md | 4 +- doc/example/50_docker_module/build.py | 2 +- infrastructure/clojure/build.py | 6 +- infrastructure/devops-build/build.py | 46 +++--- src/main/python/ddadevops/__init__.py | 6 +- src/main/python/ddadevops/application.py | 24 ++-- ..._docker_build.py => devops_image_build.py} | 42 +++--- src/main/python/ddadevops/domain.py | 131 ------------------ src/main/python/ddadevops/domain/__init__.py | 3 + src/main/python/ddadevops/domain/c4k.py | 44 ++++++ src/main/python/ddadevops/domain/common.py | 69 +++++++++ src/main/python/ddadevops/domain/image.py | 29 ++++ src/main/python/ddadevops/infrastructure.py | 8 +- src/test/python/domain/__init__.py | 0 src/test/python/{ => domain}/test_domain.py | 8 +- src/test/python/test_devops_build.py | 2 +- ...st_docker_build.py => test_image_build.py} | 8 +- 20 files changed, 230 insertions(+), 210 deletions(-) rename src/main/python/ddadevops/{devops_docker_build.py => devops_image_build.py} (61%) delete mode 100644 src/main/python/ddadevops/domain.py create mode 100644 src/main/python/ddadevops/domain/__init__.py create mode 100644 src/main/python/ddadevops/domain/c4k.py create mode 100644 src/main/python/ddadevops/domain/common.py create mode 100644 src/main/python/ddadevops/domain/image.py create mode 100644 src/test/python/domain/__init__.py rename src/test/python/{ => domain}/test_domain.py (96%) rename src/test/python/{test_docker_build.py => test_image_build.py} (64%) diff --git a/README.md b/README.md index 0816f51..918b36c 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ def access(project): build.get_mfa_session() ``` -## Feature DdaDockerBuild +## Feature DdaImageBuild The docker build supports image building, tagging, testing and login to dockerhost. For bash based builds we support often used script-parts as predefined functions [see install_functions.sh](src/main/resources/docker/image/resources/install_functions.sh). diff --git a/build.py b/build.py index 9bc0bc7..b767497 100644 --- a/build.py +++ b/build.py @@ -28,7 +28,7 @@ use_plugin("python.distutils") default_task = "publish" name = "ddadevops" -version = "4.0.0-dev11" +version = "4.0.0-dev16" summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud" description = __doc__ authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")] diff --git a/doc/architecture/Build.md b/doc/architecture/Build.md index 735136b..7fea1a2 100644 --- a/doc/architecture/Build.md +++ b/doc/architecture/Build.md @@ -54,7 +54,7 @@ classDiagram init_client(self) } - class DevopsDockerBuild { + class DevopsImageBuild { def initialize_build_dir() image() drun() @@ -77,7 +77,7 @@ classDiagram c4k_apply(dry_run=False) } - DevopsBuild <|-- DevopsDockerBuild + DevopsBuild <|-- DevopsImageBuild DevopsBuild <|-- DevopsTerraformBuild DevopsBuild <|-- AwsRdsPgMixin diff --git a/doc/architecture/Domain.md b/doc/architecture/Domain.md index 30e348b..885362c 100644 --- a/doc/architecture/Domain.md +++ b/doc/architecture/Domain.md @@ -10,7 +10,7 @@ classDiagram build_dir_name } - class Docker { + class Image { dockerhub_user dockerhub_password build_dir_name @@ -33,7 +33,7 @@ classDiagram } C4k *-- DnsRecord - Docker *-- Devops + Image *-- Devops ``` diff --git a/doc/example/50_docker_module/build.py b/doc/example/50_docker_module/build.py index 55ae844..84ede50 100644 --- a/doc/example/50_docker_module/build.py +++ b/doc/example/50_docker_module/build.py @@ -5,7 +5,7 @@ name = 'example-project' MODULE = 'docker-module' PROJECT_ROOT_PATH = '../../..' -class MyBuild(DevopsDockerBuild): +class MyBuild(DevopsImageBuild): pass @init diff --git a/infrastructure/clojure/build.py b/infrastructure/clojure/build.py index 786a61a..515334b 100644 --- a/infrastructure/clojure/build.py +++ b/infrastructure/clojure/build.py @@ -1,8 +1,6 @@ -from subprocess import run from os import environ from pybuilder.core import task, init from ddadevops import * -import logging name = "clojure" MODULE = "docker" @@ -29,14 +27,14 @@ def initialize(project): module=MODULE, name=name, ) - docker = Docker( + image = Image( dockerhub_user=dockerhub_user, dockerhub_password=dockerhub_password, docker_publish_tag=tag, devops=devops, ) - build = DevopsDockerBuild(project, docker=docker) + build = DevopsImageBuild(project, image=image) build.initialize_build_dir() diff --git a/infrastructure/devops-build/build.py b/infrastructure/devops-build/build.py index 94ef6a1..488d160 100644 --- a/infrastructure/devops-build/build.py +++ b/infrastructure/devops-build/build.py @@ -1,33 +1,40 @@ -from subprocess import run from os import environ from pybuilder.core import task, init from ddadevops import * -import logging -name = 'devops-build' -MODULE = 'docker' -PROJECT_ROOT_PATH = '../..' +name = "devops-build" +MODULE = "docker" +PROJECT_ROOT_PATH = "../.." -class MyBuild(DevopsDockerBuild): - pass - @init def initialize(project): - project.build_depends_on('ddadevops>=0.13.0') - stage = 'notused' - dockerhub_user = environ.get('DOCKERHUB_USER') + project.build_depends_on("ddadevops>=4.0.0-dev") + stage = "notused" + dockerhub_user = environ.get("DOCKERHUB_USER") if not dockerhub_user: - dockerhub_user = gopass_field_from_path('meissa/web/docker.com', 'login') - dockerhub_password = environ.get('DOCKERHUB_PASSWORD') + dockerhub_user = gopass_field_from_path("meissa/web/docker.com", "login") + dockerhub_password = environ.get("DOCKERHUB_PASSWORD") if not dockerhub_password: - dockerhub_password = gopass_password_from_path('meissa/web/docker.com') - tag = environ.get('CI_COMMIT_TAG') + dockerhub_password = gopass_password_from_path("meissa/web/docker.com") + tag = environ.get("CI_COMMIT_TAG") if not tag: tag = get_tag_from_latest_commit() - config = create_devops_docker_build_config( - stage, PROJECT_ROOT_PATH, MODULE, dockerhub_user, dockerhub_password, docker_publish_tag=tag) - build = MyBuild(project, config) + + devops = Devops( + stage=stage, + project_root_path=PROJECT_ROOT_PATH, + module=MODULE, + name=name, + ) + image = Image( + dockerhub_user=dockerhub_user, + dockerhub_password=dockerhub_password, + docker_publish_tag=tag, + devops=devops, + ) + + build = DevopsImageBuild(project, image=image) build.initialize_build_dir() @@ -36,16 +43,19 @@ def image(project): build = get_devops_build(project) build.image() + @task def drun(project): build = get_devops_build(project) build.drun() + @task def test(project): build = get_devops_build(project) build.test() + @task def publish(project): build = get_devops_build(project) diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index c42e550..b00911c 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -14,13 +14,11 @@ from .exoscale_mixin import ExoscaleMixin, add_exoscale_mixin_config from .digitalocean_backend_properties_mixin import DigitaloceanBackendPropertiesMixin, add_digitalocean_backend_properties_mixin_config from .digitalocean_terraform_build import DigitaloceanTerraformBuild, create_digitalocean_terraform_build_config from .hetzner_mixin import HetznerMixin, add_hetzner_mixin_config -from .devops_docker_build import DevopsDockerBuild, create_devops_docker_build_config +from .devops_image_build import DevopsImageBuild, create_devops_docker_build_config from .devops_terraform_build import DevopsTerraformBuild, create_devops_terraform_build_config from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path -from .domain import Validateable, DnsRecord, Devops, Docker, C4k -from .application import DockerBuildService -from .infrastructure import ProjectRepository, ResourceApi, FileApi, DockerApi, ExecutionApi +from .domain import Validateable, DnsRecord, Devops, Image __version__ = "${version}" diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 9a7b7a4..84e65d2 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,30 +1,30 @@ -from .domain import Devops, Docker -from .infrastructure import FileApi, ResourceApi, DockerApi, ExecutionApi +from .domain import Devops, Image +from .infrastructure import FileApi, ResourceApi, ImageApi, ExecutionApi -class DockerBuildService: +class ImageBuildService: def __init__(self): self.file_api = FileApi() self.resource_api = ResourceApi() - self.docker_api = DockerApi() + self.docker_api = ImageApi() - def __copy_build_resource_file_from_package__(self, resource_name, docker: Docker): + def __copy_build_resource_file_from_package__(self, resource_name, docker: Image): data = self.resource_api.read_resource(f"src/main/resources/docker/{resource_name}") self.file_api.write_data_to_file( f"{docker.devops.build_path()}/{resource_name}", data ) - def __copy_build_resources_from_package__(self, docker: Docker): + def __copy_build_resources_from_package__(self, docker: Image): self.__copy_build_resource_file_from_package__( "image/resources/install_functions.sh", docker ) - def __copy_build_resources_from_dir__(self, docker: Docker): + def __copy_build_resources_from_dir__(self, docker: Image): self.file_api.cp_force( docker.docker_build_commons_path(), docker.devops.build_path() ) - def initialize_build_dir(self, docker: Docker): + def initialize_build_dir(self, docker: Image): build_path = docker.devops.build_path() self.file_api.clean_dir(f"{build_path}/image/resources") if docker.use_package_common_files: @@ -34,21 +34,21 @@ class DockerBuildService: self.file_api.cp_recursive("image", build_path) self.file_api.cp_recursive("test", build_path) - def image(self, docker: Docker): + def image(self, docker: Image): self.docker_api.image(docker.devops.name, docker.devops.build_path()) def drun(self, docker: Devops): self.docker_api.drun(docker.devops.name) - def dockerhub_login(self, docker: Docker): + def dockerhub_login(self, docker: Image): self.docker_api.dockerhub_login( docker.dockerhub_user, docker.dockerhub_password ) - def dockerhub_publish(self, docker: Docker): + def dockerhub_publish(self, docker: Image): self.docker_api.dockerhub_publish( docker.devops.name, docker.dockerhub_user, docker.docker_publish_tag ) - def test(self, docker: Docker): + def test(self, docker: Image): self.docker_api.test(docker.devops.name, docker.devops.build_path()) diff --git a/src/main/python/ddadevops/devops_docker_build.py b/src/main/python/ddadevops/devops_image_build.py similarity index 61% rename from src/main/python/ddadevops/devops_docker_build.py rename to src/main/python/ddadevops/devops_image_build.py index 9f5c262..119bc21 100644 --- a/src/main/python/ddadevops/devops_docker_build.py +++ b/src/main/python/ddadevops/devops_image_build.py @@ -1,6 +1,6 @@ import deprecation -from .domain import Docker -from .application import DockerBuildService +from .domain import Image +from .application import ImageBuildService from .devops_build import DevopsBuild, create_devops_build_config @@ -31,11 +31,11 @@ def create_devops_docker_build_config( return ret -class DevopsDockerBuild(DevopsBuild): - def __init__(self, project, config: map = None, docker: Docker = None): - self.docker_build_service = DockerBuildService() - if not docker: - docker = Docker( +class DevopsImageBuild(DevopsBuild): + def __init__(self, project, config: map = None, image: Image = None): + self.image_build_service = ImageBuildService() + if not image: + image = Image( dockerhub_user=config["dockerhub_user"], dockerhub_password=config["dockerhub_password"], use_package_common_files=config["use_package_common_files"], @@ -45,30 +45,30 @@ class DevopsDockerBuild(DevopsBuild): ) super().__init__(project, config=config) else: - super().__init__(project, devops=docker.devops) - self.repo.set_docker(self.project, docker) + super().__init__(project, devops=image.devops) + self.repo.set_docker(self.project, image) def initialize_build_dir(self): super().initialize_build_dir() - docker = self.repo.get_docker(self.project) - self.docker_build_service.initialize_build_dir(docker) + image = self.repo.get_docker(self.project) + self.image_build_service.initialize_build_dir(image) def image(self): - docker = self.repo.get_docker(self.project) - self.docker_build_service.image(docker) + image = self.repo.get_docker(self.project) + self.image_build_service.image(image) def drun(self): - docker = self.repo.get_docker(self.project) - self.docker_build_service.drun(docker) + image = self.repo.get_docker(self.project) + self.image_build_service.drun(image) def dockerhub_login(self): - docker = self.repo.get_docker(self.project) - self.docker_build_service.dockerhub_login(docker) + image = self.repo.get_docker(self.project) + self.image_build_service.dockerhub_login(image) def dockerhub_publish(self): - docker = self.repo.get_docker(self.project) - self.docker_build_service.dockerhub_publish(docker) + image = self.repo.get_docker(self.project) + self.image_build_service.dockerhub_publish(image) def test(self): - docker = self.repo.get_docker(self.project) - self.docker_build_service.test(docker) + image = self.repo.get_docker(self.project) + self.image_build_service.test(image) diff --git a/src/main/python/ddadevops/domain.py b/src/main/python/ddadevops/domain.py deleted file mode 100644 index 9542e19..0000000 --- a/src/main/python/ddadevops/domain.py +++ /dev/null @@ -1,131 +0,0 @@ -import deprecation -from typing import List -from .python_util import filter_none - - -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."] - else: - return [] - - def validate(self) -> List[str]: - return [] - - def is_valid(self) -> bool: - return len(self.validate()) < 1 - - -class DnsRecord(Validateable): - def __init__(self, fqdn, ipv4=None, ipv6=None): - self.fqdn = fqdn - self.ipv4 = ipv4 - self.ipv6 = ipv6 - - def validate(self) -> List[str]: - result = [] - result += self.__validate_is_not_empty__("fqdn") - if (not self.ipv4) and (not self.ipv6): - result.append("ipv4 & ipv6 may not both be empty.") - return result - - -class Devops(Validateable): - def __init__( - self, stage, project_root_path, module, name=None, build_dir_name="target" - ): - self.stage = stage - self.name = name - self.project_root_path = project_root_path - self.module = module - if not name: - self.name = module - self.build_dir_name = build_dir_name - # Deprecated - no longer use generic stack ... - self.stack = {} - - @deprecation.deprecated(deprecated_in="3.2") - # use .name instead - def name(self): - return self.name - - def build_path(self): - path = [self.project_root_path, self.build_dir_name, self.name, self.module] - return "/".join(filter_none(path)) - - def __put__(self, key, value): - self.stack[key] = value - - def __get__(self, key): - return self.stack[key] - - def __get_keys__(self, keys): - result = {} - for key in keys: - result[key] = self.__get__(key) - return result - - -class Docker(Validateable): - def __init__( - self, - dockerhub_user, - dockerhub_password, - devops: Devops, - build_dir_name="target", - use_package_common_files=True, - build_commons_path=None, - docker_build_commons_dir_name="docker", - docker_publish_tag=None, - ): - self.dockerhub_user = dockerhub_user - self.dockerhub_password = dockerhub_password - self.use_package_common_files = use_package_common_files - self.build_commons_path = build_commons_path - self.docker_build_commons_dir_name = docker_build_commons_dir_name - self.docker_publish_tag = docker_publish_tag - self.devops = devops - - def docker_build_commons_path(self): - list = [self.build_commons_path, self.docker_build_commons_dir_name] - return "/".join(filter_none(list)) + "/" - - -class C4k(Validateable): - def __init__(self, config: map): - tmp_executabel_name = config["C4kMixin"]["executabel_name"] - if not tmp_executabel_name: - tmp_executabel_name = config["module"] - self.executabel_name = tmp_executabel_name - self.c4k_mixin_config = config["C4kMixin"]["config"] - self.c4k_mixin_auth = config["C4kMixin"]["auth"] - tmp = self.c4k_mixin_config["mon-cfg"] - tmp.update({"cluster-name": config["module"], "cluster-stage": config["stage"]}) - self.c4k_mixin_config.update({"mon-cfg": tmp}) - self.dns_record = None - - # TODO: these functions should be located at TerraformBuild later on. - def update_runtime_config(self, dns_record: DnsRecord): - self.dns_record = dns_record - - def validate(self) -> List[str]: - result = [] - result += self.__validate_is_not_empty__("fqdn") - if self.dns_record: - result += self.dns_record.validate() - return result - - def config(self): - fqdn = self.dns_record.fqdn - self.c4k_mixin_config.update({"fqdn": fqdn}) - return self.c4k_mixin_config - - def command(self, build: Devops): - module = build.module - build_path = build.build_path() - config_path = f"{build_path}/out_c4k_config.yaml" - auth_path = f"{build_path}/out_c4k_auth.yaml" - output_path = f"{build_path}/out_{module}.yaml" - return f"c4k-{self.executabel_name}-standalone.jar {config_path} {auth_path} > {output_path}" diff --git a/src/main/python/ddadevops/domain/__init__.py b/src/main/python/ddadevops/domain/__init__.py new file mode 100644 index 0000000..403eaa4 --- /dev/null +++ b/src/main/python/ddadevops/domain/__init__.py @@ -0,0 +1,3 @@ +from .common import Validateable, DnsRecord, Devops +from .image import Image +from .c4k import C4k \ No newline at end of file diff --git a/src/main/python/ddadevops/domain/c4k.py b/src/main/python/ddadevops/domain/c4k.py new file mode 100644 index 0000000..842d480 --- /dev/null +++ b/src/main/python/ddadevops/domain/c4k.py @@ -0,0 +1,44 @@ +import deprecation +from typing import List +from .common import ( + Validateable, + DnsRecord, + Devops, +) + +class C4k(Validateable): + def __init__(self, config: map): + tmp_executabel_name = config["C4kMixin"]["executabel_name"] + if not tmp_executabel_name: + tmp_executabel_name = config["module"] + self.executabel_name = tmp_executabel_name + self.c4k_mixin_config = config["C4kMixin"]["config"] + self.c4k_mixin_auth = config["C4kMixin"]["auth"] + tmp = self.c4k_mixin_config["mon-cfg"] + tmp.update({"cluster-name": config["module"], "cluster-stage": config["stage"]}) + self.c4k_mixin_config.update({"mon-cfg": tmp}) + self.dns_record = None + + # TODO: these functions should be located at TerraformBuild later on. + def update_runtime_config(self, dns_record: DnsRecord): + self.dns_record = dns_record + + def validate(self) -> List[str]: + result = [] + result += self.__validate_is_not_empty__("fqdn") + if self.dns_record: + result += self.dns_record.validate() + return result + + def config(self): + fqdn = self.dns_record.fqdn + self.c4k_mixin_config.update({"fqdn": fqdn}) + return self.c4k_mixin_config + + def command(self, build: Devops): + module = build.module + build_path = build.build_path() + config_path = f"{build_path}/out_c4k_config.yaml" + auth_path = f"{build_path}/out_c4k_auth.yaml" + output_path = f"{build_path}/out_{module}.yaml" + return f"c4k-{self.executabel_name}-standalone.jar {config_path} {auth_path} > {output_path}" diff --git a/src/main/python/ddadevops/domain/common.py b/src/main/python/ddadevops/domain/common.py new file mode 100644 index 0000000..c322845 --- /dev/null +++ b/src/main/python/ddadevops/domain/common.py @@ -0,0 +1,69 @@ +import deprecation +from typing import List + +def filter_none(list_to_filter): + return [x for x in list_to_filter if x is not None] + +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."] + else: + return [] + + def validate(self) -> List[str]: + return [] + + def is_valid(self) -> bool: + return len(self.validate()) < 1 + + +class DnsRecord(Validateable): + def __init__(self, fqdn, ipv4=None, ipv6=None): + self.fqdn = fqdn + self.ipv4 = ipv4 + self.ipv6 = ipv6 + + def validate(self) -> List[str]: + result = [] + result += self.__validate_is_not_empty__("fqdn") + if (not self.ipv4) and (not self.ipv6): + result.append("ipv4 & ipv6 may not both be empty.") + return result + + +class Devops(Validateable): + def __init__( + self, stage, project_root_path, module, name=None, build_dir_name="target" + ): + self.stage = stage + self.name = name + self.project_root_path = project_root_path + self.module = module + if not name: + self.name = module + self.build_dir_name = build_dir_name + # Deprecated - no longer use generic stack ... + self.stack = {} + + @deprecation.deprecated(deprecated_in="3.2") + # use .name instead + def name(self): + return self.name + + def build_path(self): + path = [self.project_root_path, self.build_dir_name, self.name, self.module] + return "/".join(filter_none(path)) + + def __put__(self, key, value): + self.stack[key] = value + + def __get__(self, key): + return self.stack[key] + + def __get_keys__(self, keys): + result = {} + for key in keys: + result[key] = self.__get__(key) + return result diff --git a/src/main/python/ddadevops/domain/image.py b/src/main/python/ddadevops/domain/image.py new file mode 100644 index 0000000..37a23aa --- /dev/null +++ b/src/main/python/ddadevops/domain/image.py @@ -0,0 +1,29 @@ +from .common import ( + filter_none, + Validateable, + Devops, +) + +class Image(Validateable): + def __init__( + self, + dockerhub_user, + dockerhub_password, + devops: Devops, + build_dir_name="target", + use_package_common_files=True, + build_commons_path=None, + docker_build_commons_dir_name="docker", + docker_publish_tag=None, + ): + self.dockerhub_user = dockerhub_user + self.dockerhub_password = dockerhub_password + self.use_package_common_files = use_package_common_files + self.build_commons_path = build_commons_path + self.docker_build_commons_dir_name = docker_build_commons_dir_name + self.docker_publish_tag = docker_publish_tag + self.devops = devops + + def docker_build_commons_path(self): + list = [self.build_commons_path, self.docker_build_commons_dir_name] + return "/".join(filter_none(list)) + "/" diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index 7e41bcd..c4318ee 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -4,7 +4,7 @@ from pkg_resources import resource_string from os import chmod from subprocess import run import yaml -from .domain import Devops, Docker, C4k +from .domain import Devops, Image, C4k from .python_util import execute, execute_live @@ -18,10 +18,10 @@ class ProjectRepository: def set_devops(self, project, build: Devops): project.set_property("build", build) - def get_docker(self, project) -> Docker: + def get_docker(self, project) -> Image: return project.get_property("docker_build") - def set_docker(self, project, build: Docker): + def set_docker(self, project, build: Image): project.set_property("docker_build", build) def get_c4k(self, project) -> C4k: @@ -57,7 +57,7 @@ class FileApi: chmod(path, 0o600) -class DockerApi: +class ImageApi: def image(self, name: str, path: Path): run( f"docker build -t {name} --file {path}/image/Dockerfile {path}/image", diff --git a/src/test/python/domain/__init__.py b/src/test/python/domain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/test/python/test_domain.py b/src/test/python/domain/test_domain.py similarity index 96% rename from src/test/python/test_domain.py rename to src/test/python/domain/test_domain.py index 1a5b1f8..94a44f3 100644 --- a/src/test/python/test_domain.py +++ b/src/test/python/domain/test_domain.py @@ -1,11 +1,11 @@ from pybuilder.core import Project -from src.main.python.ddadevops.domain import ( +from src.main.python.ddadevops.domain.common import ( Validateable, DnsRecord, - C4k, Devops, - Docker, ) +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 @@ -72,7 +72,7 @@ def test_devops_build_commons_path(): devops = Devops( stage="test", project_root_path="../../..", module="cloud", name="meissa" ) - sut = Docker( + sut = Image( dockerhub_user="user", dockerhub_password="password", devops = devops, diff --git a/src/test/python/test_devops_build.py b/src/test/python/test_devops_build.py index d68749e..ca37caa 100644 --- a/src/test/python/test_devops_build.py +++ b/src/test/python/test_devops_build.py @@ -1,6 +1,6 @@ import os from pybuilder.core import Project -from src.main.python.ddadevops.domain import Devops +from src.main.python.ddadevops.domain.common import Devops from src.main.python.ddadevops.devops_build import DevopsBuild diff --git a/src/test/python/test_docker_build.py b/src/test/python/test_image_build.py similarity index 64% rename from src/test/python/test_docker_build.py rename to src/test/python/test_image_build.py index e1c40b3..666c0bc 100644 --- a/src/test/python/test_docker_build.py +++ b/src/test/python/test_image_build.py @@ -1,7 +1,7 @@ import os from pybuilder.core import Project -from src.main.python.ddadevops.domain import Docker, Devops -from src.main.python.ddadevops.devops_docker_build import DevopsDockerBuild +from src.main.python.ddadevops.domain import Image, Devops +from src.main.python.ddadevops.devops_image_build import DevopsImageBuild def test_devops_docker_build(tmp_path): @@ -17,8 +17,8 @@ def test_devops_docker_build(tmp_path): module=module_name, name=project_name, ) - docker = Docker(dockerhub_user="user", dockerhub_password="password", devops=devops) + image = Image(dockerhub_user="user", dockerhub_password="password", devops=devops) - docker_build = DevopsDockerBuild(project, docker=docker) + docker_build = DevopsImageBuild(project, image=image) # docker_build.initialize_build_dir() # assert os.path.exists(f"{docker_build.build_path()}") From b963342d9b02cf9c8450c7ec6b5890c319134e08 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 17 Mar 2023 17:35:08 +0100 Subject: [PATCH 171/243] fix devops-build --- infrastructure/devops-build/image/Dockerfile | 4 ++-- infrastructure/devops-build/test/Dockerfile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/devops-build/image/Dockerfile b/infrastructure/devops-build/image/Dockerfile index ef142bc..8c17d6f 100644 --- a/infrastructure/devops-build/image/Dockerfile +++ b/infrastructure/devops-build/image/Dockerfile @@ -1,7 +1,7 @@ FROM docker:latest RUN set -eux; -RUN apk add --no-cache build-base rust python3 python3-dev py3-pip py3-setuptools py3-wheel libffi-dev openssl-dev cargo; +RUN apk add --no-cache build-base rust python3 python3-dev py3-pip py3-setuptools py3-wheel libffi-dev openssl-dev cargo bash; RUN python3 -m pip install -U pip; -RUN ln -s /usr/bin/python3 /usr/bin/python +#RUN ln -s /usr/bin/python3 /usr/bin/python RUN pip3 install pybuilder ddadevops deprecation dda-python-terraform boto3 mfa; \ No newline at end of file diff --git a/infrastructure/devops-build/test/Dockerfile b/infrastructure/devops-build/test/Dockerfile index c2cdabc..0ad30f6 100644 --- a/infrastructure/devops-build/test/Dockerfile +++ b/infrastructure/devops-build/test/Dockerfile @@ -1,4 +1,4 @@ -FROM domaindrivenarchitecture/devops-build +FROM devops-build RUN apk update RUN apk add curl openjdk8 From 897784ddeadcf2d79e3a06c79b41d5aabad73d6d Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 31 Mar 2023 12:50:33 +0200 Subject: [PATCH 172/243] Fix pytest errors --- src/main/python/ddadevops/release_mixin/infrastructure_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index 73a0a20..d59722e 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -217,7 +217,7 @@ class GitApi(): return ''.join(self.system_api.stdout).rstrip() def init(self): - self.system_api.run_checked('git', 'init') + self.system_api.run_checked('git', 'init', '-b', 'main') def add_file(self, file_path: Path): self.system_api.run_checked('git', 'add', file_path) From 0778805941a937cc5c9038a915ae56a10ce50ad7 Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 31 Mar 2023 12:57:35 +0200 Subject: [PATCH 173/243] use python 3.10 for gitlab-ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 311c43b..a5ed53a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "python:3.8" +image: "python:3.10" before_script: - python --version From e69bb6adb0a9e680d5a5b9a6ecf8c877ecb5c6d7 Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 31 Mar 2023 13:12:48 +0200 Subject: [PATCH 174/243] fix wrong imports --- src/main/python/ddadevops/release_mixin/release_mixin.py | 2 +- src/test/python/release_mixin/test_release_mixin.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/release_mixin.py b/src/main/python/ddadevops/release_mixin/release_mixin.py index 07792a9..318306d 100644 --- a/src/main/python/ddadevops/release_mixin/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin/release_mixin.py @@ -1,4 +1,4 @@ -from ddadevops import DevopsBuild +from ..devops_build import DevopsBuild from .infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository from .infrastructure_api import GitApi from .services import PrepareReleaseService, TagAndPushReleaseService diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index 4bebfa1..f2c9aa5 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -1,6 +1,5 @@ import pytest as pt from pathlib import Path -from ddadevops import * from pybuilder.core import Project from src.main.python.ddadevops.release_mixin.release_mixin import ReleaseMixin, create_release_mixin_config From cfa05beae4ab4ee9eaff05e83b2001b9c1f955fe Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 31 Mar 2023 13:43:26 +0200 Subject: [PATCH 175/243] added method set_user_config to GitApi --- .../python/ddadevops/release_mixin/infrastructure_api.py | 8 ++++++-- src/test/python/release_mixin/test_infrastructure_api.py | 1 + src/test/python/release_mixin/test_release_mixin.py | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index d59722e..b09fd89 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -216,8 +216,12 @@ class GitApi(): self.system_api.run_checked('git', 'branch', '--show-current') return ''.join(self.system_api.stdout).rstrip() - def init(self): - self.system_api.run_checked('git', 'init', '-b', 'main') + def init(self, default_branch: str = "main"): + self.system_api.run_checked('git', 'init', '-b', default_branch) + + def set_user_config(self, email: str, name: str): + self.system_api.run_checked('git', 'config', 'user.email', email) + self.system_api.run_checked('git', 'config', 'user.name', name) def add_file(self, file_path: Path): self.system_api.run_checked('git', 'add', file_path) diff --git a/src/test/python/release_mixin/test_infrastructure_api.py b/src/test/python/release_mixin/test_infrastructure_api.py index d5b54dc..f59651c 100644 --- a/src/test/python/release_mixin/test_infrastructure_api.py +++ b/src/test/python/release_mixin/test_infrastructure_api.py @@ -20,6 +20,7 @@ def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): git_api = GitApi() git_api.init() + git_api.set_user_config("ex.ample@mail.com", "Ex Ample") git_api.add_file(th.TEST_FILE_NAME) git_api.commit("MINOR release") diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index f2c9aa5..6066f88 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -41,6 +41,7 @@ def test_release_mixin(tmp_path: Path, monkeypatch: pt.MonkeyPatch): git_api = GitApi() git_api.init() + git_api.set_user_config("ex.ample@mail.com", "Ex Ample") git_api.add_file(th.TEST_FILE_NAME) git_api.commit("MAJOR release") From 32847914a758d0f047d0ca7a93932fccd6c07424 Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 31 Mar 2023 14:50:14 +0200 Subject: [PATCH 176/243] fix mypy issues --- src/main/python/ddadevops/application.py | 2 +- src/main/python/ddadevops/devops_build.py | 8 ++++++-- src/main/python/ddadevops/devops_image_build.py | 8 ++++++-- src/main/python/ddadevops/domain/c4k.py | 7 +++---- src/main/python/ddadevops/domain/image.py | 1 + src/main/python/ddadevops/infrastructure.py | 2 +- 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 84e65d2..631cab2 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -37,7 +37,7 @@ class ImageBuildService: def image(self, docker: Image): self.docker_api.image(docker.devops.name, docker.devops.build_path()) - def drun(self, docker: Devops): + def drun(self, docker: Image): self.docker_api.drun(docker.devops.name) def dockerhub_login(self, docker: Image): diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 4f88a0e..1db8826 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -1,4 +1,5 @@ import deprecation +from typing import Optional from subprocess import run, CalledProcessError from .domain import Devops from .infrastructure import ProjectRepository, FileApi @@ -35,11 +36,11 @@ def get_tag_from_latest_commit(): class DevopsBuild: - def __init__(self, project, config: map = None, devops: Devops = None): + def __init__(self, project, config: Optional[dict] = None, devops: Optional[Devops] = None): self.project = project self.file_api = FileApi() self.repo = ProjectRepository() - if not devops: + if not devops and config: devops = Devops( stage=config["stage"], project_root_path=config["project_root_path"], @@ -47,6 +48,9 @@ class DevopsBuild: name=project.name, build_dir_name=config["build_dir_name"], ) + else: + raise Exception("Build parameters could not be set!") + self.repo.set_devops(self.project, devops) self.repo.set_build(self.project, self) diff --git a/src/main/python/ddadevops/devops_image_build.py b/src/main/python/ddadevops/devops_image_build.py index 119bc21..520aa57 100644 --- a/src/main/python/ddadevops/devops_image_build.py +++ b/src/main/python/ddadevops/devops_image_build.py @@ -1,4 +1,5 @@ import deprecation +from typing import Optional from .domain import Image from .application import ImageBuildService from .devops_build import DevopsBuild, create_devops_build_config @@ -32,18 +33,21 @@ def create_devops_docker_build_config( class DevopsImageBuild(DevopsBuild): - def __init__(self, project, config: map = None, image: Image = None): + def __init__(self, project, config: Optional[dict] = None, image: Optional[Image] = None): self.image_build_service = ImageBuildService() if not image: + if not config: + raise Exception("Image parameters could not be set.") + super().__init__(project, config=config) image = Image( dockerhub_user=config["dockerhub_user"], dockerhub_password=config["dockerhub_password"], + devops=self.repo.get_devops(project), use_package_common_files=config["use_package_common_files"], build_commons_path=config["build_commons_path"], docker_build_commons_dir_name=config["docker_build_commons_dir_name"], docker_publish_tag=config["docker_publish_tag"], ) - super().__init__(project, config=config) else: super().__init__(project, devops=image.devops) self.repo.set_docker(self.project, image) diff --git a/src/main/python/ddadevops/domain/c4k.py b/src/main/python/ddadevops/domain/c4k.py index 842d480..5ae0cbf 100644 --- a/src/main/python/ddadevops/domain/c4k.py +++ b/src/main/python/ddadevops/domain/c4k.py @@ -1,5 +1,4 @@ -import deprecation -from typing import List +from typing import List, Optional from .common import ( Validateable, DnsRecord, @@ -7,7 +6,7 @@ from .common import ( ) class C4k(Validateable): - def __init__(self, config: map): + def __init__(self, config: dict): tmp_executabel_name = config["C4kMixin"]["executabel_name"] if not tmp_executabel_name: tmp_executabel_name = config["module"] @@ -17,7 +16,7 @@ class C4k(Validateable): tmp = self.c4k_mixin_config["mon-cfg"] tmp.update({"cluster-name": config["module"], "cluster-stage": config["stage"]}) self.c4k_mixin_config.update({"mon-cfg": tmp}) - self.dns_record = None + self.dns_record: Optional[DnsRecord] = None # TODO: these functions should be located at TerraformBuild later on. def update_runtime_config(self, dns_record: DnsRecord): diff --git a/src/main/python/ddadevops/domain/image.py b/src/main/python/ddadevops/domain/image.py index 37a23aa..49ca555 100644 --- a/src/main/python/ddadevops/domain/image.py +++ b/src/main/python/ddadevops/domain/image.py @@ -1,3 +1,4 @@ +from typing import Optional from .common import ( filter_none, Validateable, diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index c4318ee..04b6b3f 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -111,7 +111,7 @@ class ImageApi: class ExecutionApi: - def execute(command: str, dry_run=False): + def execute(self, command: str, dry_run=False): output = "" if dry_run: print(command) From 50a722794c95a20e9187f98a04f7b1cc182c5923 Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 31 Mar 2023 14:59:37 +0200 Subject: [PATCH 177/243] Resolved some pylint issues --- src/main/python/ddadevops/application.py | 4 ++-- src/main/python/ddadevops/c4k_mixin.py | 2 +- src/main/python/ddadevops/devops_build.py | 4 ++-- src/main/python/ddadevops/devops_image_build.py | 2 +- src/main/python/ddadevops/infrastructure.py | 4 ++-- src/main/python/ddadevops/python_util.py | 1 - 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application.py index 631cab2..976aa44 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application.py @@ -1,5 +1,5 @@ -from .domain import Devops, Image -from .infrastructure import FileApi, ResourceApi, ImageApi, ExecutionApi +from .domain import Image +from .infrastructure import FileApi, ResourceApi, ImageApi class ImageBuildService: diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 8999146..61edc8c 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -2,7 +2,7 @@ import deprecation from .domain import C4k, DnsRecord from .devops_build import DevopsBuild from .credential import gopass_field_from_path, gopass_password_from_path -from .infrastructure import ProjectRepository, FileApi, ExecutionApi +from .infrastructure import ExecutionApi @deprecation.deprecated(deprecated_in="3.2") diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 1db8826..497a715 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -1,6 +1,6 @@ -import deprecation from typing import Optional from subprocess import run, CalledProcessError +import deprecation from .domain import Devops from .infrastructure import ProjectRepository, FileApi @@ -50,7 +50,7 @@ class DevopsBuild: ) else: raise Exception("Build parameters could not be set!") - + self.repo.set_devops(self.project, devops) self.repo.set_build(self.project, self) diff --git a/src/main/python/ddadevops/devops_image_build.py b/src/main/python/ddadevops/devops_image_build.py index 520aa57..7ef19f0 100644 --- a/src/main/python/ddadevops/devops_image_build.py +++ b/src/main/python/ddadevops/devops_image_build.py @@ -1,5 +1,5 @@ -import deprecation from typing import Optional +import deprecation from .domain import Image from .application import ImageBuildService from .devops_build import DevopsBuild, create_devops_build_config diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index 04b6b3f..196b076 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure.py @@ -1,11 +1,11 @@ from pathlib import Path from sys import stdout -from pkg_resources import resource_string from os import chmod from subprocess import run +from pkg_resources import resource_string import yaml from .domain import Devops, Image, C4k -from .python_util import execute, execute_live +from .python_util import execute class ProjectRepository: diff --git a/src/main/python/ddadevops/python_util.py b/src/main/python/ddadevops/python_util.py index 3b06fee..204ee4d 100644 --- a/src/main/python/ddadevops/python_util.py +++ b/src/main/python/ddadevops/python_util.py @@ -1,5 +1,4 @@ from subprocess import check_output, Popen, PIPE -import sys def execute(cmd, shell=False): output = check_output(cmd, encoding='UTF-8', shell=shell) From 6ede2528c213c76739f4e53cd8dcca13387dbd8d Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 31 Mar 2023 15:03:49 +0200 Subject: [PATCH 178/243] fixed pytest --- src/main/python/ddadevops/devops_build.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 497a715..bb3a163 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -40,7 +40,8 @@ class DevopsBuild: self.project = project self.file_api = FileApi() self.repo = ProjectRepository() - if not devops and config: + if not devops: + if not config: raise Exception("Build parameters could not be set!") devops = Devops( stage=config["stage"], project_root_path=config["project_root_path"], @@ -48,8 +49,6 @@ class DevopsBuild: name=project.name, build_dir_name=config["build_dir_name"], ) - else: - raise Exception("Build parameters could not be set!") self.repo.set_devops(self.project, devops) self.repo.set_build(self.project, self) From 012a04115a3db2ba400cd66925e313945945eff3 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 11 Apr 2023 15:03:01 +0200 Subject: [PATCH 179/243] Implement test for EnvironmentApi() --- .../python/release_mixin/test_infrastructure_api.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/test/python/release_mixin/test_infrastructure_api.py b/src/test/python/release_mixin/test_infrastructure_api.py index f59651c..c218833 100644 --- a/src/test/python/release_mixin/test_infrastructure_api.py +++ b/src/test/python/release_mixin/test_infrastructure_api.py @@ -1,7 +1,7 @@ from pathlib import Path import pytest as pt -from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi +from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi, EnvironmentApi from src.main.python.ddadevops.release_mixin.infrastructure import VersionRepository from src.main.python.ddadevops.release_mixin.domain import ReleaseType @@ -10,6 +10,16 @@ from .helper import Helper def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): monkeypatch.chdir(tmp_path) +def test_environment_api(): + # init + env_api = EnvironmentApi() + key = "TEST_ENV_KEY" + value = "data" + env_api.set(key, value) + + #test + assert env_api.get(key) == value + def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # init th = Helper() From f51afe8bbcf4e6419bc76d8aacca7fd2029d6cee Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 11 Apr 2023 16:15:17 +0200 Subject: [PATCH 180/243] Add EnvironmentKeys enum class --- src/main/python/ddadevops/release_mixin/domain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/python/ddadevops/release_mixin/domain.py b/src/main/python/ddadevops/release_mixin/domain.py index a95e847..c7cf406 100644 --- a/src/main/python/ddadevops/release_mixin/domain.py +++ b/src/main/python/ddadevops/release_mixin/domain.py @@ -8,6 +8,9 @@ class ReleaseType(Enum): SNAPSHOT = 3 BUMP = None +class EnvironmentKeys(Enum): + DDADEVOPS_RELEASE_TYPE = 0 + class Version(): def __init__(self, id: Path, version_list: list): From 3776116383e9c715bce2b40576b1bd3dd0f22e8f Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 11 Apr 2023 16:16:08 +0200 Subject: [PATCH 181/243] Use EnvironmentKeys, refactor in test accordingly --- .../python/ddadevops/release_mixin/infrastructure.py | 6 +++--- src/test/python/release_mixin/test_infrastructure.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure.py b/src/main/python/ddadevops/release_mixin/infrastructure.py index a68977d..589dce8 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure.py @@ -1,4 +1,4 @@ -from .domain import Release, Version, ReleaseType +from .domain import Release, Version, ReleaseType, EnvironmentKeys from .infrastructure_api import FileHandler, GitApi, EnvironmentApi class VersionRepository(): @@ -33,7 +33,7 @@ class VersionRepository(): class ReleaseTypeRepository(): def __init__(self, git_api: GitApi, environment_api: EnvironmentApi): self.git_api = git_api - self.environment_api = environment_api + self.environment_api = environment_api @classmethod def from_git(cls, git_api: GitApi): @@ -60,7 +60,7 @@ class ReleaseTypeRepository(): return None def __get_release_type_environment(self) -> ReleaseType | None: - release_name = self.environment_api.get('RELEASE_TYPE') + release_name = self.environment_api.get(EnvironmentKeys.DDADEVOPS_RELEASE_TYPE.name) if release_name is None: return None diff --git a/src/test/python/release_mixin/test_infrastructure.py b/src/test/python/release_mixin/test_infrastructure.py index c0c491a..534a56d 100644 --- a/src/test/python/release_mixin/test_infrastructure.py +++ b/src/test/python/release_mixin/test_infrastructure.py @@ -54,27 +54,27 @@ def test_release_type_repository_git(): assert release_type == None def test_release_type_repository_env(): - sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'MINOR test'})) + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'DDADEVOPS_RELEASE_TYPE': 'MINOR test'})) release_type = sut.get_release_type() assert release_type is ReleaseType.MINOR - sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'MINOR'})) + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'DDADEVOPS_RELEASE_TYPE': 'MINOR'})) release_type = sut.get_release_type() assert release_type is ReleaseType.MINOR - sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'Major bla'})) + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'DDADEVOPS_RELEASE_TYPE': 'Major bla'})) release_type = sut.get_release_type() assert release_type == ReleaseType.MAJOR - sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'Patch bla'})) + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'DDADEVOPS_RELEASE_TYPE': 'Patch bla'})) release_type = sut.get_release_type() assert release_type == ReleaseType.PATCH - sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'Snapshot bla'})) + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'DDADEVOPS_RELEASE_TYPE': 'Snapshot bla'})) release_type = sut.get_release_type() assert release_type == ReleaseType.SNAPSHOT - sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'RELEASE_TYPE': 'Random text'})) + sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'DDADEVOPS_RELEASE_TYPE': 'Random text'})) release_type = sut.get_release_type() assert release_type == None From 4667c58dbbc4bbf1059cfab89d276d4069dcdd98 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 11 Apr 2023 16:16:59 +0200 Subject: [PATCH 182/243] Implement priority: env first, then git Add test for this behavior. --- .../ddadevops/release_mixin/release_mixin.py | 11 +++++-- .../release_mixin/test_release_mixin.py | 33 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/release_mixin.py b/src/main/python/ddadevops/release_mixin/release_mixin.py index 318306d..fe89e70 100644 --- a/src/main/python/ddadevops/release_mixin/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin/release_mixin.py @@ -1,7 +1,8 @@ from ..devops_build import DevopsBuild from .infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository -from .infrastructure_api import GitApi +from .infrastructure_api import GitApi, EnvironmentApi from .services import PrepareReleaseService, TagAndPushReleaseService +from .domain import EnvironmentKeys def create_release_mixin_config(config_file, main_branch) -> dict: config = {} @@ -25,7 +26,13 @@ class ReleaseMixin(DevopsBuild): self.config_file = release_mixin_config['config_file'] self.main_branch = release_mixin_config['main_branch'] self.git_api = GitApi() - self.release_type_repo = ReleaseTypeRepository.from_git(self.git_api) + self.environment_api = EnvironmentApi() + self.env_key = EnvironmentKeys.DDADEVOPS_RELEASE_TYPE.name + self.environment_val_set = self.environment_api.get(self.env_key) is not "" and self.environment_api.get(self.env_key) is not None + if (self.environment_val_set): + self.release_type_repo = ReleaseTypeRepository.from_environment(self.environment_api) + else: + self.release_type_repo = ReleaseTypeRepository.from_git(self.git_api) self.version_repo = VersionRepository(self.config_file) self.release_repo = ReleaseRepository(self.version_repo, self.release_type_repo, self.main_branch) diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index 6066f88..9c4cfc3 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -3,7 +3,7 @@ from pathlib import Path from pybuilder.core import Project from src.main.python.ddadevops.release_mixin.release_mixin import ReleaseMixin, create_release_mixin_config -from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi +from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi, EnvironmentApi from .helper import Helper @@ -29,7 +29,7 @@ def initialize(project, CONFIG_FILE): build = MyBuild(project, config) return build -def test_release_mixin(tmp_path: Path, monkeypatch: pt.MonkeyPatch): +def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # init th = Helper() @@ -51,3 +51,32 @@ def test_release_mixin(tmp_path: Path, monkeypatch: pt.MonkeyPatch): # test assert "124.0.1-SNAPSHOT" in release_version.get_version_string() + +def test_release_mixin_environment(tmp_path: Path, monkeypatch: pt.MonkeyPatch): + + # init + th = Helper() + th.copy_files(th.TEST_FILE_PATH, tmp_path) + th.TEST_FILE_PATH = tmp_path / th.TEST_FILE_NAME + + change_test_dir(tmp_path, monkeypatch) + project = Project(tmp_path) + + git_api = GitApi() + git_api.init() + git_api.set_user_config("ex.ample@mail.com", "Ex Ample") + git_api.add_file(th.TEST_FILE_NAME) + git_api.commit("Commit Message") + + environment_api = EnvironmentApi() + environment_api.set("DDADEVOPS_RELEASE_TYPE", "MAJOR") + + build = initialize(project, th.TEST_FILE_PATH) + build.prepare_release() + release_version = build.version_repo.get_version() + + # test + assert "124.0.1-SNAPSHOT" in release_version.get_version_string() + + # tear down + environment_api.set("DDADEVOPS_RELEASE_TYPE", "") From 1ba7f01b934cda774a05bd2295020ecad2e25815 Mon Sep 17 00:00:00 2001 From: Clemens Date: Fri, 14 Apr 2023 12:04:10 +0200 Subject: [PATCH 183/243] Added some todos and extended mypy ci-pipeline --- .gitlab-ci.yml | 1 + src/main/python/ddadevops/release_mixin/infrastructure_api.py | 2 +- src/main/python/ddadevops/release_mixin/services.py | 2 ++ src/test/python/release_mixin/test_infrastructure_api.py | 4 ++-- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a5ed53a..22751b0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,6 +22,7 @@ mypy: script: - pip install -r dev_requirements.txt - python -m mypy src/main/python/ddadevops/*.py --ignore-missing-imports + - python -m mypy src/main/python/ddadevops/*/*.py --ignore-missing-imports pylint: stage: lint&test diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index b09fd89..9c524a7 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -22,7 +22,7 @@ class FileHandler(ABC): case _: raise Exception( f'The file type "{config_file_type}" is not implemented') - + # TODO: Attribute is only set in classmethod. Should this be initialized outside of this class? file_handler.config_file_path = file_path file_handler.config_file_type = config_file_type return file_handler diff --git a/src/main/python/ddadevops/release_mixin/services.py b/src/main/python/ddadevops/release_mixin/services.py index 11a2433..b75de96 100644 --- a/src/main/python/ddadevops/release_mixin/services.py +++ b/src/main/python/ddadevops/release_mixin/services.py @@ -10,6 +10,7 @@ class PrepareReleaseService(): self.git_api = GitApi() def __write_and_commit_version(self, version: Version, commit_message: str): + # TODO: isValid is missing self.release.validate(self.release_repo.main_branch) self.release_repo.version_repository.write_file(version.get_version_string()) @@ -30,6 +31,7 @@ class TagAndPushReleaseService(): def tag_release(self, release: Release): annotation = 'v' + release.version.get_version_string() message = 'Release ' + annotation + # TODO: Why is the count a parameter? We always tag the second last commit in this process. self.git_api.tag_annotated(annotation, message, 1) def push_release(self): diff --git a/src/test/python/release_mixin/test_infrastructure_api.py b/src/test/python/release_mixin/test_infrastructure_api.py index c218833..0379d89 100644 --- a/src/test/python/release_mixin/test_infrastructure_api.py +++ b/src/test/python/release_mixin/test_infrastructure_api.py @@ -1,7 +1,7 @@ from pathlib import Path import pytest as pt -from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi, EnvironmentApi +from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi, EnvironmentApi, JsonFileHandler from src.main.python.ddadevops.release_mixin.infrastructure import VersionRepository from src.main.python.ddadevops.release_mixin.domain import ReleaseType @@ -100,4 +100,4 @@ def test_python(tmp_path): repo.write_file(version.get_version_string()) # check - assert '3.1.3-SNAPSHOT' in th.TEST_FILE_PATH.read_text() \ No newline at end of file + assert '3.1.3-SNAPSHOT' in th.TEST_FILE_PATH.read_text() From a253960f52d7623b0769f604ff525034ab925451 Mon Sep 17 00:00:00 2001 From: Mirco Date: Fri, 14 Apr 2023 17:15:17 +0200 Subject: [PATCH 184/243] Add ReleaseMixin to architecture --- doc/architecture/{Build.md => BuildAndMixins.md} | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) rename doc/architecture/{Build.md => BuildAndMixins.md} (91%) diff --git a/doc/architecture/Build.md b/doc/architecture/BuildAndMixins.md similarity index 91% rename from doc/architecture/Build.md rename to doc/architecture/BuildAndMixins.md index 7fea1a2..75090a1 100644 --- a/doc/architecture/Build.md +++ b/doc/architecture/BuildAndMixins.md @@ -1,4 +1,7 @@ -# Build Frontend +# Overview of Build and Mixins + +* Build can be used standalone +* Mixin can be added to Build ```mermaid classDiagram @@ -8,6 +11,7 @@ classDiagram initialize_build_dir() } + class DevopsTerraformBuild { terraform_build_commons_path() project_vars() @@ -63,6 +67,11 @@ classDiagram test() } + class ReleaseMixin { + prepare_release() + tag_and_push_release() + } + class ProvsK3sMixin { // ProvsK3sMixin -> ProvsK3sBuild def update_runtime_config(fqdn, ipv4, ipv6=None) @@ -80,6 +89,7 @@ classDiagram DevopsBuild <|-- DevopsImageBuild DevopsBuild <|-- DevopsTerraformBuild DevopsBuild <|-- AwsRdsPgMixin + DevopsBuild <|-- ReleaseMixin DevopsTerraformBuild <|-- AwsBackendPropertiesMixin DevopsTerraformBuild <|-- DigitaloceanTerraformBuild From d6d668902b64195ae0b33bfd8dccc8797382b10a Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 14 Apr 2023 17:24:15 +0200 Subject: [PATCH 185/243] add review comments --- src/main/python/ddadevops/release_mixin/release_mixin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/release_mixin/release_mixin.py b/src/main/python/ddadevops/release_mixin/release_mixin.py index fe89e70..e9348aa 100644 --- a/src/main/python/ddadevops/release_mixin/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin/release_mixin.py @@ -1,9 +1,12 @@ +# TODO: jem, zam - 2023_04_14: mv file one dir up + from ..devops_build import DevopsBuild from .infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository from .infrastructure_api import GitApi, EnvironmentApi from .services import PrepareReleaseService, TagAndPushReleaseService from .domain import EnvironmentKeys +# TODO: jem, zam - 2023_04_14: use of config fkts is deprecated - use domain object instead (see: devops_image_build.py) def create_release_mixin_config(config_file, main_branch) -> dict: config = {} config.update( @@ -12,6 +15,7 @@ def create_release_mixin_config(config_file, main_branch) -> dict: 'config_file': config_file}}) return config +# TODO: jem, zam - 2023_04_14: use of config fkts is deprecated - use domain object instead (see: devops_image_build.py) def add_versions(config, release_version, bump_version) -> dict: config['ReleaseMixin'].update( {'release_version': release_version, @@ -19,7 +23,7 @@ def add_versions(config, release_version, bump_version) -> dict: return config class ReleaseMixin(DevopsBuild): - + # TODO: jem, zam - 2023_04_14: handover config as domain object def __init__(self, project, config): # todo: create services in init, dont expose repos etc in api super().__init__(project, config) release_mixin_config = config['ReleaseMixin'] @@ -37,11 +41,13 @@ class ReleaseMixin(DevopsBuild): self.release_repo = ReleaseRepository(self.version_repo, self.release_type_repo, self.main_branch) def prepare_release(self): + # TODO: jem, zam - 2023_04_14: instantiate service on object creation? prepare_release_service = PrepareReleaseService(self.release_repo) prepare_release_service.write_and_commit_release() prepare_release_service.write_and_commit_bump() def tag_and_push_release(self): + # TODO: jem, zam - 2023_04_14: instantiate service on object creation? tag_and_push_release_service = TagAndPushReleaseService(self.git_api) tag_and_push_release_service.tag_release(self.release_repo.get_release()) # tag_and_push_release_service.push_release() From d4d49970ec9e9a0d501c03c9678d81bf74333ded Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 14 Apr 2023 17:48:12 +0200 Subject: [PATCH 186/243] reviwe --- doc/architecture/Architecture.md | 20 +++++++++++++++++++ .../ddadevops/release_mixin/infrastructure.py | 2 ++ .../release_mixin/infrastructure_api.py | 1 + .../ddadevops/release_mixin/services.py | 2 ++ 4 files changed, 25 insertions(+) create mode 100644 doc/architecture/Architecture.md diff --git a/doc/architecture/Architecture.md b/doc/architecture/Architecture.md new file mode 100644 index 0000000..3412691 --- /dev/null +++ b/doc/architecture/Architecture.md @@ -0,0 +1,20 @@ +# Architecture + + +```mermaid + C4Context + title Architectrue od dda-devops-build + + Component(buildAndMixin, "Build and Mixin", "") + Component(app, "Application", "") + Component(dom, "Domain", "") + Component(infra, "Infrastructure", "") + + Rel(buildAndMixin,app, "use") + Rel(buildAndMixin,dom, "use") + Rel(app, dom, "use") + Rel(app, infra, "use") + + UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="1") + +``` \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/infrastructure.py b/src/main/python/ddadevops/release_mixin/infrastructure.py index 589dce8..d63af7f 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure.py @@ -1,6 +1,8 @@ +# TODO: jem, zam - 2023_04_14: move content to src/main/python/ddadevops/infrastructure.py (or subdir) from .domain import Release, Version, ReleaseType, EnvironmentKeys from .infrastructure_api import FileHandler, GitApi, EnvironmentApi + class VersionRepository(): def __init__(self, file): diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index 9c524a7..3eb0877 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -1,3 +1,4 @@ +# TODO: jem, zam - 2023_04_14: Discuss if we can move more functionality to domain? import json import re import subprocess as sub diff --git a/src/main/python/ddadevops/release_mixin/services.py b/src/main/python/ddadevops/release_mixin/services.py index b75de96..7ad3757 100644 --- a/src/main/python/ddadevops/release_mixin/services.py +++ b/src/main/python/ddadevops/release_mixin/services.py @@ -1,7 +1,9 @@ +# TODO: jem, zam - 2023_04_14: Move this services to application layer from .infrastructure import ReleaseRepository from .infrastructure_api import GitApi from .domain import Version, Release + class PrepareReleaseService(): def __init__(self, release_repo: ReleaseRepository): From 785940d3b61d158a054ad4e8d3ae4090d70a554f Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 18 Apr 2023 08:56:50 +0200 Subject: [PATCH 187/243] add review --- src/main/python/ddadevops/release_mixin/infrastructure_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index 3eb0877..2f62616 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -1,4 +1,4 @@ -# TODO: jem, zam - 2023_04_14: Discuss if we can move more functionality to domain? +# TODO: jem, zam - 2023_04_18: Mv this to an infrastructure namespace? import json import re import subprocess as sub @@ -6,6 +6,7 @@ from abc import ABC, abstractmethod from pathlib import Path from os import environ +# TODO: jem, zam - 2023_04_18: Discuss if we can move more functionality to domain? class FileHandler(ABC): @classmethod From 446ed9744a33b0bbc0ac3a7c0a83dc51f32f57ae Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Tue, 18 Apr 2023 08:57:00 +0200 Subject: [PATCH 188/243] add review --- src/main/python/ddadevops/release_mixin/build.py | 2 ++ src/main/python/ddadevops/release_mixin/config.json | 1 + src/main/python/ddadevops/release_mixin/doc/arch.md | 2 ++ src/main/python/ddadevops/release_mixin/domain.py | 2 ++ src/main/python/ddadevops/release_mixin/infrastructure.py | 2 +- 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/release_mixin/build.py b/src/main/python/ddadevops/release_mixin/build.py index cfedec4..641ae3a 100644 --- a/src/main/python/ddadevops/release_mixin/build.py +++ b/src/main/python/ddadevops/release_mixin/build.py @@ -1,3 +1,5 @@ +# TODO: jem, zam - 2023_04_18: move this to an build-test repo? + import sys import os from pathlib import Path diff --git a/src/main/python/ddadevops/release_mixin/config.json b/src/main/python/ddadevops/release_mixin/config.json index 6068f8d..359f087 100644 --- a/src/main/python/ddadevops/release_mixin/config.json +++ b/src/main/python/ddadevops/release_mixin/config.json @@ -1,3 +1,4 @@ +// TODO: jem, zam - 2023_04_18: move this to an build-test repo ? { "version": "123.125.1-SNAPSHOT" } \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/doc/arch.md b/src/main/python/ddadevops/release_mixin/doc/arch.md index 201223d..d8b63fe 100644 --- a/src/main/python/ddadevops/release_mixin/doc/arch.md +++ b/src/main/python/ddadevops/release_mixin/doc/arch.md @@ -1 +1,3 @@ +# TODO: jem, zam - 2023_04_18: move this to doc/architecture & use markup instead of link + [![](https://mermaid.ink/img/pako:eNrtV99vmzAQ_lcsPzUSrUjIj8JDpUqb9jSpaqs9TJGQg6_UGxhmTNes6v8-E0MCGIem6-MiBYnz3Xd3vu8ulxccZRRwgAv4VQKP4BMjsSApUp81r54CIolEvDmbup6D6sdEn21KllB0fnWFbiEBUsBX9sx4gMKQcSbD8CwX2Q9l76Ao4w8sDh9YArUtiSR7IhI6pge3HWnl4QuT1zlrYU8sirXgfpvDLeRZwWQmti07DVRb50RIFrGccNk2tEB_A1GwjA_Cmhm2sWvL4yEP4ho-neEMHZQSxsONIDx6tGenD4BTo7oO8tV3NVLaXIBChVBoaVOFPc7MrfixcNDCtRXoRkPU8jsQTyyCVsbGbfQZMwigdQaPbHccg-zn0WflQb2TzEF8jHIt_FCqM5uTrl3HUfeo0wgVeqJQChlGWZoyacBrTS2kMCi2u2mdBOjQly2c8eh7kAPtUyXxpMVG-Ia6PjfENuwkI3TXjw3ymy0VhVTJ3mza4m5l46A6ozBhhZwY92bJ6yi1zPaort1psNSALYUALrv9v29zs2p972Pn4wgfMAJ-CyYhJJzWjO5352h3B6jpNxupOmOwfunWMhKgFEMD6FgPjIVnHXlGxo27OpzJO8baiS2oQ9iRveFtIQXj8ajvZhIRSs_erNydVeYP0QeyZ1Om-QnUqdSHyn06d2xI_4nzYcRpXeWRdWBPr4GFpyLaym3xzLbySBLvLjovi8fDRDqyqt6T-JrTG6Vu3XE6S-g-E5vhu3wNh61hrI7GIU9BakqnQ-GZVEnSf4zh5HSaICrDAfbYbG0du7v6HqmqJ3ZwCkLt4FT9m3qpJGssHyGFNVadhSkRP9d4zV-VHilldrflEQ4eSFKAg8ucKg_1X6-e9DOt2m0vVLv89yxTSlKU-hUHL_gZB-fT6cWlt_Td5dzz1ACdL30Hb5Xcny4uZjN_7k2ni5k39y5fHfxnBzG7cD135fnzxdJfrObu6vUveFPSvA?type=png)](https://mermaid.live/edit#pako:eNrtV99vmzAQ_lcsPzUSrUjIj8JDpUqb9jSpaqs9TJGQg6_UGxhmTNes6v8-E0MCGIem6-MiBYnz3Xd3vu8ulxccZRRwgAv4VQKP4BMjsSApUp81r54CIolEvDmbup6D6sdEn21KllB0fnWFbiEBUsBX9sx4gMKQcSbD8CwX2Q9l76Ao4w8sDh9YArUtiSR7IhI6pge3HWnl4QuT1zlrYU8sirXgfpvDLeRZwWQmti07DVRb50RIFrGccNk2tEB_A1GwjA_Cmhm2sWvL4yEP4ho-neEMHZQSxsONIDx6tGenD4BTo7oO8tV3NVLaXIBChVBoaVOFPc7MrfixcNDCtRXoRkPU8jsQTyyCVsbGbfQZMwigdQaPbHccg-zn0WflQb2TzEF8jHIt_FCqM5uTrl3HUfeo0wgVeqJQChlGWZoyacBrTS2kMCi2u2mdBOjQly2c8eh7kAPtUyXxpMVG-Ia6PjfENuwkI3TXjw3ymy0VhVTJ3mza4m5l46A6ozBhhZwY92bJ6yi1zPaort1psNSALYUALrv9v29zs2p972Pn4wgfMAJ-CyYhJJzWjO5352h3B6jpNxupOmOwfunWMhKgFEMD6FgPjIVnHXlGxo27OpzJO8baiS2oQ9iRveFtIQXj8ajvZhIRSs_erNydVeYP0QeyZ1Om-QnUqdSHyn06d2xI_4nzYcRpXeWRdWBPr4GFpyLaym3xzLbySBLvLjovi8fDRDqyqt6T-JrTG6Vu3XE6S-g-E5vhu3wNh61hrI7GIU9BakqnQ-GZVEnSf4zh5HSaICrDAfbYbG0du7v6HqmqJ3ZwCkLt4FT9m3qpJGssHyGFNVadhSkRP9d4zV-VHilldrflEQ4eSFKAg8ucKg_1X6-e9DOt2m0vVLv89yxTSlKU-hUHL_gZB-fT6cWlt_Td5dzz1ACdL30Hb5Xcny4uZjN_7k2ni5k39y5fHfxnBzG7cD135fnzxdJfrObu6vUveFPSvA) \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/domain.py b/src/main/python/ddadevops/release_mixin/domain.py index c7cf406..4ad8822 100644 --- a/src/main/python/ddadevops/release_mixin/domain.py +++ b/src/main/python/ddadevops/release_mixin/domain.py @@ -1,3 +1,4 @@ +# TODO: jem, zam - 2023_04_18: Mv this to src/main/python/ddadevops/domain/release.py from enum import Enum from pathlib import Path @@ -57,6 +58,7 @@ class Version(): bump_version.increment(ReleaseType.BUMP) return bump_version +# TODO: jem, zam - 2023_04_18: xtend abstract validateable class Release(): def __init__(self, release_type: ReleaseType, version: Version, current_branch: str): self.release_type = release_type diff --git a/src/main/python/ddadevops/release_mixin/infrastructure.py b/src/main/python/ddadevops/release_mixin/infrastructure.py index d63af7f..7adae14 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure.py @@ -1,4 +1,4 @@ -# TODO: jem, zam - 2023_04_14: move content to src/main/python/ddadevops/infrastructure.py (or subdir) +# TODO: jem, zam - 2023_04_18: move content to src/main/python/ddadevops/infrastructure/repo.py (or subdir) from .domain import Release, Version, ReleaseType, EnvironmentKeys from .infrastructure_api import FileHandler, GitApi, EnvironmentApi From fc17d6a14d9b26ae99f7f9c2399b485fc315d2f3 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 10:49:13 +0200 Subject: [PATCH 189/243] Move Documentation of ReleaseMixin --- doc/architecture/ReleaseMixinArchitecture.md | 81 ++++++++++++++++++ .../ddadevops/release_mixin/doc/arch.md | 3 - .../release_mixin/doc/architecture.png.png | Bin 112256 -> 0 bytes 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 doc/architecture/ReleaseMixinArchitecture.md delete mode 100644 src/main/python/ddadevops/release_mixin/doc/arch.md delete mode 100644 src/main/python/ddadevops/release_mixin/doc/architecture.png.png diff --git a/doc/architecture/ReleaseMixinArchitecture.md b/doc/architecture/ReleaseMixinArchitecture.md new file mode 100644 index 0000000..281c2c1 --- /dev/null +++ b/doc/architecture/ReleaseMixinArchitecture.md @@ -0,0 +1,81 @@ +# Architecture of ReleaseMixin + +[Link to live editor](https://mermaid.live/edit#pako:eNrtV99vmzAQ_lcsPzUSrUjIj8JDpUqb9jSpaqs9TJGQg6_UGxhmTNes6v8-E0MCGIem6-MiBYnz3Xd3vu8ulxccZRRwgAv4VQKP4BMjsSApUp81r54CIolEvDmbup6D6sdEn21KllB0fnWFbiEBUsBX9sx4gMKQcSbD8CwX2Q9l76Ao4w8sDh9YArUtiSR7IhI6pge3HWnl4QuT1zlrYU8sirXgfpvDLeRZwWQmti07DVRb50RIFrGccNk2tEB_A1GwjA_Cmhm2sWvL4yEP4ho-neEMHZQSxsONIDx6tGenD4BTo7oO8tV3NVLaXIBChVBoaVOFPc7MrfixcNDCtRXoRkPU8jsQTyyCVsbGbfQZMwigdQaPbHccg-zn0WflQb2TzEF8jHIt_FCqM5uTrl3HUfeo0wgVeqJQChlGWZoyacBrTS2kMCi2u2mdBOjQly2c8eh7kAPtUyXxpMVG-Ia6PjfENuwkI3TXjw3ymy0VhVTJ3mza4m5l46A6ozBhhZwY92bJ6yi1zPaort1psNSALYUALrv9v29zs2p972Pn4wgfMAJ-CyYhJJzWjO5352h3B6jpNxupOmOwfunWMhKgFEMD6FgPjIVnHXlGxo27OpzJO8baiS2oQ9iRveFtIQXj8ajvZhIRSs_erNydVeYP0QeyZ1Om-QnUqdSHyn06d2xI_4nzYcRpXeWRdWBPr4GFpyLaym3xzLbySBLvLjovi8fDRDqyqt6T-JrTG6Vu3XE6S-g-E5vhu3wNh61hrI7GIU9BakqnQ-GZVEnSf4zh5HSaICrDAfbYbG0du7v6HqmqJ3ZwCkLt4FT9m3qpJGssHyGFNVadhSkRP9d4zV-VHilldrflEQ4eSFKAg8ucKg_1X6-e9DOt2m0vVLv89yxTSlKU-hUHL_gZB-fT6cWlt_Td5dzz1ACdL30Hb5Xcny4uZjN_7k2ni5k39y5fHfxnBzG7cD135fnzxdJfrObu6vUveFPSvA) + +```mermaid +sequenceDiagram + rect rgb(103, 103, 10) + build ->> ReleaseMixin: __init__(project, config_file) + activate ReleaseMixin + ReleaseMixin ->> GitApi: __init__() + ReleaseMixin ->> ReleaseTypeRepository: __init__(GitApi) + participant ReleaseType + ReleaseMixin ->> VersionRepository: __init__(config_file) + participant Version + ReleaseMixin ->> ReleaseRepository: __init__(VersionRepository, ReleaseTypeRepository, main_branch) + participant Release + end + rect rgb(10, 90, 7) + build ->> ReleaseMixin: prepare_release() + rect rgb(20, 105, 50) + ReleaseMixin ->> PrepareReleaseService: __init__(ReleaseRepository) + activate PrepareReleaseService + PrepareReleaseService ->> ReleaseRepository: get_release() + activate ReleaseRepository + ReleaseRepository ->> ReleaseTypeRepository: get_release_type() + activate ReleaseTypeRepository + ReleaseTypeRepository ->> GitApi: get_latest_commit() + activate GitApi + deactivate GitApi + ReleaseTypeRepository ->> ReleaseType: + deactivate ReleaseTypeRepository + ReleaseRepository ->> VersionRepository: get_version() + activate VersionRepository + VersionRepository ->> VersionRepository: load_file() + VersionRepository ->> VersionRepository: parse_file() + VersionRepository ->> Version: __init__(file, version_list) + deactivate VersionRepository + ReleaseRepository ->> Release: __init__(ReleaseType, Version, current_branch) + end + deactivate ReleaseRepository + activate ReleaseRepository + deactivate ReleaseRepository + rect rgb(20, 105, 50) + ReleaseMixin ->> PrepareReleaseService: write_and_commit_release() + PrepareReleaseService ->> Release: release_version() + activate Release + Release ->> Version: create_release_version() + deactivate Release + PrepareReleaseService ->> PrepareReleaseService: __write_and_commit_version(Version) + PrepareReleaseService ->> ReleaseRepository: + ReleaseRepository ->> VersionRepository: write_file(version_string) + PrepareReleaseService ->> GitApi: add() + PrepareReleaseService ->> GitApi: commit() + end + rect rgb(20, 105, 50) + ReleaseMixin ->> PrepareReleaseService: write_and_commit_bump() + PrepareReleaseService ->> Release: bump_version() + activate Release + Release ->> Version: create_bump_version() + deactivate Release + PrepareReleaseService ->> PrepareReleaseService: __write_and_commit_version(Version) + PrepareReleaseService ->> ReleaseRepository: + ReleaseRepository ->> VersionRepository: write_file(version_string) + PrepareReleaseService ->> GitApi: add() + PrepareReleaseService ->> GitApi: commit() + deactivate PrepareReleaseService + end + end + rect rgb(120, 70, 50) + build ->> ReleaseMixin: tag_and_push_release() + ReleaseMixin ->> TagAndPushReleaseService: __init__(GitApi) + activate TagAndPushReleaseService + ReleaseMixin ->> TagAndPushReleaseService: tag_and_push_release() + TagAndPushReleaseService ->> TagAndPushReleaseService: tag_release() + TagAndPushReleaseService ->> GitApi: tag_annotated() + TagAndPushReleaseService ->> TagAndPushReleaseService: push_release() + TagAndPushReleaseService ->> GitApi: push() + deactivate TagAndPushReleaseService + deactivate ReleaseMixin + end +``` \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/doc/arch.md b/src/main/python/ddadevops/release_mixin/doc/arch.md deleted file mode 100644 index d8b63fe..0000000 --- a/src/main/python/ddadevops/release_mixin/doc/arch.md +++ /dev/null @@ -1,3 +0,0 @@ -# TODO: jem, zam - 2023_04_18: move this to doc/architecture & use markup instead of link - -[![](https://mermaid.ink/img/pako:eNrtV99vmzAQ_lcsPzUSrUjIj8JDpUqb9jSpaqs9TJGQg6_UGxhmTNes6v8-E0MCGIem6-MiBYnz3Xd3vu8ulxccZRRwgAv4VQKP4BMjsSApUp81r54CIolEvDmbup6D6sdEn21KllB0fnWFbiEBUsBX9sx4gMKQcSbD8CwX2Q9l76Ao4w8sDh9YArUtiSR7IhI6pge3HWnl4QuT1zlrYU8sirXgfpvDLeRZwWQmti07DVRb50RIFrGccNk2tEB_A1GwjA_Cmhm2sWvL4yEP4ho-neEMHZQSxsONIDx6tGenD4BTo7oO8tV3NVLaXIBChVBoaVOFPc7MrfixcNDCtRXoRkPU8jsQTyyCVsbGbfQZMwigdQaPbHccg-zn0WflQb2TzEF8jHIt_FCqM5uTrl3HUfeo0wgVeqJQChlGWZoyacBrTS2kMCi2u2mdBOjQly2c8eh7kAPtUyXxpMVG-Ia6PjfENuwkI3TXjw3ymy0VhVTJ3mza4m5l46A6ozBhhZwY92bJ6yi1zPaort1psNSALYUALrv9v29zs2p972Pn4wgfMAJ-CyYhJJzWjO5352h3B6jpNxupOmOwfunWMhKgFEMD6FgPjIVnHXlGxo27OpzJO8baiS2oQ9iRveFtIQXj8ajvZhIRSs_erNydVeYP0QeyZ1Om-QnUqdSHyn06d2xI_4nzYcRpXeWRdWBPr4GFpyLaym3xzLbySBLvLjovi8fDRDqyqt6T-JrTG6Vu3XE6S-g-E5vhu3wNh61hrI7GIU9BakqnQ-GZVEnSf4zh5HSaICrDAfbYbG0du7v6HqmqJ3ZwCkLt4FT9m3qpJGssHyGFNVadhSkRP9d4zV-VHilldrflEQ4eSFKAg8ucKg_1X6-e9DOt2m0vVLv89yxTSlKU-hUHL_gZB-fT6cWlt_Td5dzz1ACdL30Hb5Xcny4uZjN_7k2ni5k39y5fHfxnBzG7cD135fnzxdJfrObu6vUveFPSvA?type=png)](https://mermaid.live/edit#pako:eNrtV99vmzAQ_lcsPzUSrUjIj8JDpUqb9jSpaqs9TJGQg6_UGxhmTNes6v8-E0MCGIem6-MiBYnz3Xd3vu8ulxccZRRwgAv4VQKP4BMjsSApUp81r54CIolEvDmbup6D6sdEn21KllB0fnWFbiEBUsBX9sx4gMKQcSbD8CwX2Q9l76Ao4w8sDh9YArUtiSR7IhI6pge3HWnl4QuT1zlrYU8sirXgfpvDLeRZwWQmti07DVRb50RIFrGccNk2tEB_A1GwjA_Cmhm2sWvL4yEP4ho-neEMHZQSxsONIDx6tGenD4BTo7oO8tV3NVLaXIBChVBoaVOFPc7MrfixcNDCtRXoRkPU8jsQTyyCVsbGbfQZMwigdQaPbHccg-zn0WflQb2TzEF8jHIt_FCqM5uTrl3HUfeo0wgVeqJQChlGWZoyacBrTS2kMCi2u2mdBOjQly2c8eh7kAPtUyXxpMVG-Ia6PjfENuwkI3TXjw3ymy0VhVTJ3mza4m5l46A6ozBhhZwY92bJ6yi1zPaort1psNSALYUALrv9v29zs2p972Pn4wgfMAJ-CyYhJJzWjO5352h3B6jpNxupOmOwfunWMhKgFEMD6FgPjIVnHXlGxo27OpzJO8baiS2oQ9iRveFtIQXj8ajvZhIRSs_erNydVeYP0QeyZ1Om-QnUqdSHyn06d2xI_4nzYcRpXeWRdWBPr4GFpyLaym3xzLbySBLvLjovi8fDRDqyqt6T-JrTG6Vu3XE6S-g-E5vhu3wNh61hrI7GIU9BakqnQ-GZVEnSf4zh5HSaICrDAfbYbG0du7v6HqmqJ3ZwCkLt4FT9m3qpJGssHyGFNVadhSkRP9d4zV-VHilldrflEQ4eSFKAg8ucKg_1X6-e9DOt2m0vVLv89yxTSlKU-hUHL_gZB-fT6cWlt_Td5dzz1ACdL30Hb5Xcny4uZjN_7k2ni5k39y5fHfxnBzG7cD135fnzxdJfrObu6vUveFPSvA) \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/doc/architecture.png.png b/src/main/python/ddadevops/release_mixin/doc/architecture.png.png deleted file mode 100644 index cef9e94942292c52776003e4850d7b62092a9797..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112256 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 rM;NnK>^x-Z|BY_{zx|~$b$p`!`Oo2kUe Date: Tue, 18 Apr 2023 12:39:02 +0200 Subject: [PATCH 190/243] Sanitize arguments to system calls --- .../python/ddadevops/release_mixin/infrastructure_api.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index 2f62616..908179d 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -175,8 +175,13 @@ class SystemApi(): self.stdout = [""] self.stderr = [""] - def run(self, args): - stream = sub.Popen(args, + def run(self, args: list[str]): + sanitized_args = [] + for arg in args: + str_arg = str(arg) + if str_arg is not "": + sanitized_args.append(str_arg.replace("\n", "")) + stream = sub.Popen(sanitized_args, stdout=sub.PIPE, stderr=sub.PIPE, text=True, From 6dc078877cb194904def6783b4daae2aaa5891a3 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 12:39:39 +0200 Subject: [PATCH 191/243] Move build.py and config.json to resources --- .../release_mixin/config.json => test/resources/alt_config.json} | 0 .../python/ddadevops/release_mixin => test/resources}/build.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{main/python/ddadevops/release_mixin/config.json => test/resources/alt_config.json} (100%) rename src/{main/python/ddadevops/release_mixin => test/resources}/build.py (100%) diff --git a/src/main/python/ddadevops/release_mixin/config.json b/src/test/resources/alt_config.json similarity index 100% rename from src/main/python/ddadevops/release_mixin/config.json rename to src/test/resources/alt_config.json diff --git a/src/main/python/ddadevops/release_mixin/build.py b/src/test/resources/build.py similarity index 100% rename from src/main/python/ddadevops/release_mixin/build.py rename to src/test/resources/build.py From ce1fd3f559f071ba3a3746b58e30fa63a89fee19 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 12:43:19 +0200 Subject: [PATCH 192/243] Add_remote functionality to GitApi --- src/main/python/ddadevops/release_mixin/infrastructure_api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/release_mixin/infrastructure_api.py index 908179d..520b744 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/release_mixin/infrastructure_api.py @@ -234,6 +234,10 @@ class GitApi(): self.system_api.run_checked('git', 'add', file_path) return self.system_api.stdout + def add_remote(self, origin: str, url: str): + self.system_api.run_checked('git', 'remote', 'add', origin, url) + return self.system_api.stdout + def commit(self, commit_message: str): self.system_api.run_checked( 'git', 'commit', '-m', commit_message) From d3049271cc08b4b973a61d3c9bcbbb21b348799f Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 12:44:05 +0200 Subject: [PATCH 193/243] Uncomment tag_and_push_release --- src/main/python/ddadevops/release_mixin/release_mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/release_mixin/release_mixin.py b/src/main/python/ddadevops/release_mixin/release_mixin.py index e9348aa..be9b801 100644 --- a/src/main/python/ddadevops/release_mixin/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin/release_mixin.py @@ -50,4 +50,4 @@ class ReleaseMixin(DevopsBuild): # TODO: jem, zam - 2023_04_14: instantiate service on object creation? tag_and_push_release_service = TagAndPushReleaseService(self.git_api) tag_and_push_release_service.tag_release(self.release_repo.get_release()) - # tag_and_push_release_service.push_release() + tag_and_push_release_service.push_release() From fce12d2e21fde747c1f25649a29c12fe1f20265c Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 15:30:20 +0200 Subject: [PATCH 194/243] Make Release object available --- src/main/python/ddadevops/domain/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/domain/__init__.py b/src/main/python/ddadevops/domain/__init__.py index 403eaa4..f6bf5e2 100644 --- a/src/main/python/ddadevops/domain/__init__.py +++ b/src/main/python/ddadevops/domain/__init__.py @@ -1,3 +1,4 @@ from .common import Validateable, DnsRecord, Devops from .image import Image -from .c4k import C4k \ No newline at end of file +from .c4k import C4k +from .release_config import ReleaseConfig \ No newline at end of file From d0ddeb3288c4bfea9769ffe26a242861dc017c9b Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 15:31:47 +0200 Subject: [PATCH 195/243] Implement ReleaseConfig --- .../python/ddadevops/domain/release_config.py | 22 +++++++++++++++++++ src/main/python/ddadevops/infrastructure.py | 9 +++++++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/main/python/ddadevops/domain/release_config.py diff --git a/src/main/python/ddadevops/domain/release_config.py b/src/main/python/ddadevops/domain/release_config.py new file mode 100644 index 0000000..f919aff --- /dev/null +++ b/src/main/python/ddadevops/domain/release_config.py @@ -0,0 +1,22 @@ +from typing import Optional +from .common import ( + filter_none, + Validateable, + Devops, +) + +class ReleaseConfig(Validateable): + def __init__( + self, + main_branch: str, + config_file: str, + devops: Devops, + release_version: str = None, + bump_version: str = None + ): + self.main_branch = main_branch + self.config_file = config_file + self.release_version = release_version + self.bump_version = bump_version + self.devops = devops + \ No newline at end of file diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure.py index 196b076..d12569b 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/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 +from .domain import Devops, Image, C4k, ReleaseConfig from .python_util import execute @@ -30,6 +30,13 @@ class ProjectRepository: def set_c4k(self, project, build: C4k): project.set_property("c4k_build", build) + def get_release(self, project) -> ReleaseConfig: + return project.get_property("release_build") + + def set_release(self, project, build: ReleaseConfig): + project.set_property("release_build", build) + + class ResourceApi: def read_resource(self, path: str) -> bytes: From 537a5836b93c8f3b6bbe1ce297033d1b02b2aa63 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 15:33:14 +0200 Subject: [PATCH 196/243] Refactor to use ReleaseConfig object --- .../ddadevops/release_mixin/release_mixin.py | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/release_mixin.py b/src/main/python/ddadevops/release_mixin/release_mixin.py index be9b801..7ed8f10 100644 --- a/src/main/python/ddadevops/release_mixin/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin/release_mixin.py @@ -1,12 +1,13 @@ # TODO: jem, zam - 2023_04_14: mv file one dir up - +from typing import Optional +from pybuilder.core import Project from ..devops_build import DevopsBuild from .infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository from .infrastructure_api import GitApi, EnvironmentApi from .services import PrepareReleaseService, TagAndPushReleaseService from .domain import EnvironmentKeys +from src.main.python.ddadevops.domain.release_config import ReleaseConfig -# TODO: jem, zam - 2023_04_14: use of config fkts is deprecated - use domain object instead (see: devops_image_build.py) def create_release_mixin_config(config_file, main_branch) -> dict: config = {} config.update( @@ -15,7 +16,6 @@ def create_release_mixin_config(config_file, main_branch) -> dict: 'config_file': config_file}}) return config -# TODO: jem, zam - 2023_04_14: use of config fkts is deprecated - use domain object instead (see: devops_image_build.py) def add_versions(config, release_version, bump_version) -> dict: config['ReleaseMixin'].update( {'release_version': release_version, @@ -23,31 +23,41 @@ def add_versions(config, release_version, bump_version) -> dict: return config class ReleaseMixin(DevopsBuild): - # TODO: jem, zam - 2023_04_14: handover config as domain object - def __init__(self, project, config): # todo: create services in init, dont expose repos etc in api - super().__init__(project, config) - release_mixin_config = config['ReleaseMixin'] - self.config_file = release_mixin_config['config_file'] - self.main_branch = release_mixin_config['main_branch'] - self.git_api = GitApi() - self.environment_api = EnvironmentApi() - self.env_key = EnvironmentKeys.DDADEVOPS_RELEASE_TYPE.name - self.environment_val_set = self.environment_api.get(self.env_key) is not "" and self.environment_api.get(self.env_key) is not None - if (self.environment_val_set): - self.release_type_repo = ReleaseTypeRepository.from_environment(self.environment_api) + def __init__(self, project: Project, config: Optional[dict] = None, release_config: Optional[ReleaseConfig] = None): + if not release_config: + if not config: + raise Exception("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: - self.release_type_repo = ReleaseTypeRepository.from_git(self.git_api) - self.version_repo = VersionRepository(self.config_file) - self.release_repo = ReleaseRepository(self.version_repo, self.release_type_repo, self.main_branch) + super().__init__(project, devops=release_config.devops) + self.repo.set_release(self.project, release_config) + + git_api = GitApi() + environment_api = EnvironmentApi() + env_key = EnvironmentKeys.DDADEVOPS_RELEASE_TYPE.name + environment_val_set = environment_api.get(env_key) is not "" and environment_api.get(env_key) is not None + + if environment_val_set: + release_type_repo = ReleaseTypeRepository.from_environment(environment_api) + 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) + + self.prepare_release_service = PrepareReleaseService(release_repo) + self.tag_and_push_release_service = TagAndPushReleaseService(git_api, release_repo) def prepare_release(self): - # TODO: jem, zam - 2023_04_14: instantiate service on object creation? - prepare_release_service = PrepareReleaseService(self.release_repo) - prepare_release_service.write_and_commit_release() - prepare_release_service.write_and_commit_bump() + self.prepare_release_service.write_and_commit_release() + self.prepare_release_service.write_and_commit_bump() def tag_and_push_release(self): - # TODO: jem, zam - 2023_04_14: instantiate service on object creation? - tag_and_push_release_service = TagAndPushReleaseService(self.git_api) - tag_and_push_release_service.tag_release(self.release_repo.get_release()) - tag_and_push_release_service.push_release() + self.tag_and_push_release_service.tag_release() + self.tag_and_push_release_service.push_release() From 84d3e29c28833e9e7c9cebb082ab8f20b93c6c52 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 15:33:58 +0200 Subject: [PATCH 197/243] Refactor for call consistency --- src/main/python/ddadevops/release_mixin/services.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin/services.py b/src/main/python/ddadevops/release_mixin/services.py index 7ad3757..7f14bf7 100644 --- a/src/main/python/ddadevops/release_mixin/services.py +++ b/src/main/python/ddadevops/release_mixin/services.py @@ -27,11 +27,13 @@ class PrepareReleaseService(): class TagAndPushReleaseService(): - def __init__(self, git_api: GitApi): + def __init__(self, git_api: GitApi, release_repo: ReleaseRepository = None): self.git_api = git_api + self.release_repo = release_repo + self.release = release_repo.get_release() - def tag_release(self, release: Release): - annotation = 'v' + release.version.get_version_string() + def tag_release(self): + annotation = 'v' + self.release.version.get_version_string() message = 'Release ' + annotation # TODO: Why is the count a parameter? We always tag the second last commit in this process. self.git_api.tag_annotated(annotation, message, 1) From a1f9992fbae1484111082d2dbd9a71a80e3be8c6 Mon Sep 17 00:00:00 2001 From: erik Date: Tue, 18 Apr 2023 15:34:29 +0200 Subject: [PATCH 198/243] Refactor tests to accomodate changes --- .../python/release_mixin/test_release_mixin.py | 14 +++++++++++--- src/test/python/release_mixin/test_services.py | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index 9c4cfc3..ed4a52f 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -4,6 +4,7 @@ from pybuilder.core import Project from src.main.python.ddadevops.release_mixin.release_mixin import ReleaseMixin, create_release_mixin_config from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi, EnvironmentApi +from src.main.python.ddadevops.domain import Devops, ReleaseConfig from .helper import Helper @@ -27,6 +28,13 @@ def initialize(project, CONFIG_FILE): 'project_root_path': PROJECT_ROOT_PATH, 'build_dir_name': BUILD_DIR_NAME}) build = MyBuild(project, config) + return build + +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) return build def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch): @@ -45,9 +53,9 @@ def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch): git_api.add_file(th.TEST_FILE_NAME) git_api.commit("MAJOR release") - build = initialize(project, th.TEST_FILE_PATH) + build = initialize_with_object(project, th.TEST_FILE_PATH) build.prepare_release() - release_version = build.version_repo.get_version() + release_version = build.prepare_release_service.release_repo.version_repository.get_version() # test assert "124.0.1-SNAPSHOT" in release_version.get_version_string() @@ -73,7 +81,7 @@ def test_release_mixin_environment(tmp_path: Path, monkeypatch: pt.MonkeyPatch): build = initialize(project, th.TEST_FILE_PATH) build.prepare_release() - release_version = build.version_repo.get_version() + release_version = build.prepare_release_service.release_repo.version_repository.get_version() # test assert "124.0.1-SNAPSHOT" in release_version.get_version_string() diff --git a/src/test/python/release_mixin/test_services.py b/src/test/python/release_mixin/test_services.py index 8d7526d..ca8ca32 100644 --- a/src/test/python/release_mixin/test_services.py +++ b/src/test/python/release_mixin/test_services.py @@ -30,8 +30,8 @@ def test_prepare_release_service(): # todo: maybe use mocks for service api test def test_tag_and_push_release_service(): # init mock_release_repo = MockReleaseRepository(MockVersionRepository(), MockReleaseTypeRepository(MockGitApi()), 'main') - tag_and_push_release_service = TagAndPushReleaseService(MockGitApi()) - tag_and_push_release_service.tag_release(mock_release_repo.get_release()) + tag_and_push_release_service = TagAndPushReleaseService(MockGitApi(), mock_release_repo) + tag_and_push_release_service.tag_release() tag_and_push_release_service.push_release() #test From 529f5f165b2de13e939b1990601fc4123d1a9e82 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:33:40 +0200 Subject: [PATCH 199/243] DDD Refactor: Move image_build_service to application layer Make imports available as package. --- src/main/python/ddadevops/application/__init__.py | 1 + .../{application.py => application/image_build_service.py} | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/main/python/ddadevops/application/__init__.py rename src/main/python/ddadevops/{application.py => application/image_build_service.py} (93%) diff --git a/src/main/python/ddadevops/application/__init__.py b/src/main/python/ddadevops/application/__init__.py new file mode 100644 index 0000000..9ec265b --- /dev/null +++ b/src/main/python/ddadevops/application/__init__.py @@ -0,0 +1 @@ +from .image_build_service import ImageBuildService \ No newline at end of file diff --git a/src/main/python/ddadevops/application.py b/src/main/python/ddadevops/application/image_build_service.py similarity index 93% rename from src/main/python/ddadevops/application.py rename to src/main/python/ddadevops/application/image_build_service.py index 976aa44..a1fd210 100644 --- a/src/main/python/ddadevops/application.py +++ b/src/main/python/ddadevops/application/image_build_service.py @@ -1,5 +1,5 @@ -from .domain import Image -from .infrastructure import FileApi, ResourceApi, ImageApi +from src.main.python.ddadevops.domain import Image +from src.main.python.ddadevops.infrastructure import FileApi, ResourceApi, ImageApi class ImageBuildService: From 1eac5d36ef8f3334e39b1b388d7b387fe6508275 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:36:23 +0200 Subject: [PATCH 200/243] DDD Refactor: Move infrastructure.py to infrastructure layer Make imports available as module. --- src/main/python/ddadevops/infrastructure/__init__.py | 1 + .../python/ddadevops/{ => infrastructure}/infrastructure.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/main/python/ddadevops/infrastructure/__init__.py rename src/main/python/ddadevops/{ => infrastructure}/infrastructure.py (97%) diff --git a/src/main/python/ddadevops/infrastructure/__init__.py b/src/main/python/ddadevops/infrastructure/__init__.py new file mode 100644 index 0000000..2b6d1de --- /dev/null +++ b/src/main/python/ddadevops/infrastructure/__init__.py @@ -0,0 +1 @@ +from .infrastructure import FileApi, ImageApi, ResourceApi, ExecutionApi, ProjectRepository \ No newline at end of file diff --git a/src/main/python/ddadevops/infrastructure.py b/src/main/python/ddadevops/infrastructure/infrastructure.py similarity index 97% rename from src/main/python/ddadevops/infrastructure.py rename to src/main/python/ddadevops/infrastructure/infrastructure.py index d12569b..b329ae5 100644 --- a/src/main/python/ddadevops/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure/infrastructure.py @@ -4,8 +4,8 @@ from os import chmod from subprocess import run from pkg_resources import resource_string import yaml -from .domain import Devops, Image, C4k, ReleaseConfig -from .python_util import execute +from ..domain import Devops, Image, C4k, ReleaseConfig +from ..python_util import execute class ProjectRepository: From 628ff353037db32edfff5001d7510ce638d44ed2 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:55:28 +0200 Subject: [PATCH 201/243] Fix CI: Add fields for stage and module As required by linter. --- src/main/python/ddadevops/devops_terraform_build.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/python/ddadevops/devops_terraform_build.py b/src/main/python/ddadevops/devops_terraform_build.py index 9b4f85b..9b47af5 100644 --- a/src/main/python/ddadevops/devops_terraform_build.py +++ b/src/main/python/ddadevops/devops_terraform_build.py @@ -55,6 +55,8 @@ class DevopsTerraformBuild(DevopsBuild): self.debug_print_terraform_command = config['debug_print_terraform_command'] self.additional_tfvar_files = config['additional_tfvar_files'] self.terraform_semantic_version = config['terraform_semantic_version'] + self.stage = config["stage"] + self.module = config["module"] def terraform_build_commons_path(self): mylist = [self.build_commons_path, From bf8fef87c79ef7134bc70fda457d5196f523d4d5 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:39:51 +0200 Subject: [PATCH 202/243] DDD Refactor: Move release to domain Merge release_config and release. Make imports available. --- src/main/python/ddadevops/domain/__init__.py | 2 +- .../domain.py => domain/release.py} | 22 +++++++++++++++++-- .../python/ddadevops/domain/release_config.py | 22 ------------------- .../ddadevops/release_mixin/__init__.py | 0 4 files changed, 21 insertions(+), 25 deletions(-) rename src/main/python/ddadevops/{release_mixin/domain.py => domain/release.py} (85%) delete mode 100644 src/main/python/ddadevops/domain/release_config.py delete mode 100644 src/main/python/ddadevops/release_mixin/__init__.py diff --git a/src/main/python/ddadevops/domain/__init__.py b/src/main/python/ddadevops/domain/__init__.py index f6bf5e2..8eaba18 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_config import ReleaseConfig \ No newline at end of file +from .release import Release, ReleaseConfig, ReleaseType, Version, EnvironmentKeys \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/domain.py b/src/main/python/ddadevops/domain/release.py similarity index 85% rename from src/main/python/ddadevops/release_mixin/domain.py rename to src/main/python/ddadevops/domain/release.py index 4ad8822..8d57dfb 100644 --- a/src/main/python/ddadevops/release_mixin/domain.py +++ b/src/main/python/ddadevops/domain/release.py @@ -1,6 +1,10 @@ -# TODO: jem, zam - 2023_04_18: Mv this to src/main/python/ddadevops/domain/release.py from enum import Enum from pathlib import Path +from .common import ( + filter_none, + Validateable, + Devops, +) class ReleaseType(Enum): MAJOR = 0 @@ -58,7 +62,21 @@ class Version(): bump_version.increment(ReleaseType.BUMP) return bump_version -# TODO: jem, zam - 2023_04_18: xtend abstract validateable +class ReleaseConfig(Validateable): + def __init__( + self, + main_branch: str, + config_file: str, + devops: Devops, + release_version: str = None, + bump_version: str = None + ): + self.main_branch = main_branch + self.config_file = config_file + self.release_version = release_version + self.bump_version = bump_version + self.devops = devops + class Release(): def __init__(self, release_type: ReleaseType, version: Version, current_branch: str): self.release_type = release_type diff --git a/src/main/python/ddadevops/domain/release_config.py b/src/main/python/ddadevops/domain/release_config.py deleted file mode 100644 index f919aff..0000000 --- a/src/main/python/ddadevops/domain/release_config.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional -from .common import ( - filter_none, - Validateable, - Devops, -) - -class ReleaseConfig(Validateable): - def __init__( - self, - main_branch: str, - config_file: str, - devops: Devops, - release_version: str = None, - bump_version: str = None - ): - self.main_branch = main_branch - self.config_file = config_file - self.release_version = release_version - self.bump_version = bump_version - self.devops = devops - \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/__init__.py b/src/main/python/ddadevops/release_mixin/__init__.py deleted file mode 100644 index e69de29..0000000 From 858666a95703086e1d09f431544b256f7dada8fc Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:41:53 +0200 Subject: [PATCH 203/243] DDD Refactor: Move release_mixin_services to application layer --- src/main/python/ddadevops/application/__init__.py | 3 ++- .../services.py => application/release_mixin_services.py} | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) rename src/main/python/ddadevops/{release_mixin/services.py => application/release_mixin_services.py} (88%) diff --git a/src/main/python/ddadevops/application/__init__.py b/src/main/python/ddadevops/application/__init__.py index 9ec265b..73018ad 100644 --- a/src/main/python/ddadevops/application/__init__.py +++ b/src/main/python/ddadevops/application/__init__.py @@ -1 +1,2 @@ -from .image_build_service import ImageBuildService \ No newline at end of file +from .image_build_service import ImageBuildService +from .release_mixin_services import TagAndPushReleaseService, PrepareReleaseService \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/services.py b/src/main/python/ddadevops/application/release_mixin_services.py similarity index 88% rename from src/main/python/ddadevops/release_mixin/services.py rename to src/main/python/ddadevops/application/release_mixin_services.py index 7f14bf7..b953e11 100644 --- a/src/main/python/ddadevops/release_mixin/services.py +++ b/src/main/python/ddadevops/application/release_mixin_services.py @@ -1,7 +1,5 @@ -# TODO: jem, zam - 2023_04_14: Move this services to application layer -from .infrastructure import ReleaseRepository -from .infrastructure_api import GitApi -from .domain import Version, Release +from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseRepository, GitApi +from src.main.python.ddadevops.domain import Version class PrepareReleaseService(): From 38f700cad5842f30faa7e503f7ca98d26d4e345a Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:43:08 +0200 Subject: [PATCH 204/243] DDD Refactor: Move release_mixin one layer up --- src/main/python/ddadevops/__init__.py | 1 + .../ddadevops/{release_mixin => }/release_mixin.py | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) rename src/main/python/ddadevops/{release_mixin => }/release_mixin.py (85%) diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index b00911c..5404bac 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -18,6 +18,7 @@ from .devops_image_build import DevopsImageBuild, create_devops_docker_build_con from .devops_terraform_build import DevopsTerraformBuild, create_devops_terraform_build_config from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit from .credential import gopass_password_from_path, gopass_field_from_path +from .release_mixin import ReleaseMixin from .domain import Validateable, DnsRecord, Devops, Image diff --git a/src/main/python/ddadevops/release_mixin/release_mixin.py b/src/main/python/ddadevops/release_mixin.py similarity index 85% rename from src/main/python/ddadevops/release_mixin/release_mixin.py rename to src/main/python/ddadevops/release_mixin.py index 7ed8f10..a744c45 100644 --- a/src/main/python/ddadevops/release_mixin/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin.py @@ -1,12 +1,9 @@ -# TODO: jem, zam - 2023_04_14: mv file one dir up from typing import Optional from pybuilder.core import Project -from ..devops_build import DevopsBuild -from .infrastructure import ReleaseRepository, ReleaseTypeRepository, VersionRepository -from .infrastructure_api import GitApi, EnvironmentApi -from .services import PrepareReleaseService, TagAndPushReleaseService -from .domain import EnvironmentKeys -from src.main.python.ddadevops.domain.release_config import ReleaseConfig +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.application import PrepareReleaseService, TagAndPushReleaseService +from src.main.python.ddadevops.domain import ReleaseConfig, EnvironmentKeys def create_release_mixin_config(config_file, main_branch) -> dict: config = {} From 2c65187741c1f7150fd2cd49d6c0e46cf4bae551 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:45:30 +0200 Subject: [PATCH 205/243] DDD Refactor: Move release_mixin infrastructure to infrastructure --- .../ddadevops/infrastructure/release_mixin/__init__.py | 2 ++ .../{ => infrastructure}/release_mixin/infrastructure_api.py | 1 - .../release_mixin/repo.py} | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 src/main/python/ddadevops/infrastructure/release_mixin/__init__.py rename src/main/python/ddadevops/{ => infrastructure}/release_mixin/infrastructure_api.py (99%) rename src/main/python/ddadevops/{release_mixin/infrastructure.py => infrastructure/release_mixin/repo.py} (93%) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py b/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py new file mode 100644 index 0000000..8d31c07 --- /dev/null +++ b/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py @@ -0,0 +1,2 @@ +from .infrastructure_api import FileHandler, SystemApi, EnvironmentApi, GitApi, JsonFileHandler, GradleFileHandler, PythonFileHandler, ClojureFileHandler +from .repo import VersionRepository, ReleaseRepository, ReleaseTypeRepository \ No newline at end of file diff --git a/src/main/python/ddadevops/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py similarity index 99% rename from src/main/python/ddadevops/release_mixin/infrastructure_api.py rename to src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py index 520b744..521e00a 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py @@ -1,4 +1,3 @@ -# TODO: jem, zam - 2023_04_18: Mv this to an infrastructure namespace? import json import re import subprocess as sub diff --git a/src/main/python/ddadevops/release_mixin/infrastructure.py b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py similarity index 93% rename from src/main/python/ddadevops/release_mixin/infrastructure.py rename to src/main/python/ddadevops/infrastructure/release_mixin/repo.py index 7adae14..bf1a7f7 100644 --- a/src/main/python/ddadevops/release_mixin/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py @@ -1,6 +1,5 @@ -# TODO: jem, zam - 2023_04_18: move content to src/main/python/ddadevops/infrastructure/repo.py (or subdir) -from .domain import Release, Version, ReleaseType, EnvironmentKeys -from .infrastructure_api import FileHandler, GitApi, EnvironmentApi +from src.main.python.ddadevops.domain import Release, Version, ReleaseType, EnvironmentKeys +from src.main.python.ddadevops.infrastructure.release_mixin import FileHandler, GitApi, EnvironmentApi class VersionRepository(): From 0a85790410dbbb454c6379a105d4efffb05cc0f7 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:49:21 +0200 Subject: [PATCH 206/243] Fix CI: Update Imports on tests --- src/test/python/release_mixin/__init__.py | 2 ++ src/test/python/release_mixin/helper.py | 2 +- src/test/python/release_mixin/mock_domain.py | 2 +- src/test/python/release_mixin/mock_infrastructure.py | 2 +- src/test/python/release_mixin/test_domain.py | 2 +- src/test/python/release_mixin/test_infrastructure.py | 4 ++-- src/test/python/release_mixin/test_infrastructure_api.py | 6 +++--- src/test/python/release_mixin/test_release_mixin.py | 4 ++-- src/test/python/release_mixin/test_services.py | 7 +++---- 9 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/test/python/release_mixin/__init__.py b/src/test/python/release_mixin/__init__.py index e69de29..9b7968b 100644 --- a/src/test/python/release_mixin/__init__.py +++ b/src/test/python/release_mixin/__init__.py @@ -0,0 +1,2 @@ +from .mock_infrastructure import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository +from .mock_infrastructure_api import MockGitApi \ No newline at end of file diff --git a/src/test/python/release_mixin/helper.py b/src/test/python/release_mixin/helper.py index 251703a..8009e18 100644 --- a/src/test/python/release_mixin/helper.py +++ b/src/test/python/release_mixin/helper.py @@ -1,5 +1,5 @@ from pathlib import Path -from src.main.python.ddadevops.release_mixin.infrastructure_api import SystemApi +from src.main.python.ddadevops.infrastructure.release_mixin import SystemApi class Helper(): diff --git a/src/test/python/release_mixin/mock_domain.py b/src/test/python/release_mixin/mock_domain.py index 14e625c..1a12ed2 100644 --- a/src/test/python/release_mixin/mock_domain.py +++ b/src/test/python/release_mixin/mock_domain.py @@ -1,4 +1,4 @@ -from src.main.python.ddadevops.release_mixin.domain import ReleaseType +from src.main.python.ddadevops.domain import ReleaseType class MockVersion(): diff --git a/src/test/python/release_mixin/mock_infrastructure.py b/src/test/python/release_mixin/mock_infrastructure.py index cb0fd5f..6d03180 100644 --- a/src/test/python/release_mixin/mock_infrastructure.py +++ b/src/test/python/release_mixin/mock_infrastructure.py @@ -1,4 +1,4 @@ -from src.main.python.ddadevops.release_mixin.domain import ReleaseType +from src.main.python.ddadevops.domain import ReleaseType from .mock_domain import MockRelease, MockVersion from .mock_infrastructure_api import MockGitApi diff --git a/src/test/python/release_mixin/test_domain.py b/src/test/python/release_mixin/test_domain.py index a76ab9c..47cffb4 100644 --- a/src/test/python/release_mixin/test_domain.py +++ b/src/test/python/release_mixin/test_domain.py @@ -1,5 +1,5 @@ from pathlib import Path -from src.main.python.ddadevops.release_mixin.domain import Version, ReleaseType, Release +from src.main.python.ddadevops.domain import Version, ReleaseType, Release def test_version(tmp_path: Path): version = Version(tmp_path, [1, 2, 3]) diff --git a/src/test/python/release_mixin/test_infrastructure.py b/src/test/python/release_mixin/test_infrastructure.py index 534a56d..fefe233 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.release_mixin.domain import ReleaseType -from src.main.python.ddadevops.release_mixin.infrastructure import ReleaseTypeRepository, VersionRepository, ReleaseRepository +from src.main.python.ddadevops.domain import ReleaseType +from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseTypeRepository, VersionRepository, ReleaseRepository from .mock_infrastructure_api import MockGitApi, MockEnvironmentApi from .helper import Helper diff --git a/src/test/python/release_mixin/test_infrastructure_api.py b/src/test/python/release_mixin/test_infrastructure_api.py index 0379d89..e983fac 100644 --- a/src/test/python/release_mixin/test_infrastructure_api.py +++ b/src/test/python/release_mixin/test_infrastructure_api.py @@ -1,9 +1,9 @@ from pathlib import Path import pytest as pt -from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi, EnvironmentApi, JsonFileHandler -from src.main.python.ddadevops.release_mixin.infrastructure import VersionRepository -from src.main.python.ddadevops.release_mixin.domain import ReleaseType +from src.main.python.ddadevops.infrastructure.release_mixin import GitApi, EnvironmentApi, JsonFileHandler +from src.main.python.ddadevops.infrastructure.release_mixin import VersionRepository +from src.main.python.ddadevops.domain.release import ReleaseType from .helper import Helper diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index ed4a52f..a32fc85 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -2,8 +2,8 @@ import pytest as pt from pathlib import Path from pybuilder.core import Project -from src.main.python.ddadevops.release_mixin.release_mixin import ReleaseMixin, create_release_mixin_config -from src.main.python.ddadevops.release_mixin.infrastructure_api import GitApi, EnvironmentApi +from src.main.python.ddadevops.release_mixin import ReleaseMixin, create_release_mixin_config +from src.main.python.ddadevops.infrastructure.release_mixin import GitApi, EnvironmentApi from src.main.python.ddadevops.domain import Devops, ReleaseConfig from .helper import Helper diff --git a/src/test/python/release_mixin/test_services.py b/src/test/python/release_mixin/test_services.py index ca8ca32..5eb6ffc 100644 --- a/src/test/python/release_mixin/test_services.py +++ b/src/test/python/release_mixin/test_services.py @@ -1,7 +1,6 @@ -from src.main.python.ddadevops.release_mixin.services import PrepareReleaseService, TagAndPushReleaseService - -from .mock_infrastructure import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository -from .mock_infrastructure_api import MockGitApi +from src.main.python.ddadevops.application import PrepareReleaseService, TagAndPushReleaseService +from src.test.python.release_mixin import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository +from src.test.python.release_mixin import MockGitApi def test_prepare_release_service(): # todo: maybe use mocks for service api tests # init From c5306281929789abe792ca0bbc995c1ce566fd62 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 11:57:26 +0200 Subject: [PATCH 207/243] Fix CI: Raise specific exception. Do comparison with !=. --- src/main/python/ddadevops/release_mixin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/python/ddadevops/release_mixin.py b/src/main/python/ddadevops/release_mixin.py index a744c45..590958c 100644 --- a/src/main/python/ddadevops/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin.py @@ -1,5 +1,5 @@ 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.infrastructure.release_mixin import ReleaseRepository, ReleaseTypeRepository, VersionRepository, GitApi, EnvironmentApi from src.main.python.ddadevops.application import PrepareReleaseService, TagAndPushReleaseService @@ -23,7 +23,7 @@ class ReleaseMixin(DevopsBuild): def __init__(self, project: Project, config: Optional[dict] = None, release_config: Optional[ReleaseConfig] = None): if not release_config: if not config: - raise Exception("Release parameters could not be set.") + raise ValueError("Release parameters could not be set.") super().__init__(project, config=config) release_mixin_config = config['ReleaseMixin'] release_config = ReleaseConfig( @@ -38,8 +38,8 @@ class ReleaseMixin(DevopsBuild): git_api = GitApi() environment_api = EnvironmentApi() env_key = EnvironmentKeys.DDADEVOPS_RELEASE_TYPE.name - environment_val_set = environment_api.get(env_key) is not "" and environment_api.get(env_key) is not None - + environment_val_set = environment_api.get(env_key) != "" and environment_api.get(env_key) is not None + if environment_val_set: release_type_repo = ReleaseTypeRepository.from_environment(environment_api) else: From e424732a30419bb501edde443c3777ca97d0fb18 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 12:33:03 +0200 Subject: [PATCH 208/243] Fix CI: Fix mocks --- src/test/python/release_mixin/mock_domain.py | 5 +++-- src/test/python/release_mixin/mock_infrastructure_api.py | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/test/python/release_mixin/mock_domain.py b/src/test/python/release_mixin/mock_domain.py index 1a12ed2..dc676fd 100644 --- a/src/test/python/release_mixin/mock_domain.py +++ b/src/test/python/release_mixin/mock_domain.py @@ -35,7 +35,8 @@ class MockRelease(): def validate(self, main_branch): self.validate_count += 1 - return self.is_valid + return [] def is_valid(self, main_branch): - return True + return len(self.validate(main_branch)) < 1 + diff --git a/src/test/python/release_mixin/mock_infrastructure_api.py b/src/test/python/release_mixin/mock_infrastructure_api.py index 843577e..7f84571 100644 --- a/src/test/python/release_mixin/mock_infrastructure_api.py +++ b/src/test/python/release_mixin/mock_infrastructure_api.py @@ -32,6 +32,10 @@ class MockGitApi(): def tag_annotated(self, annotation: str, message: str, count: int): self.tag_annotated_count += 1 return " " + + def tag_annotated_second_last(self, annotation: str, message: str): + self.tag_annotated(annotation, message, 1) + return " " def get_latest_tag(self): return " " From b050e9d07c87d890eae8ca6f343faeacb4cc7107 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 12:33:36 +0200 Subject: [PATCH 209/243] Fix CI: Correctly validate --- .../python/ddadevops/application/release_mixin_services.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/python/ddadevops/application/release_mixin_services.py b/src/main/python/ddadevops/application/release_mixin_services.py index b953e11..f5684df 100644 --- a/src/main/python/ddadevops/application/release_mixin_services.py +++ b/src/main/python/ddadevops/application/release_mixin_services.py @@ -10,8 +10,7 @@ class PrepareReleaseService(): self.git_api = GitApi() def __write_and_commit_version(self, version: Version, commit_message: str): - # TODO: isValid is missing - self.release.validate(self.release_repo.main_branch) + self.release.is_valid(self.release_repo.main_branch) self.release_repo.version_repository.write_file(version.get_version_string()) self.git_api.add_file(self.release_repo.version_repository.file) @@ -33,8 +32,7 @@ class TagAndPushReleaseService(): def tag_release(self): annotation = 'v' + self.release.version.get_version_string() message = 'Release ' + annotation - # TODO: Why is the count a parameter? We always tag the second last commit in this process. - self.git_api.tag_annotated(annotation, message, 1) + self.git_api.tag_annotated_second_last(annotation, message) def push_release(self): self.git_api.push() From 55114f0075064d4ae3a7b3a06396978da0d04d60 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 12:34:01 +0200 Subject: [PATCH 210/243] Fix CI: Fix validation --- src/main/python/ddadevops/domain/release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/domain/release.py b/src/main/python/ddadevops/domain/release.py index 8d57dfb..a639bd5 100644 --- a/src/main/python/ddadevops/domain/release.py +++ b/src/main/python/ddadevops/domain/release.py @@ -96,4 +96,4 @@ class Release(): return result def is_valid(self, main_branch): - return self.validate(main_branch).count < 1 + return len(self.validate(main_branch)) < 1 From 943cde3aeb92c88ed4f73ecc13d6f703ea1900a8 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 12:34:38 +0200 Subject: [PATCH 211/243] Fix ToDo: Method for always tagging second last --- .../infrastructure/release_mixin/infrastructure_api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py index 521e00a..9ab5253 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py @@ -214,6 +214,10 @@ class GitApi(): 'git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') return self.system_api.stdout + def tag_annotated_second_last(self, annotation: str, message:str): + self.tag_annotated(annotation, message, 1) + return self.system_api.stdout + def get_latest_tag(self): self.system_api.run_checked('git', 'describe', '--tags', '--abbrev=0') return self.system_api.stdout From b6d21167b1e3387396c7d71f61854be4d6f28384 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 12:35:04 +0200 Subject: [PATCH 212/243] Remove todo --- src/test/python/release_mixin/test_services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/python/release_mixin/test_services.py b/src/test/python/release_mixin/test_services.py index 5eb6ffc..00b8380 100644 --- a/src/test/python/release_mixin/test_services.py +++ b/src/test/python/release_mixin/test_services.py @@ -2,7 +2,7 @@ from src.main.python.ddadevops.application import PrepareReleaseService, TagAndP from src.test.python.release_mixin import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository from src.test.python.release_mixin import MockGitApi -def test_prepare_release_service(): # todo: maybe use mocks for service api tests +def test_prepare_release_service(): # init mock_release_repo = MockReleaseRepository(MockVersionRepository(), MockReleaseTypeRepository(MockGitApi()), 'main') prepare_release_service = PrepareReleaseService(mock_release_repo) From 24fc95ff8ad5ddb8ab2cb13b78f6c5075387ead6 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 12:38:21 +0200 Subject: [PATCH 213/243] Fix CI: Rename to avoid warnings --- src/test/python/domain/test_domain.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/python/domain/test_domain.py b/src/test/python/domain/test_domain.py index 94a44f3..4295612 100644 --- a/src/test/python/domain/test_domain.py +++ b/src/test/python/domain/test_domain.py @@ -9,7 +9,7 @@ from src.main.python.ddadevops.domain.c4k import C4k from src.main.python.ddadevops.c4k_mixin import add_c4k_mixin_config -class TestValidateable(Validateable): +class MockValidateable(Validateable): def __init__(self, value): self.field = value @@ -18,32 +18,32 @@ class TestValidateable(Validateable): def test_should_validate_non_empty_strings(): - sut = TestValidateable("content") + sut = MockValidateable("content") assert sut.is_valid() - sut = TestValidateable(None) + sut = MockValidateable(None) assert not sut.is_valid() - sut = TestValidateable("") + sut = MockValidateable("") assert not sut.is_valid() def test_should_validate_non_empty_others(): - sut = TestValidateable(1) + sut = MockValidateable(1) assert sut.is_valid() - sut = TestValidateable(1.0) + sut = MockValidateable(1.0) assert sut.is_valid() - sut = TestValidateable(True) + sut = MockValidateable(True) assert sut.is_valid() - sut = TestValidateable(None) + sut = MockValidateable(None) assert not sut.is_valid() def test_validate_with_reason(): - sut = TestValidateable(None) + sut = MockValidateable(None) assert sut.validate()[0] == "Field 'field' may not be empty." From 44d17084489e1944aadb4eac22524a6b0d570169 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 15:22:34 +0200 Subject: [PATCH 214/243] Fix CI: Move ValueError to new line --- src/main/python/ddadevops/devops_build.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index bb3a163..8485bc4 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -41,7 +41,8 @@ class DevopsBuild: self.file_api = FileApi() self.repo = ProjectRepository() if not devops: - if not config: raise Exception("Build parameters could not be set!") + if not config: + raise ValueError("Build parameters could not be set!") devops = Devops( stage=config["stage"], project_root_path=config["project_root_path"], From e31b4ef7b2936b3a96086d98e27211603108c62d Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 15:23:29 +0200 Subject: [PATCH 215/243] Fix CI: More Specific Error --- src/main/python/ddadevops/devops_image_build.py | 2 +- .../python/ddadevops/devops_terraform_build.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/python/ddadevops/devops_image_build.py b/src/main/python/ddadevops/devops_image_build.py index 7ef19f0..a5e6e65 100644 --- a/src/main/python/ddadevops/devops_image_build.py +++ b/src/main/python/ddadevops/devops_image_build.py @@ -37,7 +37,7 @@ class DevopsImageBuild(DevopsBuild): self.image_build_service = ImageBuildService() if not image: if not config: - raise Exception("Image parameters could not be set.") + raise ValueError("Image parameters could not be set.") super().__init__(project, config=config) image = Image( dockerhub_user=config["dockerhub_user"], diff --git a/src/main/python/ddadevops/devops_terraform_build.py b/src/main/python/ddadevops/devops_terraform_build.py index 9b47af5..f2019fb 100644 --- a/src/main/python/ddadevops/devops_terraform_build.py +++ b/src/main/python/ddadevops/devops_terraform_build.py @@ -138,7 +138,7 @@ class DevopsTerraformBuild(DevopsBuild): self.post_build() self.print_terraform_command(terraform) if return_code > 0: - raise Exception(return_code, "terraform error:", stderr) + raise RuntimeError(return_code, "terraform error:", stderr) def plan_fail_on_diff(self): terraform = self.init_client() @@ -148,9 +148,9 @@ class DevopsTerraformBuild(DevopsBuild): self.post_build() self.print_terraform_command(terraform) if return_code not in (0, 2): - raise Exception(return_code, "terraform error:", stderr) + raise RuntimeError(return_code, "terraform error:", stderr) if return_code == 2: - raise Exception(return_code, "diff in config found:", stderr) + raise RuntimeError(return_code, "diff in config found:", stderr) def apply(self, auto_approve=False): terraform = self.init_client() @@ -172,7 +172,7 @@ class DevopsTerraformBuild(DevopsBuild): self.post_build() self.print_terraform_command(terraform) if return_code > 0: - raise Exception(return_code, "terraform error:", stderr) + raise RuntimeError(return_code, "terraform error:", stderr) def refresh(self): terraform = self.init_client() @@ -183,7 +183,7 @@ class DevopsTerraformBuild(DevopsBuild): self.post_build() self.print_terraform_command(terraform) if return_code > 0: - raise Exception(return_code, "terraform error:", stderr) + raise RuntimeError(return_code, "terraform error:", stderr) def destroy(self, auto_approve=False): terraform = self.init_client() @@ -204,7 +204,7 @@ class DevopsTerraformBuild(DevopsBuild): self.post_build() self.print_terraform_command(terraform) if return_code > 0: - raise Exception(return_code, "terraform error:", stderr) + raise RuntimeError(return_code, "terraform error:", stderr) def tf_import(self, tf_import_name, tf_import_resource,): terraform = self.init_client() @@ -215,7 +215,7 @@ class DevopsTerraformBuild(DevopsBuild): self.post_build() self.print_terraform_command(terraform) if return_code > 0: - raise Exception(return_code, "terraform error:", stderr) + raise RuntimeError(return_code, "terraform error:", stderr) def print_terraform_command(self, terraform): if self.debug_print_terraform_command: From 578ac29ebbe2a2d5409cd570f3501aa66f973847 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 15:24:21 +0200 Subject: [PATCH 216/243] Fix CI: Remove unnecessary default val --- src/main/python/ddadevops/application/release_mixin_services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/application/release_mixin_services.py b/src/main/python/ddadevops/application/release_mixin_services.py index f5684df..1a3c0cb 100644 --- a/src/main/python/ddadevops/application/release_mixin_services.py +++ b/src/main/python/ddadevops/application/release_mixin_services.py @@ -24,7 +24,7 @@ class PrepareReleaseService(): class TagAndPushReleaseService(): - def __init__(self, git_api: GitApi, release_repo: ReleaseRepository = None): + def __init__(self, git_api: GitApi, release_repo: ReleaseRepository): self.git_api = git_api self.release_repo = release_repo self.release = release_repo.get_release() From 10e7deb74b5153d6cc12e8ba433a7c2dfd6cdbb4 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 15:25:35 +0200 Subject: [PATCH 217/243] Fix CI: Update Type declarations --- src/main/python/ddadevops/domain/release.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/python/ddadevops/domain/release.py b/src/main/python/ddadevops/domain/release.py index a639bd5..67da3ef 100644 --- a/src/main/python/ddadevops/domain/release.py +++ b/src/main/python/ddadevops/domain/release.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import Optional from pathlib import Path from .common import ( filter_none, @@ -21,8 +22,8 @@ class Version(): def __init__(self, id: Path, version_list: list): self.id = id self.version_list = version_list - self.version_string = None - self.is_snapshot = None + self.version_string: Optional[str | None] = None + self.is_snapshot: Optional[bool | None] = None def increment(self, release_type: ReleaseType): self.is_snapshot = False @@ -68,8 +69,8 @@ class ReleaseConfig(Validateable): main_branch: str, config_file: str, devops: Devops, - release_version: str = None, - bump_version: str = None + release_version: Optional[str | None] = None, + bump_version: Optional[str | None] = None ): self.main_branch = main_branch self.config_file = config_file @@ -78,7 +79,7 @@ class ReleaseConfig(Validateable): self.devops = devops class Release(): - def __init__(self, release_type: ReleaseType, version: Version, current_branch: str): + 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 From 420030fa0ae4ee7cb4828b66275a52b19832e168 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 15:26:17 +0200 Subject: [PATCH 218/243] Fix CI: Update type declaration and value assignments --- .../release_mixin/infrastructure_api.py | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py index 9ab5253..3b0a5d6 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py @@ -2,11 +2,15 @@ import json import re import subprocess as sub from abc import ABC, abstractmethod +from typing import Optional, Union from pathlib import Path from os import environ # TODO: jem, zam - 2023_04_18: Discuss if we can move more functionality to domain? class FileHandler(ABC): + def __init__(self) -> None: + self.config_file_path: Optional[Path | None] = None + self.config_file_type: Optional[Path | None] = None @classmethod def from_file_path(cls, file_path): @@ -40,6 +44,8 @@ class FileHandler(ABC): class JsonFileHandler(FileHandler): def parse(self) -> tuple[list[int], bool]: + if self.config_file_path is None: + raise ValueError("No file name given.") with open(self.config_file_path, 'r') as json_file: json_version = json.load(json_file)['version'] is_snapshot = False @@ -61,6 +67,8 @@ class JsonFileHandler(FileHandler): class GradleFileHandler(FileHandler): def parse(self) -> tuple[list[int], bool]: + if self.config_file_path is None: + raise ValueError("No file name given.") with open(self.config_file_path, 'r') as gradle_file: contents = gradle_file.read() version_line = re.search("\nversion = .*", contents) @@ -68,19 +76,19 @@ class GradleFileHandler(FileHandler): if version_line is None: raise exception - version_line = version_line.group() + version_line_group = version_line.group() version_string = re.search( - '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line_group) if version_string is None: raise exception - version_string = version_string.group() + version_string_group = version_string.group() is_snapshot = False - if '-SNAPSHOT' in version_string: + if '-SNAPSHOT' in version_string_group: is_snapshot = True - version_string = version_string.replace('-SNAPSHOT', '') + version_string_group = version_string_group.replace('-SNAPSHOT', '') - version = [int(x) for x in version_string.split('.')] + version = [int(x) for x in version_string_group.split('.')] return version, is_snapshot @@ -97,6 +105,8 @@ class GradleFileHandler(FileHandler): class PythonFileHandler(FileHandler): def parse(self) -> tuple[list[int], bool]: + if self.config_file_path is None: + raise ValueError("No file name given.") with open(self.config_file_path, 'r') as python_file: contents = python_file.read() version_line = re.search("\nversion = .*\n", contents) @@ -104,19 +114,19 @@ class PythonFileHandler(FileHandler): if version_line is None: raise exception - version_line = version_line.group() + version_line_group = version_line.group() version_string = re.search( - '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line_group) if version_string is None: raise exception - version_string = version_string.group() + version_string_group = version_string.group() is_snapshot = False - if '-SNAPSHOT' in version_string: + if '-SNAPSHOT' in version_string_group: is_snapshot = True - version_string = version_string.replace('-SNAPSHOT', '') + version_string_group = version_string_group.replace('-SNAPSHOT', '') - version = [int(x) for x in version_string.split('.')] + version = [int(x) for x in version_string_group.split('.')] return version, is_snapshot @@ -133,6 +143,8 @@ class PythonFileHandler(FileHandler): class ClojureFileHandler(FileHandler): def parse(self) -> tuple[list[int], bool]: + if self.config_file_path is None: + raise ValueError("No file name given.") with open(self.config_file_path, 'r') as clj_file: contents = clj_file.read() version_line = re.search("^\\(defproject .*\n", contents) @@ -140,19 +152,19 @@ class ClojureFileHandler(FileHandler): if version_line is None: raise exception - version_line = version_line.group() + version_line_group = version_line.group() version_string = re.search( - '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line) + '[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?', version_line_group) if version_string is None: raise exception - version_string = version_string.group() + version_string_group = version_string.group() is_snapshot = False - if '-SNAPSHOT' in version_string: + if '-SNAPSHOT' in version_string_group: is_snapshot = True - version_string = version_string.replace('-SNAPSHOT', '') + version_string_group = version_string_group.replace('-SNAPSHOT', '') - version = [int(x) for x in version_string.split('.')] + version = [int(x) for x in version_string_group.split('.')] return version, is_snapshot @@ -185,8 +197,14 @@ class SystemApi(): stderr=sub.PIPE, text=True, encoding="UTF-8") - self.stdout = stream.stdout.readlines() - self.stderr = stream.stderr.readlines() + if stream.stdout is not None: + self.stdout = stream.stdout.readlines() + else: + self.stdout = None + if stream.stderr is not None: + self.stderr = stream.stderr.readlines() + else: + self.stderr = None def run_checked(self, *args): self.run(args) From 8e8ad034a75dba130125e1050926377b8b7f854c Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 15:29:59 +0200 Subject: [PATCH 219/243] Fix CI: Refactor None like logic We now do check wether from_git or from_env was called and then take a bool based decision on which method to call. Also: Type declarations and specific errors. --- .../infrastructure/release_mixin/repo.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py index bf1a7f7..e32c4f9 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py @@ -1,3 +1,4 @@ +from typing import Optional from src.main.python.ddadevops.domain import Release, Version, ReleaseType, EnvironmentKeys from src.main.python.ddadevops.infrastructure.release_mixin import FileHandler, GitApi, EnvironmentApi @@ -32,19 +33,23 @@ class VersionRepository(): return version class ReleaseTypeRepository(): - def __init__(self, git_api: GitApi, environment_api: EnvironmentApi): - self.git_api = git_api - self.environment_api = environment_api + def __init__(self, git_api: GitApi = GitApi(), environment_api: EnvironmentApi = EnvironmentApi()): + self.git_api: GitApi = git_api + self.environment_api: EnvironmentApi = environment_api + self.get_from_git: bool = False + self.get_from_env: bool = False @classmethod def from_git(cls, git_api: GitApi): - environment_api = None - return cls(git_api, environment_api) + releaseTypeRepo = cls(git_api= git_api) + releaseTypeRepo.get_from_git = True + return releaseTypeRepo @classmethod def from_environment(cls, environment_api: EnvironmentApi): - git_api = None - return cls(git_api, environment_api) + releaseTypeRepo = cls(environment_api=environment_api) + releaseTypeRepo.get_from_env = True + return releaseTypeRepo def __get_release_type_git(self) -> ReleaseType | None: latest_commit = self.git_api.get_latest_commit() @@ -64,7 +69,7 @@ class ReleaseTypeRepository(): release_name = self.environment_api.get(EnvironmentKeys.DDADEVOPS_RELEASE_TYPE.name) if release_name is None: - return None + raise ValueError("Release Name not found. Is the Environment correctly configured?") elif ReleaseType.MAJOR.name in release_name.upper(): return ReleaseType.MAJOR elif ReleaseType.MINOR.name in release_name.upper(): @@ -77,12 +82,12 @@ class ReleaseTypeRepository(): return None def get_release_type(self) -> ReleaseType | None: - if self.git_api is not None: + if self.get_from_git: return self.__get_release_type_git() - elif self.environment_api is not None: + elif self.get_from_env: return self.__get_release_type_environment() else: - raise Exception('No valid api passed to ReleaseTypeRepository') + raise ValueError('No valid api passed to ReleaseTypeRepository') class ReleaseRepository(): def __init__(self, version_repository: VersionRepository, release_type_repository: ReleaseTypeRepository, main_branch: str): From d263ba73cef0ee69f08b4266b768baad4bdcec35 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 15:30:42 +0200 Subject: [PATCH 220/243] Fix CI: Test for Exception rather than None --- src/test/python/release_mixin/test_infrastructure.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/test/python/release_mixin/test_infrastructure.py b/src/test/python/release_mixin/test_infrastructure.py index fefe233..da427b3 100644 --- a/src/test/python/release_mixin/test_infrastructure.py +++ b/src/test/python/release_mixin/test_infrastructure.py @@ -79,5 +79,8 @@ def test_release_type_repository_env(): assert release_type == None sut = ReleaseTypeRepository.from_environment(MockEnvironmentApi({'REL_TYPE': 'Not the right variable'})) - release_type = sut.get_release_type() - assert release_type == None \ No newline at end of file + try: + release_type = sut.get_release_type() + except: + assert release_type == None + \ No newline at end of file From b51031413524894c68e01d7b5e13bdaf32cf6245 Mon Sep 17 00:00:00 2001 From: erik Date: Wed, 19 Apr 2023 15:40:57 +0200 Subject: [PATCH 221/243] Fix CI: Removed R0201 This has been moved to optional and does not need to be disabled by us. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 22751b0..3f6a768 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,7 +28,7 @@ pylint: stage: lint&test script: - pip install -r dev_requirements.txt - - pylint -d C0301,W0614,R0201,C0114,C0115,C0116,similarities,W0702,W0702,R0913,R0902,R0914,R1732 src/main/python/ddadevops/*.py + - pylint -d C0301,W0614,C0114,C0115,C0116,similarities,W0702,W0702,R0913,R0902,R0914,R1732 src/main/python/ddadevops/*.py pytest: stage: lint&test From 066c87473490a1d7d1ad7503626ffcb73d4c79af Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 20 Apr 2023 16:26:42 +0200 Subject: [PATCH 222/243] DDD Refactor: Use only init with object --- .../python/release_mixin/test_release_mixin.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index a32fc85..b56fe5f 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -2,7 +2,7 @@ import pytest as pt from pathlib import Path from pybuilder.core import Project -from src.main.python.ddadevops.release_mixin import ReleaseMixin, create_release_mixin_config +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 @@ -20,16 +20,6 @@ def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): class MyBuild(ReleaseMixin): pass -def initialize(project, CONFIG_FILE): - project.build_depends_on('ddadevops>=3.1.2') - config = create_release_mixin_config(CONFIG_FILE, MAIN_BRANCH) - config.update({'stage': STAGE, - 'module': MODULE, - 'project_root_path': PROJECT_ROOT_PATH, - 'build_dir_name': BUILD_DIR_NAME}) - build = MyBuild(project, config) - return build - 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) @@ -79,7 +69,7 @@ def test_release_mixin_environment(tmp_path: Path, monkeypatch: pt.MonkeyPatch): environment_api = EnvironmentApi() environment_api.set("DDADEVOPS_RELEASE_TYPE", "MAJOR") - build = initialize(project, th.TEST_FILE_PATH) + build = initialize_with_object(project, th.TEST_FILE_PATH) build.prepare_release() release_version = build.prepare_release_service.release_repo.version_repository.get_version() From 83a7c7ad7d0d115abf22613d1b62aa3561616f6b Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 20 Apr 2023 16:48:59 +0200 Subject: [PATCH 223/243] Fix CI: Update how pylint and mypy look for files --- .gitlab-ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3f6a768..842deff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ before_script: - python --version - python -m pip install --upgrade pip - pip install -r requirements.txt - + stages: - lint&test - upload @@ -21,14 +21,13 @@ mypy: stage: lint&test script: - pip install -r dev_requirements.txt - - python -m mypy src/main/python/ddadevops/*.py --ignore-missing-imports - - python -m mypy src/main/python/ddadevops/*/*.py --ignore-missing-imports + - python -m mypy src/ --ignore-missing-imports pylint: stage: lint&test script: - pip install -r dev_requirements.txt - - pylint -d C0301,W0614,C0114,C0115,C0116,similarities,W0702,W0702,R0913,R0902,R0914,R1732 src/main/python/ddadevops/*.py + - pylint -d C0301,W0614,C0114,C0115,C0116,similarities,W0702,W0702,R0913,R0902,R0914,R1732 src/ pytest: stage: lint&test From 580bf81c1542adbf1646622cc4a03e22f28dcdf6 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 20 Apr 2023 16:50:31 +0200 Subject: [PATCH 224/243] Fix CI: Remove whitespaces, add newlines --- src/main/python/ddadevops/application/__init__.py | 2 +- src/main/python/ddadevops/devops_build.py | 2 +- src/main/python/ddadevops/domain/__init__.py | 2 +- src/main/python/ddadevops/domain/release.py | 4 ++-- .../python/ddadevops/infrastructure/__init__.py | 2 +- .../ddadevops/infrastructure/infrastructure.py | 2 +- .../infrastructure/release_mixin/__init__.py | 2 +- .../release_mixin/infrastructure_api.py | 10 +++++----- .../ddadevops/infrastructure/release_mixin/repo.py | 14 +++++++------- src/test/python/release_mixin/__init__.py | 2 +- src/test/python/release_mixin/helper.py | 4 ++-- src/test/python/release_mixin/mock_domain.py | 1 - .../python/release_mixin/mock_infrastructure.py | 6 +++--- .../release_mixin/mock_infrastructure_api.py | 8 ++++---- .../python/release_mixin/test_infrastructure.py | 3 +-- .../release_mixin/test_infrastructure_api.py | 6 +++--- .../python/release_mixin/test_release_mixin.py | 8 ++++---- src/test/python/release_mixin/test_services.py | 2 +- src/test/python/test_c4k_mixin.py | 3 +-- 19 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/main/python/ddadevops/application/__init__.py b/src/main/python/ddadevops/application/__init__.py index 73018ad..6159750 100644 --- a/src/main/python/ddadevops/application/__init__.py +++ b/src/main/python/ddadevops/application/__init__.py @@ -1,2 +1,2 @@ from .image_build_service import ImageBuildService -from .release_mixin_services import TagAndPushReleaseService, PrepareReleaseService \ No newline at end of file +from .release_mixin_services import TagAndPushReleaseService, PrepareReleaseService diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index 8485bc4..cc3f860 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -21,7 +21,7 @@ def get_devops_build(project): @deprecation.deprecated(deprecated_in="3.2") -# TODO: Remove from here! +# TODO: Remove from here! # pylint: disable=W0511 def get_tag_from_latest_commit(): try: value = run( diff --git a/src/main/python/ddadevops/domain/__init__.py b/src/main/python/ddadevops/domain/__init__.py index 8eaba18..c584453 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 \ No newline at end of file +from .release import Release, ReleaseConfig, ReleaseType, Version, EnvironmentKeys diff --git a/src/main/python/ddadevops/domain/release.py b/src/main/python/ddadevops/domain/release.py index 67da3ef..7f9baf0 100644 --- a/src/main/python/ddadevops/domain/release.py +++ b/src/main/python/ddadevops/domain/release.py @@ -69,13 +69,13 @@ class ReleaseConfig(Validateable): main_branch: str, config_file: str, devops: Devops, - release_version: Optional[str | None] = None, + release_version: Optional[str | None] = None, bump_version: Optional[str | None] = None ): self.main_branch = main_branch self.config_file = config_file self.release_version = release_version - self.bump_version = bump_version + self.bump_version = bump_version self.devops = devops class Release(): diff --git a/src/main/python/ddadevops/infrastructure/__init__.py b/src/main/python/ddadevops/infrastructure/__init__.py index 2b6d1de..10deeea 100644 --- a/src/main/python/ddadevops/infrastructure/__init__.py +++ b/src/main/python/ddadevops/infrastructure/__init__.py @@ -1 +1 @@ -from .infrastructure import FileApi, ImageApi, ResourceApi, ExecutionApi, ProjectRepository \ No newline at end of file +from .infrastructure import FileApi, ImageApi, ResourceApi, ExecutionApi, ProjectRepository diff --git a/src/main/python/ddadevops/infrastructure/infrastructure.py b/src/main/python/ddadevops/infrastructure/infrastructure.py index b329ae5..c8bc41e 100644 --- a/src/main/python/ddadevops/infrastructure/infrastructure.py +++ b/src/main/python/ddadevops/infrastructure/infrastructure.py @@ -32,7 +32,7 @@ class ProjectRepository: def get_release(self, project) -> ReleaseConfig: return project.get_property("release_build") - + def set_release(self, project, build: ReleaseConfig): 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 8d31c07..8d6598a 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, SystemApi, EnvironmentApi, GitApi, JsonFileHandler, GradleFileHandler, PythonFileHandler, ClojureFileHandler -from .repo import VersionRepository, ReleaseRepository, ReleaseTypeRepository \ No newline at end of file +from .repo import VersionRepository, ReleaseRepository, ReleaseTypeRepository diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py index 3b0a5d6..d4c8ca1 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py @@ -197,11 +197,11 @@ class SystemApi(): stderr=sub.PIPE, text=True, encoding="UTF-8") - if stream.stdout is not None: + if stream.stdout is not None: self.stdout = stream.stdout.readlines() else: self.stdout = None - if stream.stderr is not None: + if stream.stderr is not None: self.stderr = stream.stderr.readlines() else: self.stderr = None @@ -246,7 +246,7 @@ class GitApi(): def init(self, default_branch: str = "main"): self.system_api.run_checked('git', 'init', '-b', default_branch) - + def set_user_config(self, email: str, name: str): self.system_api.run_checked('git', 'config', 'user.email', email) self.system_api.run_checked('git', 'config', 'user.name', name) @@ -279,6 +279,6 @@ class EnvironmentApi(): def get(self, key): return self.environ.get(key) - + def set(self, key, value): - self.environ[key] = value \ No newline at end of file + self.environ[key] = value diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py index e32c4f9..80bd461 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py @@ -8,28 +8,28 @@ class VersionRepository(): def __init__(self, file): self.file = file self.file_handler = None - + def load_file(self): self.file_handler = FileHandler.from_file_path(self.file) return self.file_handler def write_file(self, version_string): if self.file_handler is None: - raise Exception('Version was not created by load_file method.') + raise Exception('Version was not created by load_file method.') else: self.file_handler.write(version_string) - + def parse_file(self): version_list, is_snapshot = self.file_handler.parse() return version_list, is_snapshot - + def get_version(self) -> Version: self.file_handler = self.load_file() version_list, is_snapshot = self.parse_file() version = Version(self.file, version_list) version.is_snapshot = is_snapshot - + return version class ReleaseTypeRepository(): @@ -50,7 +50,7 @@ class ReleaseTypeRepository(): releaseTypeRepo = cls(environment_api=environment_api) releaseTypeRepo.get_from_env = True return releaseTypeRepo - + def __get_release_type_git(self) -> ReleaseType | None: latest_commit = self.git_api.get_latest_commit() @@ -87,7 +87,7 @@ class ReleaseTypeRepository(): elif self.get_from_env: return self.__get_release_type_environment() else: - raise ValueError('No valid api passed to ReleaseTypeRepository') + raise ValueError('No valid api passed to ReleaseTypeRepository') class ReleaseRepository(): def __init__(self, version_repository: VersionRepository, release_type_repository: ReleaseTypeRepository, main_branch: str): diff --git a/src/test/python/release_mixin/__init__.py b/src/test/python/release_mixin/__init__.py index 9b7968b..1514703 100644 --- a/src/test/python/release_mixin/__init__.py +++ b/src/test/python/release_mixin/__init__.py @@ -1,2 +1,2 @@ from .mock_infrastructure import MockReleaseRepository, MockReleaseTypeRepository, MockVersionRepository -from .mock_infrastructure_api import MockGitApi \ No newline at end of file +from .mock_infrastructure_api import MockGitApi diff --git a/src/test/python/release_mixin/helper.py b/src/test/python/release_mixin/helper.py index 8009e18..5c41a79 100644 --- a/src/test/python/release_mixin/helper.py +++ b/src/test/python/release_mixin/helper.py @@ -6,8 +6,8 @@ class Helper(): def __init__(self, file_name = 'config.json'): self.TEST_FILE_NAME = file_name self.TEST_FILE_ROOT = Path('src/test/resources/') - self.TEST_FILE_PATH = self.TEST_FILE_ROOT / self.TEST_FILE_NAME + self.TEST_FILE_PATH = self.TEST_FILE_ROOT / self.TEST_FILE_NAME def copy_files(self, source: Path, target: Path): api = SystemApi() - api.run_checked('cp', source, target) \ No newline at end of file + api.run_checked('cp', source, target) diff --git a/src/test/python/release_mixin/mock_domain.py b/src/test/python/release_mixin/mock_domain.py index dc676fd..e350a7b 100644 --- a/src/test/python/release_mixin/mock_domain.py +++ b/src/test/python/release_mixin/mock_domain.py @@ -39,4 +39,3 @@ class MockRelease(): def is_valid(self, main_branch): return len(self.validate(main_branch)) < 1 - diff --git a/src/test/python/release_mixin/mock_infrastructure.py b/src/test/python/release_mixin/mock_infrastructure.py index 6d03180..9d7891f 100644 --- a/src/test/python/release_mixin/mock_infrastructure.py +++ b/src/test/python/release_mixin/mock_infrastructure.py @@ -9,14 +9,14 @@ class MockVersionRepository(): self.file = None self.file_handler = None self.write_file_count = 0 - + def load_file(self): pass def write_file(self, version_string): self.write_file_count += 1 pass - + def parse_file(self): pass @@ -39,4 +39,4 @@ class MockReleaseRepository(): def get_release(self) -> MockRelease: self.get_release_count += 1 - return MockRelease(self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch) \ No newline at end of file + return MockRelease(self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch) diff --git a/src/test/python/release_mixin/mock_infrastructure_api.py b/src/test/python/release_mixin/mock_infrastructure_api.py index 7f84571..368203b 100644 --- a/src/test/python/release_mixin/mock_infrastructure_api.py +++ b/src/test/python/release_mixin/mock_infrastructure_api.py @@ -17,7 +17,7 @@ class MockGitApi(): self.system_api = MockSystemApi() self.get_latest_commit_count = 0 self.commit_string = commit_string - self.tag_annotated_count = 0 + self.tag_annotated_count = 0 self.add_file_count = 0 self.commit_count = 0 self.push_count = 0 @@ -30,9 +30,9 @@ class MockGitApi(): return self.commit_string def tag_annotated(self, annotation: str, message: str, count: int): - self.tag_annotated_count += 1 + self.tag_annotated_count += 1 return " " - + def tag_annotated_second_last(self, annotation: str, message: str): self.tag_annotated(annotation, message, 1) return " " @@ -70,4 +70,4 @@ class MockEnvironmentApi(): return self.environ.get(name) def set(self, name, value): - self.environ[name] = value \ No newline at end of file + self.environ[name] = value diff --git a/src/test/python/release_mixin/test_infrastructure.py b/src/test/python/release_mixin/test_infrastructure.py index da427b3..b008ef0 100644 --- a/src/test/python/release_mixin/test_infrastructure.py +++ b/src/test/python/release_mixin/test_infrastructure.py @@ -15,7 +15,7 @@ def test_version_repository(tmp_path): def test_release_repository(tmp_path): - # init + # init th = Helper() th.copy_files( th.TEST_FILE_PATH, tmp_path) version_repo = VersionRepository(th.TEST_FILE_PATH) @@ -83,4 +83,3 @@ def test_release_type_repository_env(): release_type = sut.get_release_type() except: assert release_type == None - \ No newline at end of file diff --git a/src/test/python/release_mixin/test_infrastructure_api.py b/src/test/python/release_mixin/test_infrastructure_api.py index e983fac..f2ba909 100644 --- a/src/test/python/release_mixin/test_infrastructure_api.py +++ b/src/test/python/release_mixin/test_infrastructure_api.py @@ -11,13 +11,13 @@ def change_test_dir( tmp_path: Path, monkeypatch: pt.MonkeyPatch): monkeypatch.chdir(tmp_path) def test_environment_api(): - # init + # init env_api = EnvironmentApi() key = "TEST_ENV_KEY" value = "data" env_api.set(key, value) - #test + #test assert env_api.get(key) == value def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): @@ -26,7 +26,7 @@ def test_git_api(tmp_path: Path, monkeypatch: pt.MonkeyPatch): th.copy_files(th.TEST_FILE_PATH, tmp_path) # change the context of the script execution to tmp_path - change_test_dir(tmp_path, monkeypatch) + change_test_dir(tmp_path, monkeypatch) git_api = GitApi() git_api.init() diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index b56fe5f..d681d4a 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -25,7 +25,7 @@ def initialize_with_object(project, CONFIG_FILE): 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) - return build + return build def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch): @@ -34,7 +34,7 @@ def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch): th.copy_files(th.TEST_FILE_PATH, tmp_path) th.TEST_FILE_PATH = tmp_path / th.TEST_FILE_NAME - change_test_dir(tmp_path, monkeypatch) + change_test_dir(tmp_path, monkeypatch) project = Project(tmp_path) git_api = GitApi() @@ -57,7 +57,7 @@ def test_release_mixin_environment(tmp_path: Path, monkeypatch: pt.MonkeyPatch): th.copy_files(th.TEST_FILE_PATH, tmp_path) th.TEST_FILE_PATH = tmp_path / th.TEST_FILE_NAME - change_test_dir(tmp_path, monkeypatch) + change_test_dir(tmp_path, monkeypatch) project = Project(tmp_path) git_api = GitApi() @@ -68,7 +68,7 @@ def test_release_mixin_environment(tmp_path: Path, monkeypatch: pt.MonkeyPatch): environment_api = EnvironmentApi() environment_api.set("DDADEVOPS_RELEASE_TYPE", "MAJOR") - + build = initialize_with_object(project, th.TEST_FILE_PATH) build.prepare_release() release_version = build.prepare_release_service.release_repo.version_repository.get_version() diff --git a/src/test/python/release_mixin/test_services.py b/src/test/python/release_mixin/test_services.py index 00b8380..5aff9ac 100644 --- a/src/test/python/release_mixin/test_services.py +++ b/src/test/python/release_mixin/test_services.py @@ -25,7 +25,7 @@ def test_prepare_release_service(): assert prepare_release_service.release_repo.version_repository.write_file_count == 2 assert prepare_release_service.git_api.add_file_count == 2 assert prepare_release_service.git_api.commit_count == 2 - + def test_tag_and_push_release_service(): # init mock_release_repo = MockReleaseRepository(MockVersionRepository(), MockReleaseTypeRepository(MockGitApi()), 'main') diff --git a/src/test/python/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py index 2f5db38..59cf436 100644 --- a/src/test/python/test_c4k_mixin.py +++ b/src/test/python/test_c4k_mixin.py @@ -28,7 +28,7 @@ def test_c4k_mixin(tmp_path): add_c4k_mixin_config(project_config, config, auth, grafana_cloud_user='user', grafana_cloud_password='password') assert project_config.get('C4kMixin') is not None - + mixin = MyC4kMixin(project, project_config) mixin.initialize_build_dir() assert mixin.build_path() == f'{tmp_path_str}/{build_dir}/{project_name}/{module_name}' @@ -44,4 +44,3 @@ def test_c4k_mixin(tmp_path): mixin.write_c4k_auth() assert os.path.exists(f'{mixin.build_path()}/out_c4k_auth.yaml') - \ No newline at end of file From 381baa2eb2eaac4371ca327d2438ee7d022b3a14 Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 21 Apr 2023 12:18:09 +0200 Subject: [PATCH 225/243] [Skip CI] Uncomment assertion in test_image_build Add logging. --- src/main/python/ddadevops/domain/common.py | 5 ++++- src/test/python/test_image_build.py | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/python/ddadevops/domain/common.py b/src/main/python/ddadevops/domain/common.py index c322845..be43a01 100644 --- a/src/main/python/ddadevops/domain/common.py +++ b/src/main/python/ddadevops/domain/common.py @@ -1,4 +1,5 @@ import deprecation +import logging from typing import List def filter_none(list_to_filter): @@ -35,11 +36,12 @@ class DnsRecord(Validateable): class Devops(Validateable): def __init__( - self, stage, project_root_path, module, name=None, build_dir_name="target" + self, stage: str, project_root_path: str, module: str, name: str | None =None, build_dir_name: str="target" ): self.stage = stage self.name = name self.project_root_path = project_root_path + logging.warn(f"Set project root in DevOps {self.project_root_path}") self.module = module if not name: self.name = module @@ -54,6 +56,7 @@ class Devops(Validateable): def build_path(self): path = [self.project_root_path, self.build_dir_name, self.name, self.module] + logging.warn(f"Set project build_path in Devops {path}") return "/".join(filter_none(path)) def __put__(self, key, value): diff --git a/src/test/python/test_image_build.py b/src/test/python/test_image_build.py index 666c0bc..ff8f9cf 100644 --- a/src/test/python/test_image_build.py +++ b/src/test/python/test_image_build.py @@ -16,9 +16,10 @@ def test_devops_docker_build(tmp_path): project_root_path=tmp_path_str, module=module_name, name=project_name, + build_dir_name=build_dir ) image = Image(dockerhub_user="user", dockerhub_password="password", devops=devops) docker_build = DevopsImageBuild(project, image=image) - # docker_build.initialize_build_dir() - # assert os.path.exists(f"{docker_build.build_path()}") + docker_build.initialize_build_dir() + assert os.path.exists(f"{docker_build.build_path()}") From 223ba498d36169bd8b41e6a365f772a21949c81a Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 21 Apr 2023 13:49:12 +0200 Subject: [PATCH 226/243] Review TODOs --- .gitlab-ci.yml | 4 ++-- .../python/ddadevops/application/release_mixin_services.py | 6 +++--- src/main/python/ddadevops/domain/release.py | 2 +- src/main/python/ddadevops/release_mixin.py | 4 +++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 842deff..1605ca1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ flake8: stage: lint&test script: - pip install -r dev_requirements.txt - - flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 --show-source --statistics src/main/python/ddadevops/*.py + - flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 --show-source --statistics src/main/python/ddadevops/*.py # TODO: change target - flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics src/main/python/ddadevops/*.py mypy: @@ -27,7 +27,7 @@ pylint: stage: lint&test script: - pip install -r dev_requirements.txt - - pylint -d C0301,W0614,C0114,C0115,C0116,similarities,W0702,W0702,R0913,R0902,R0914,R1732 src/ + - pylint -d C0301,W0614,C0114,C0115,C0116,similarities,W0702,W0702,R0913,R0902,R0914,R1732 src/ # TODO: narrower focus on target pytest: stage: lint&test diff --git a/src/main/python/ddadevops/application/release_mixin_services.py b/src/main/python/ddadevops/application/release_mixin_services.py index 1a3c0cb..3a4dbe2 100644 --- a/src/main/python/ddadevops/application/release_mixin_services.py +++ b/src/main/python/ddadevops/application/release_mixin_services.py @@ -6,7 +6,7 @@ class PrepareReleaseService(): def __init__(self, release_repo: ReleaseRepository): self.release_repo = release_repo - self.release = release_repo.get_release() + 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): @@ -26,8 +26,8 @@ class TagAndPushReleaseService(): def __init__(self, git_api: GitApi, release_repo: ReleaseRepository): self.git_api = git_api - self.release_repo = release_repo - self.release = release_repo.get_release() + self.release_repo = release_repo + self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func def tag_release(self): annotation = 'v' + self.release.version.get_version_string() diff --git a/src/main/python/ddadevops/domain/release.py b/src/main/python/ddadevops/domain/release.py index 7f9baf0..a2b1e0c 100644 --- a/src/main/python/ddadevops/domain/release.py +++ b/src/main/python/ddadevops/domain/release.py @@ -97,4 +97,4 @@ class Release(): return result def is_valid(self, main_branch): - return len(self.validate(main_branch)) < 1 + return len(self.validate(main_branch)) < 1 # TODO: Use abstract is_valid in Validatable diff --git a/src/main/python/ddadevops/release_mixin.py b/src/main/python/ddadevops/release_mixin.py index 590958c..373283a 100644 --- a/src/main/python/ddadevops/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin.py @@ -5,6 +5,7 @@ from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseReposi from src.main.python.ddadevops.application import PrepareReleaseService, TagAndPushReleaseService from src.main.python.ddadevops.domain import ReleaseConfig, EnvironmentKeys +# TODO: remove the config creation def create_release_mixin_config(config_file, main_branch) -> dict: config = {} config.update( @@ -12,13 +13,14 @@ def create_release_mixin_config(config_file, main_branch) -> dict: {'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, config: Optional[dict] = None, release_config: Optional[ReleaseConfig] = None): if not release_config: From 36e78d4139bdc558c172fcdd9dac7e2388a41554 Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 21 Apr 2023 13:51:05 +0200 Subject: [PATCH 227/243] Limit linters to main files --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1605ca1..1c2920c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,20 +14,20 @@ flake8: stage: lint&test script: - pip install -r dev_requirements.txt - - flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 --show-source --statistics src/main/python/ddadevops/*.py # TODO: change target - - flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics src/main/python/ddadevops/*.py + - flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 --show-source --statistics src/main/python/ddadevops/ + - flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics src/main/python/ddadevops/ mypy: stage: lint&test script: - pip install -r dev_requirements.txt - - python -m mypy src/ --ignore-missing-imports + - python -m mypy src/main/python/ddadevops/ --ignore-missing-imports pylint: stage: lint&test script: - pip install -r dev_requirements.txt - - pylint -d C0301,W0614,C0114,C0115,C0116,similarities,W0702,W0702,R0913,R0902,R0914,R1732 src/ # TODO: narrower focus on target + - pylint -d C0301,W0614,C0114,C0115,C0116,similarities,W0702,W0702,R0913,R0902,R0914,R1732 src/main/python/ddadevops/ pytest: stage: lint&test From b6f03c70c5f21ee536b45dfdf5c4595c933b3a6d Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 21 Apr 2023 14:06:40 +0200 Subject: [PATCH 228/243] Comment out tewst for later use --- src/test/python/test_image_build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/python/test_image_build.py b/src/test/python/test_image_build.py index ff8f9cf..9657085 100644 --- a/src/test/python/test_image_build.py +++ b/src/test/python/test_image_build.py @@ -21,5 +21,5 @@ def test_devops_docker_build(tmp_path): image = Image(dockerhub_user="user", dockerhub_password="password", devops=devops) docker_build = DevopsImageBuild(project, image=image) - docker_build.initialize_build_dir() - assert os.path.exists(f"{docker_build.build_path()}") + # docker_build.initialize_build_dir() + # assert os.path.exists(f"{docker_build.build_path()}") From 3d29277285d05d2918c957d73249bb445d586f61 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 21 Apr 2023 14:20:03 +0200 Subject: [PATCH 229/243] tried to get debugg outputs --- .../infrastructure/release_mixin/infrastructure_api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py index d4c8ca1..57cc506 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py @@ -5,6 +5,7 @@ from abc import ABC, abstractmethod from typing import Optional, Union from pathlib import Path from os import environ +import logging # TODO: jem, zam - 2023_04_18: Discuss if we can move more functionality to domain? class FileHandler(ABC): @@ -185,6 +186,7 @@ class SystemApi(): def __init__(self): self.stdout = [""] self.stderr = [""] + self.exitcode = 0 def run(self, args: list[str]): sanitized_args = [] @@ -206,9 +208,14 @@ class SystemApi(): else: self.stderr = None + self.exitcode = stream.returncode + def run_checked(self, *args): self.run(args) + logging.warning(f"err: {self.stderr}") + logging.warning(f"exit: {self.exitcode}") + if len(self.stderr) > 0: raise Exception(f"Command failed with: {self.stderr}") From 5fe58e95cd6d98002e4e0483444371752dd27a53 Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 21 Apr 2023 14:38:35 +0200 Subject: [PATCH 230/243] Deprecate SystemApi in Favour of ExecutionApi --- .../release_mixin/infrastructure_api.py | 59 +++++++++---------- src/test/python/release_mixin/helper.py | 7 +-- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py index 57cc506..042bc95 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py @@ -2,10 +2,10 @@ import json import re import subprocess as sub from abc import ABC, abstractmethod -from typing import Optional, Union +from typing import Optional from pathlib import Path from os import environ -import logging +from ..infrastructure import ExecutionApi # TODO: jem, zam - 2023_04_18: Discuss if we can move more functionality to domain? class FileHandler(ABC): @@ -188,7 +188,7 @@ class SystemApi(): self.stderr = [""] self.exitcode = 0 - def run(self, args: list[str]): + def run(self, args: list[str]): #TODO Make stateless sanitized_args = [] for arg in args: str_arg = str(arg) @@ -208,14 +208,9 @@ class SystemApi(): else: self.stderr = None - self.exitcode = stream.returncode - def run_checked(self, *args): self.run(args) - logging.warning(f"err: {self.stderr}") - logging.warning(f"exit: {self.exitcode}") - if len(self.stderr) > 0: raise Exception(f"Command failed with: {self.stderr}") @@ -223,61 +218,61 @@ class SystemApi(): class GitApi(): def __init__(self): - self.system_api = SystemApi() + self.execution_api = ExecutionApi() def get_latest_n_commits(self, n: int): - self.system_api.run_checked( - 'git', 'log', '--oneline', '--format="%s %b"', f'-n {n}') - return self.system_api.stdout + self.execution_api.execute( + f'git log --oneline --format="%s %b" -n {n}') + return self.execution_api.stdout def get_latest_commit(self): output = self.get_latest_n_commits(1) return " ".join(output) def tag_annotated(self, annotation: str, message: str, count: int): - self.system_api.run_checked( + self.execution_api.execute( 'git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') - return self.system_api.stdout + return self.execution_api.stdout def tag_annotated_second_last(self, annotation: str, message:str): self.tag_annotated(annotation, message, 1) - return self.system_api.stdout + return self.execution_api.stdout def get_latest_tag(self): - self.system_api.run_checked('git', 'describe', '--tags', '--abbrev=0') - return self.system_api.stdout + self.execution_api.execute('git', 'describe', '--tags', '--abbrev=0') + return self.execution_api.stdout def get_current_branch(self): - self.system_api.run_checked('git', 'branch', '--show-current') - return ''.join(self.system_api.stdout).rstrip() + self.execution_api.execute('git', 'branch', '--show-current') + return ''.join(self.execution_api.stdout).rstrip() def init(self, default_branch: str = "main"): - self.system_api.run_checked('git', 'init', '-b', default_branch) + self.execution_api.execute('git', 'init', '-b', default_branch) def set_user_config(self, email: str, name: str): - self.system_api.run_checked('git', 'config', 'user.email', email) - self.system_api.run_checked('git', 'config', 'user.name', name) + self.execution_api.execute('git', 'config', 'user.email', email) + self.execution_api.execute('git', 'config', 'user.name', name) def add_file(self, file_path: Path): - self.system_api.run_checked('git', 'add', file_path) - return self.system_api.stdout + self.execution_api.execute('git', 'add', file_path) + return self.execution_api.stdout def add_remote(self, origin: str, url: str): - self.system_api.run_checked('git', 'remote', 'add', origin, url) - return self.system_api.stdout + self.execution_api.execute('git', 'remote', 'add', origin, url) + return self.execution_api.stdout def commit(self, commit_message: str): - self.system_api.run_checked( + self.execution_api.execute( 'git', 'commit', '-m', commit_message) - return self.system_api.stdout + return self.execution_api.stdout def push(self): - self.system_api.run_checked('git', 'push') - return self.system_api.stdout + self.execution_api.execute('git', 'push') + return self.execution_api.stdout def checkout(self, branch: str): - self.system_api.run_checked('git', 'checkout', branch) - return self.system_api.stdout + self.execution_api.execute('git', 'checkout', branch) + return self.execution_api.stdout class EnvironmentApi(): diff --git a/src/test/python/release_mixin/helper.py b/src/test/python/release_mixin/helper.py index 5c41a79..c9843ca 100644 --- a/src/test/python/release_mixin/helper.py +++ b/src/test/python/release_mixin/helper.py @@ -1,6 +1,5 @@ from pathlib import Path -from src.main.python.ddadevops.infrastructure.release_mixin import SystemApi - +from src.main.python.ddadevops.infrastructure import ExecutionApi class Helper(): def __init__(self, file_name = 'config.json'): @@ -9,5 +8,5 @@ class Helper(): self.TEST_FILE_PATH = self.TEST_FILE_ROOT / self.TEST_FILE_NAME def copy_files(self, source: Path, target: Path): - api = SystemApi() - api.run_checked('cp', source, target) + api = ExecutionApi() + api.execute(f"cp {source} {target}") From 54f3938088d2398903f0d5718015c19f2b856b25 Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 21 Apr 2023 14:51:17 +0200 Subject: [PATCH 231/243] Finish refactoring for ExecutionAPI --- .../release_mixin/infrastructure_api.py | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py index 042bc95..036568e 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py @@ -221,58 +221,49 @@ class GitApi(): self.execution_api = ExecutionApi() def get_latest_n_commits(self, n: int): - self.execution_api.execute( + return self.execution_api.execute( f'git log --oneline --format="%s %b" -n {n}') - return self.execution_api.stdout def get_latest_commit(self): - output = self.get_latest_n_commits(1) - return " ".join(output) + return self.get_latest_n_commits(1) def tag_annotated(self, annotation: str, message: str, count: int): - self.execution_api.execute( - 'git', 'tag', '-a', annotation, '-m', message, f'HEAD~{count}') - return self.execution_api.stdout + return self.execution_api.execute( + f'git tag -a {annotation} -m {message} HEAD~{count}') def tag_annotated_second_last(self, annotation: str, message:str): - self.tag_annotated(annotation, message, 1) - return self.execution_api.stdout + return self.tag_annotated(annotation, message, 1) def get_latest_tag(self): - self.execution_api.execute('git', 'describe', '--tags', '--abbrev=0') - return self.execution_api.stdout + return self.execution_api.execute('git describe --tags --abbrev=0') def get_current_branch(self): - self.execution_api.execute('git', 'branch', '--show-current') + self.execution_api.execute('git branch --show-current') return ''.join(self.execution_api.stdout).rstrip() def init(self, default_branch: str = "main"): - self.execution_api.execute('git', 'init', '-b', default_branch) + self.execution_api.execute(f'git init') + self.execution_api.execute(f'git checkout -b {default_branch}') def set_user_config(self, email: str, name: str): - self.execution_api.execute('git', 'config', 'user.email', email) - self.execution_api.execute('git', 'config', 'user.name', name) + self.execution_api.execute(f'git config user.email {email}') + self.execution_api.execute(f'git config user.name {name}') def add_file(self, file_path: Path): - self.execution_api.execute('git', 'add', file_path) - return self.execution_api.stdout + return self.execution_api.execute(f'git add {file_path}') def add_remote(self, origin: str, url: str): - self.execution_api.execute('git', 'remote', 'add', origin, url) - return self.execution_api.stdout + return self.execution_api.execute(f'git remote add {origin} {url}') def commit(self, commit_message: str): - self.execution_api.execute( - 'git', 'commit', '-m', commit_message) - return self.execution_api.stdout + return self.execution_api.execute( + f'git commit -m "{commit_message}"') def push(self): - self.execution_api.execute('git', 'push') - return self.execution_api.stdout + return self.execution_api.execute('git push') def checkout(self, branch: str): - self.execution_api.execute('git', 'checkout', branch) - return self.execution_api.stdout + return self.execution_api.execute(f'git checkout {branch}') class EnvironmentApi(): From 134ee68cfada86174963b40ca55060f4c33ac6c7 Mon Sep 17 00:00:00 2001 From: bom Date: Fri, 21 Apr 2023 14:52:06 +0200 Subject: [PATCH 232/243] Remove deprecated SystemAPI --- .../release_mixin/infrastructure_api.py | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py index 036568e..09587b9 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/infrastructure_api.py @@ -181,40 +181,6 @@ class ClojureFileHandler(FileHandler): clj_file.truncate() -class SystemApi(): - - def __init__(self): - self.stdout = [""] - self.stderr = [""] - self.exitcode = 0 - - def run(self, args: list[str]): #TODO Make stateless - sanitized_args = [] - for arg in args: - str_arg = str(arg) - if str_arg is not "": - sanitized_args.append(str_arg.replace("\n", "")) - stream = sub.Popen(sanitized_args, - stdout=sub.PIPE, - stderr=sub.PIPE, - text=True, - encoding="UTF-8") - if stream.stdout is not None: - self.stdout = stream.stdout.readlines() - else: - self.stdout = None - if stream.stderr is not None: - self.stderr = stream.stderr.readlines() - else: - self.stderr = None - - def run_checked(self, *args): - self.run(args) - - if len(self.stderr) > 0: - raise Exception(f"Command failed with: {self.stderr}") - - class GitApi(): def __init__(self): From 28f0b629c30974abc9b7831d76f2b380225e766c Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 21 Apr 2023 14:54:24 +0200 Subject: [PATCH 233/243] fixed tests --- .../python/ddadevops/infrastructure/release_mixin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py b/src/main/python/ddadevops/infrastructure/release_mixin/__init__.py index 8d6598a..ec16e32 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, SystemApi, EnvironmentApi, GitApi, JsonFileHandler, GradleFileHandler, PythonFileHandler, ClojureFileHandler +from .infrastructure_api import FileHandler, EnvironmentApi, GitApi, JsonFileHandler, GradleFileHandler, PythonFileHandler, ClojureFileHandler from .repo import VersionRepository, ReleaseRepository, ReleaseTypeRepository From d717cd5a8cea440ede4bae3625a33503ed132d4a Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 21 Apr 2023 15:10:36 +0200 Subject: [PATCH 234/243] removed deprecated dict create option --- src/main/python/ddadevops/release_mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/release_mixin.py b/src/main/python/ddadevops/release_mixin.py index 373283a..62e7249 100644 --- a/src/main/python/ddadevops/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin.py @@ -22,7 +22,7 @@ def add_versions(config, release_version, bump_version) -> dict: # TODO: remove the config argument class ReleaseMixin(DevopsBuild): - def __init__(self, project: Project, config: Optional[dict] = None, release_config: Optional[ReleaseConfig] = None): + 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.") From 84cf82ac57a31e7e1f91f433d7bc55fb8508c950 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 21 Apr 2023 15:11:08 +0200 Subject: [PATCH 235/243] reformat --- .../infrastructure/release_mixin/repo.py | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py index 80bd461..c28c4cd 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py @@ -1,10 +1,18 @@ from typing import Optional -from src.main.python.ddadevops.domain import Release, Version, ReleaseType, EnvironmentKeys -from src.main.python.ddadevops.infrastructure.release_mixin import FileHandler, GitApi, EnvironmentApi +from src.main.python.ddadevops.domain import ( + Release, + Version, + ReleaseType, + EnvironmentKeys, +) +from src.main.python.ddadevops.infrastructure.release_mixin import ( + FileHandler, + GitApi, + EnvironmentApi, +) -class VersionRepository(): - +class VersionRepository: def __init__(self, file): self.file = file self.file_handler = None @@ -15,7 +23,7 @@ class VersionRepository(): def write_file(self, version_string): if self.file_handler is None: - raise Exception('Version was not created by load_file method.') + raise Exception("Version was not created by load_file method.") else: self.file_handler.write(version_string) @@ -24,7 +32,6 @@ class VersionRepository(): return version_list, is_snapshot def get_version(self) -> Version: - self.file_handler = self.load_file() version_list, is_snapshot = self.parse_file() version = Version(self.file, version_list) @@ -32,8 +39,13 @@ class VersionRepository(): return version -class ReleaseTypeRepository(): - def __init__(self, git_api: GitApi = GitApi(), environment_api: EnvironmentApi = EnvironmentApi()): + +class ReleaseTypeRepository: + def __init__( + self, + git_api: GitApi = GitApi(), + environment_api: EnvironmentApi = EnvironmentApi(), + ): self.git_api: GitApi = git_api self.environment_api: EnvironmentApi = environment_api self.get_from_git: bool = False @@ -41,7 +53,7 @@ class ReleaseTypeRepository(): @classmethod def from_git(cls, git_api: GitApi): - releaseTypeRepo = cls(git_api= git_api) + releaseTypeRepo = cls(git_api=git_api) releaseTypeRepo.get_from_git = True return releaseTypeRepo @@ -66,10 +78,14 @@ class ReleaseTypeRepository(): return None def __get_release_type_environment(self) -> ReleaseType | None: - release_name = self.environment_api.get(EnvironmentKeys.DDADEVOPS_RELEASE_TYPE.name) + release_name = self.environment_api.get( + EnvironmentKeys.DDADEVOPS_RELEASE_TYPE.name + ) if release_name is None: - raise ValueError("Release Name not found. Is the Environment correctly configured?") + raise ValueError( + "Release Name not found. Is the Environment correctly configured?" + ) elif ReleaseType.MAJOR.name in release_name.upper(): return ReleaseType.MAJOR elif ReleaseType.MINOR.name in release_name.upper(): @@ -87,13 +103,23 @@ class ReleaseTypeRepository(): elif self.get_from_env: return self.__get_release_type_environment() else: - raise ValueError('No valid api passed to ReleaseTypeRepository') + raise ValueError("No valid api passed to ReleaseTypeRepository") -class ReleaseRepository(): - def __init__(self, version_repository: VersionRepository, release_type_repository: ReleaseTypeRepository, main_branch: str): + +class ReleaseRepository: + def __init__( + self, + version_repository: VersionRepository, + release_type_repository: ReleaseTypeRepository, + main_branch: str, + ): self.version_repository = version_repository self.release_type_repository = release_type_repository self.main_branch = main_branch def get_release(self) -> Release: - return Release(self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch) + return Release( + self.release_type_repository.get_release_type(), + self.version_repository.get_version(), + self.main_branch, + ) From 92081947c5395a9102d3a4598b92029e27868837 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Fri, 21 Apr 2023 15:11:33 +0200 Subject: [PATCH 236/243] going to merge Release & ReleaseConfig [skip-ci] --- src/main/python/ddadevops/domain/release.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/python/ddadevops/domain/release.py b/src/main/python/ddadevops/domain/release.py index a2b1e0c..883bb20 100644 --- a/src/main/python/ddadevops/domain/release.py +++ b/src/main/python/ddadevops/domain/release.py @@ -74,15 +74,18 @@ class ReleaseConfig(Validateable): ): 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(): - def __init__(self, release_type: ReleaseType | None, version: Version, current_branch: str): +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 def release_version(self): return self.version.create_release_version(self.release_type) @@ -90,11 +93,9 @@ class Release(): def bump_version(self): return self.release_version().create_bump_version() - def validate(self, main_branch): + def validate(self): result = [] - if self.release_type is not None and main_branch != self.current_branch: + if self.release_type is not None and self.main_branch != self.current_branch: result.append(f"Releases are allowed only on {main_branch}") return result - def is_valid(self, main_branch): - return len(self.validate(main_branch)) < 1 # TODO: Use abstract is_valid in Validatable From 749c8f2849a3e38ed51b423108761d9eb87e2d65 Mon Sep 17 00:00:00 2001 From: Michael Jerger Date: Sat, 22 Apr 2023 12:40:40 +0000 Subject: [PATCH 237/243] 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 | 67 ++++++++++++------- .../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, 145 insertions(+), 124 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): - def __init__( - self, - 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): +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 - self.main_branch = main_branch - def release_version(self): + def release_version(self) -> Version: return self.version.create_release_version(self.release_type) - def bump_version(self): + def bump_version(self) -> Version: return self.release_version().create_bump_version() def validate(self): result = [] - if self.release_type is not None and self.main_branch != self.current_branch: + 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, + ): + self.devops = devops + 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.release_context.release_version() + + def bump_version(self): + return self.release_context.bump_version() + + + def validate(self): + result = [] + 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): From 3a83be485cda001ba0ad6abacfac1d5c285b5396 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 27 Apr 2023 14:46:15 +0200 Subject: [PATCH 238/243] Fix invalid code --- src/main/python/ddadevops/infrastructure/release_mixin/repo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py index efbc0f6..4ab3ae5 100644 --- a/src/main/python/ddadevops/infrastructure/release_mixin/repo.py +++ b/src/main/python/ddadevops/infrastructure/release_mixin/repo.py @@ -125,6 +125,6 @@ class ReleaseContextRepository: self.main_branch, ) if not result.is_valid(): - issues = '\n'.join.result.validate() + issues = '\n'.join(result.validate()) raise ValueError(f"invalid release: {issues}") return result From 35465b543b4f2ef9f534c08ffb2929873e43b5b7 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 27 Apr 2023 14:46:49 +0200 Subject: [PATCH 239/243] Make services stateless Resolves 2 TODOs --- .../application/release_mixin_services.py | 32 ++++++++----------- src/main/python/ddadevops/release_mixin.py | 13 ++++---- .../release_mixin/test_release_mixin.py | 4 +-- .../python/release_mixin/test_services.py | 16 +++------- 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/main/python/ddadevops/application/release_mixin_services.py b/src/main/python/ddadevops/application/release_mixin_services.py index 913478a..22c904c 100644 --- a/src/main/python/ddadevops/application/release_mixin_services.py +++ b/src/main/python/ddadevops/application/release_mixin_services.py @@ -1,36 +1,32 @@ -from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseContextRepository, GitApi -from src.main.python.ddadevops.domain import Version +from src.main.python.ddadevops.infrastructure.release_mixin import ReleaseContextRepository, VersionRepository, GitApi +from src.main.python.ddadevops.domain import Version, Release class PrepareReleaseService(): - def __init__(self, release_repo: ReleaseContextRepository): - self.release_repo = release_repo - self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func + def __init__(self): self.git_api = GitApi() - def __write_and_commit_version(self, version: Version, commit_message: str): - self.release.is_valid() + def __write_and_commit_version(self, release: Release, version_repository: VersionRepository, version: Version, commit_message: str): + 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) + version_repository.write_file(version.get_version_string()) + self.git_api.add_file(version_repository.file) self.git_api.commit(commit_message) - def write_and_commit_release(self): - self.__write_and_commit_version(self.release.release_version(), commit_message=f'Release v{self.release.release_version().get_version_string()}') + def write_and_commit_release(self, release: Release, version_repository: VersionRepository): + self.__write_and_commit_version(release, version_repository, release.release_version(), commit_message=f'Release v{release.release_version().get_version_string()}') - def write_and_commit_bump(self): - self.__write_and_commit_version(self.release.bump_version(), commit_message='Version bump') + def write_and_commit_bump(self, release: Release, version_repository: VersionRepository): + self.__write_and_commit_version(release, version_repository, release.bump_version(), commit_message='Version bump') class TagAndPushReleaseService(): - def __init__(self, git_api: GitApi, release_repo: ReleaseContextRepository): + def __init__(self, git_api: GitApi): self.git_api = git_api - self.release_repo = release_repo - self.release = release_repo.get_release() # TODO: make stateless: argument to receiving func - def tag_release(self): - annotation = 'v' + self.release.version.get_version_string() + def tag_release(self, release_repo: ReleaseContextRepository): + annotation = 'v' + release_repo.get_release().version.get_version_string() message = 'Release ' + annotation self.git_api.tag_annotated_second_last(annotation, message) diff --git a/src/main/python/ddadevops/release_mixin.py b/src/main/python/ddadevops/release_mixin.py index f10f847..c992a1f 100644 --- a/src/main/python/ddadevops/release_mixin.py +++ b/src/main/python/ddadevops/release_mixin.py @@ -21,15 +21,16 @@ class ReleaseMixin(DevopsBuild): release_type_repo = ReleaseTypeRepository.from_git(git_api) version_repo = VersionRepository(release.config_file) - release_repo = ReleaseContextRepository(version_repo, release_type_repo, release.main_branch) + self.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) + self.prepare_release_service = PrepareReleaseService() + self.tag_and_push_release_service = TagAndPushReleaseService(git_api) def prepare_release(self): - self.prepare_release_service.write_and_commit_release() - self.prepare_release_service.write_and_commit_bump() + release = self.release_repo.get_release() + self.prepare_release_service.write_and_commit_release(release, self.release_repo.version_repository) + self.prepare_release_service.write_and_commit_bump(release, self.release_repo.version_repository) def tag_and_push_release(self): - self.tag_and_push_release_service.tag_release() + self.tag_and_push_release_service.tag_release(self.release_repo) self.tag_and_push_release_service.push_release() diff --git a/src/test/python/release_mixin/test_release_mixin.py b/src/test/python/release_mixin/test_release_mixin.py index 78493aa..62a890d 100644 --- a/src/test/python/release_mixin/test_release_mixin.py +++ b/src/test/python/release_mixin/test_release_mixin.py @@ -45,7 +45,7 @@ def test_release_mixin_git(tmp_path: Path, monkeypatch: pt.MonkeyPatch): build = initialize_with_object(project, th.TEST_FILE_PATH) build.prepare_release() - release_version = build.prepare_release_service.release_repo.version_repository.get_version() + release_version = build.release_repo.version_repository.get_version() # test assert "124.0.1-SNAPSHOT" in release_version.get_version_string() @@ -71,7 +71,7 @@ def test_release_mixin_environment(tmp_path: Path, monkeypatch: pt.MonkeyPatch): build = initialize_with_object(project, th.TEST_FILE_PATH) build.prepare_release() - release_version = build.prepare_release_service.release_repo.version_repository.get_version() + release_version = build.release_repo.version_repository.get_version() # test assert "124.0.1-SNAPSHOT" in release_version.get_version_string() diff --git a/src/test/python/release_mixin/test_services.py b/src/test/python/release_mixin/test_services.py index 5aff9ac..35063d8 100644 --- a/src/test/python/release_mixin/test_services.py +++ b/src/test/python/release_mixin/test_services.py @@ -5,32 +5,26 @@ from src.test.python.release_mixin import MockGitApi def test_prepare_release_service(): # init mock_release_repo = MockReleaseRepository(MockVersionRepository(), MockReleaseTypeRepository(MockGitApi()), 'main') - prepare_release_service = PrepareReleaseService(mock_release_repo) + prepare_release_service = PrepareReleaseService() prepare_release_service.git_api = MockGitApi() - prepare_release_service.write_and_commit_release() + prepare_release_service.write_and_commit_release(mock_release_repo.get_release(), mock_release_repo.version_repository) #test - assert prepare_release_service.release_repo.get_release_count == 1 - assert prepare_release_service.release.validate_count == 1 - assert prepare_release_service.release_repo.version_repository.write_file_count == 1 assert prepare_release_service.git_api.add_file_count == 1 assert prepare_release_service.git_api.commit_count == 1 # init - prepare_release_service.write_and_commit_bump() + prepare_release_service.write_and_commit_bump(mock_release_repo.get_release(), mock_release_repo.version_repository) # test - assert prepare_release_service.release_repo.get_release_count == 1 - assert prepare_release_service.release.validate_count == 2 - assert prepare_release_service.release_repo.version_repository.write_file_count == 2 assert prepare_release_service.git_api.add_file_count == 2 assert prepare_release_service.git_api.commit_count == 2 def test_tag_and_push_release_service(): # init mock_release_repo = MockReleaseRepository(MockVersionRepository(), MockReleaseTypeRepository(MockGitApi()), 'main') - tag_and_push_release_service = TagAndPushReleaseService(MockGitApi(), mock_release_repo) - tag_and_push_release_service.tag_release() + tag_and_push_release_service = TagAndPushReleaseService(MockGitApi()) + tag_and_push_release_service.tag_release(mock_release_repo) tag_and_push_release_service.push_release() #test From a28a1b43d31bb03327ea2d4363e70dadebdf5553 Mon Sep 17 00:00:00 2001 From: bom Date: Thu, 27 Apr 2023 15:01:07 +0200 Subject: [PATCH 240/243] Stop mocking domain objects Resolves 1 TODO --- src/test/python/release_mixin/mock_domain.py | 43 ------------------- .../release_mixin/mock_infrastructure.py | 13 +++--- 2 files changed, 7 insertions(+), 49 deletions(-) delete mode 100644 src/test/python/release_mixin/mock_domain.py diff --git a/src/test/python/release_mixin/mock_domain.py b/src/test/python/release_mixin/mock_domain.py deleted file mode 100644 index bde9c4e..0000000 --- a/src/test/python/release_mixin/mock_domain.py +++ /dev/null @@ -1,43 +0,0 @@ -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): - self.id = None - self.version_list = None - self.version_string = None - self.is_snapshot = None - - def increment(self, release_type: ReleaseType): - pass - - def get_version_string(self) -> str: - return "" - - def create_release_version(self, release_type: ReleaseType): - return MockVersion() - - def create_bump_version(self): - return MockVersion() - -class MockRelease(): - def __init__(self, release_type, version, current_branch): - self.release_type = release_type - self.version = version - self.current_branch = current_branch - self.validate_count = 0 - - def release_version(self): - return self.version.create_release_version(self.release_type) - - def bump_version(self): - return self.release_version().create_bump_version() - - def validate(self): - self.validate_count += 1 - return [] - - def is_valid(self): - return len(self.validate()) < 1 diff --git a/src/test/python/release_mixin/mock_infrastructure.py b/src/test/python/release_mixin/mock_infrastructure.py index 9d7891f..81b09b5 100644 --- a/src/test/python/release_mixin/mock_infrastructure.py +++ b/src/test/python/release_mixin/mock_infrastructure.py @@ -1,6 +1,7 @@ -from src.main.python.ddadevops.domain import ReleaseType +from pathlib import Path + +from src.main.python.ddadevops.domain import ReleaseType, Version, ReleaseContext -from .mock_domain import MockRelease, MockVersion from .mock_infrastructure_api import MockGitApi class MockVersionRepository(): @@ -20,8 +21,8 @@ class MockVersionRepository(): def parse_file(self): pass - def get_version(self) -> MockVersion: - return MockVersion() + def get_version(self) -> Version: + return Version(Path(), [0,0,0]) class MockReleaseTypeRepository(): def __init__(self, mock_git_api: MockGitApi): @@ -37,6 +38,6 @@ class MockReleaseRepository(): self.main_branch = main_branch self.get_release_count = 0 - def get_release(self) -> MockRelease: + def get_release(self) -> ReleaseContext: self.get_release_count += 1 - return MockRelease(self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch) + return ReleaseContext(self.release_type_repository.get_release_type(), self.version_repository.get_version(), self.main_branch) From 349884cb6cfc66f4c4bcc6ce90371524b102f29b Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 28 Apr 2023 13:28:40 +0200 Subject: [PATCH 241/243] Refactor to C4kBuild --- src/main/python/ddadevops/__init__.py | 2 +- src/main/python/ddadevops/c4k_mixin.py | 4 +--- src/test/python/test_c4k_mixin.py | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index fc4363f..f62b93d 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -9,7 +9,7 @@ from .provs_k3s_mixin import ProvsK3sMixin, add_provs_k3s_mixin_config from .aws_rds_pg_mixin import AwsRdsPgMixin, add_aws_rds_pg_mixin_config from .aws_mfa_mixin import AwsMfaMixin, add_aws_mfa_mixin_config from .aws_backend_properties_mixin import AwsBackendPropertiesMixin, add_aws_backend_properties_mixin_config -from .c4k_mixin import C4kMixin, add_c4k_mixin_config +from .c4k_mixin import C4kBuild, add_c4k_mixin_config from .exoscale_mixin import ExoscaleMixin, add_exoscale_mixin_config from .digitalocean_backend_properties_mixin import DigitaloceanBackendPropertiesMixin, add_digitalocean_backend_properties_mixin_config from .digitalocean_terraform_build import DigitaloceanTerraformBuild, create_digitalocean_terraform_build_config diff --git a/src/main/python/ddadevops/c4k_mixin.py b/src/main/python/ddadevops/c4k_mixin.py index 61edc8c..8a5f3f2 100644 --- a/src/main/python/ddadevops/c4k_mixin.py +++ b/src/main/python/ddadevops/c4k_mixin.py @@ -44,9 +44,7 @@ def add_c4k_mixin_config( ) return config - -#TODO: refactor this to C4kBuild -class C4kMixin(DevopsBuild): +class C4kBuild(DevopsBuild): def __init__(self, project, config): super().__init__(project, config) self.execution_api = ExecutionApi() diff --git a/src/test/python/test_c4k_mixin.py b/src/test/python/test_c4k_mixin.py index 59cf436..c4c2b17 100644 --- a/src/test/python/test_c4k_mixin.py +++ b/src/test/python/test_c4k_mixin.py @@ -1,9 +1,9 @@ import os from pybuilder.core import Project from src.main.python.ddadevops.domain import DnsRecord -from src.main.python.ddadevops.c4k_mixin import C4kMixin, add_c4k_mixin_config +from src.main.python.ddadevops.c4k_mixin import C4kBuild, add_c4k_mixin_config -class MyC4kMixin(C4kMixin): +class MyC4kBuild(C4kBuild): pass def test_c4k_mixin(tmp_path): @@ -29,7 +29,7 @@ def test_c4k_mixin(tmp_path): assert project_config.get('C4kMixin') is not None - mixin = MyC4kMixin(project, project_config) + mixin = MyC4kBuild(project, project_config) mixin.initialize_build_dir() assert mixin.build_path() == f'{tmp_path_str}/{build_dir}/{project_name}/{module_name}' From 91a5964448e2cfd1db749cfe70936c311d9db653 Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 28 Apr 2023 13:32:10 +0200 Subject: [PATCH 242/243] Remove deprecated function --- src/main/python/ddadevops/__init__.py | 2 +- src/main/python/ddadevops/devops_build.py | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/main/python/ddadevops/__init__.py b/src/main/python/ddadevops/__init__.py index f62b93d..3cd3560 100644 --- a/src/main/python/ddadevops/__init__.py +++ b/src/main/python/ddadevops/__init__.py @@ -16,7 +16,7 @@ from .digitalocean_terraform_build import DigitaloceanTerraformBuild, create_dig from .hetzner_mixin import HetznerMixin, add_hetzner_mixin_config from .devops_image_build import DevopsImageBuild, create_devops_docker_build_config from .devops_terraform_build import DevopsTerraformBuild, create_devops_terraform_build_config -from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build, get_tag_from_latest_commit +from .devops_build import DevopsBuild, create_devops_build_config, get_devops_build from .credential import gopass_password_from_path, gopass_field_from_path from .release_mixin import ReleaseMixin diff --git a/src/main/python/ddadevops/devops_build.py b/src/main/python/ddadevops/devops_build.py index cc3f860..36ddb84 100644 --- a/src/main/python/ddadevops/devops_build.py +++ b/src/main/python/ddadevops/devops_build.py @@ -19,22 +19,6 @@ def create_devops_build_config( def get_devops_build(project): return project.get_property("devops_build") - -@deprecation.deprecated(deprecated_in="3.2") -# TODO: Remove from here! # pylint: disable=W0511 -def get_tag_from_latest_commit(): - try: - value = run( - "git describe --abbrev=0 --tags --exact-match", - shell=True, - capture_output=True, - check=True, - ) - return value.stdout.decode("UTF-8").rstrip() - except CalledProcessError: - return None - - class DevopsBuild: def __init__(self, project, config: Optional[dict] = None, devops: Optional[Devops] = None): self.project = project From 2ad63b8a753464d225224b326bea8fbb66b694ea Mon Sep 17 00:00:00 2001 From: erik Date: Fri, 28 Apr 2023 13:32:40 +0200 Subject: [PATCH 243/243] Remove unused files --- src/test/resources/alt_config.json | 4 --- src/test/resources/build.py | 49 ------------------------------ 2 files changed, 53 deletions(-) delete mode 100644 src/test/resources/alt_config.json delete mode 100644 src/test/resources/build.py diff --git a/src/test/resources/alt_config.json b/src/test/resources/alt_config.json deleted file mode 100644 index 359f087..0000000 --- a/src/test/resources/alt_config.json +++ /dev/null @@ -1,4 +0,0 @@ -// TODO: jem, zam - 2023_04_18: move this to an build-test repo ? -{ - "version": "123.125.1-SNAPSHOT" -} \ No newline at end of file diff --git a/src/test/resources/build.py b/src/test/resources/build.py deleted file mode 100644 index 641ae3a..0000000 --- a/src/test/resources/build.py +++ /dev/null @@ -1,49 +0,0 @@ -# TODO: jem, zam - 2023_04_18: move this to an build-test repo? - -import sys -import os -from pathlib import Path -from ddadevops import * - -# getting the name of the directory -# where the this file is present. -current = os.path.dirname(os.path.realpath(__file__)) - -# adding the current directory to -# the sys.path. -sys.path.append(current) - -# now we can import the module in the current -# directory. - -from pybuilder.core import task, init -from ddadevops import * -from release_mixin import ReleaseMixin, create_release_mixin_config - -CONFIG_FILE = Path('config.json') -MAIN_BRANCH = 'main' -STAGE = 'test' -PROJECT_ROOT_PATH = '.' -MODULE = 'test' -BUILD_DIR_NAME = "build_dir" - -class MyBuild(ReleaseMixin): - pass - -@init -def initialize(project): - project.build_depends_on('ddadevops>=3.1.2') - config = create_release_mixin_config(CONFIG_FILE, MAIN_BRANCH) - config.update({'stage': STAGE, - 'module': MODULE, - 'project_root_path': PROJECT_ROOT_PATH, - 'build_dir_name': BUILD_DIR_NAME}) - build = MyBuild(project, config) - build.initialize_build_dir() - -@task -def release(project): - build = get_devops_build(project) - - build.prepare_release() - build.tag_and_push_release()