Compare commits

..

25 commits

Author SHA1 Message Date
3bc72f5bb8 bump version to: 4.13.2-dev 2024-08-26 18:59:29 +02:00
e9fdfdf520 release: 4.13.1 2024-08-26 18:59:29 +02:00
7fa2a8056d fix provision over dns 2024-08-26 18:59:11 +02:00
dfb46d76a5 bump version to: 4.13.1-dev 2024-08-22 08:25:43 +02:00
39c6b95af8 release: 4.13.0 2024-08-22 08:25:43 +02:00
0935eae193 pylintno longer works in our setup 2024-08-22 08:25:33 +02:00
58a1b005e9 add deps.edn 2024-08-22 08:16:41 +02:00
bba684d76e add deps.edn 2024-08-22 08:15:24 +02:00
ec150dde62 remove backup image 2024-08-21 17:46:12 +02:00
76d4ad16dc doc 2024-08-21 16:57:16 +02:00
126ae37845 backup integration test with bb - init starts to work 2024-08-16 14:41:39 +02:00
e9f1915655 Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/dda-devops-build 2024-08-06 14:41:45 +02:00
7448333d7c [Skip-CI] Fix mastodon add website link 2024-08-06 14:41:38 +02:00
bom
e8555798e4 bump version to: 4.12.2-dev 2024-06-28 12:01:48 +02:00
bom
cf4d1e450c release: 4.12.1 2024-06-28 12:01:48 +02:00
bom
a661eaf3ca Add optional release tag prefix
Used for go modules
2024-06-28 12:00:54 +02:00
f4da27f63f Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/dda-devops-build 2024-05-22 15:45:36 +02:00
e764534487 sequence chart retsic-management.clj 2024-05-22 15:42:55 +02:00
bom
6093d160e8 bump version to: 4.12.1-dev 2024-05-10 15:25:58 +02:00
bom
4d8dc95d8e release: 4.12.0 2024-05-10 15:25:58 +02:00
bom
e6f39eab21 Add support for hetzner csi 2024-05-10 15:24:29 +02:00
0cb4bc43f9 store state passwords in map & edn 2024-04-17 11:37:13 +02:00
5b5bc0ab96 install restic_management.clj to image 2024-04-17 11:36:24 +02:00
b139865f89 bump version to: 4.11.11-dev 2024-04-17 09:26:00 +02:00
e4fa06fc42 release: 4.11.10 2024-04-17 09:26:00 +02:00
28 changed files with 246 additions and 47 deletions

View file

@ -87,10 +87,3 @@ kotlin-image-publish:
stage: image
script:
- cd infrastructure/kotlin && pyb image publish
backup-image-publish:
<<: *img
<<: *tag_only
stage: image
script:
- cd infrastructure/backup && pyb image publish

View file

@ -1,6 +1,6 @@
# dda-devops-build
[![Slack](https://img.shields.io/badge/chat-clojurians-green.svg?style=flat)](https://clojurians.slack.com/messages/#dda-pallet/) | [<img src="https://meissa-gmbh.de/img/community/Mastodon_Logotype.svg" width=20 alt="team@social.meissa-gmbh.de"> team@social.meissa-gmbh.de](https://social.meissa-gmbh.de/@team) | [Website & Blog](https://domaindrivenarchitecture.org)
[![Slack](https://img.shields.io/badge/chat-clojurians-green.svg?style=flat)](https://clojurians.slack.com/messages/#dda-pallet/) | [<img src="https://domaindrivenarchitecture.org/img/delta-chat.svg" width=20 alt="DeltaChat"> chat over e-mail](mailto:buero@meissa-gmbh.de?subject=community-chat) | [<img src="https://meissa.de/images/parts/contact/mastodon36_hue9b2464f10b18e134322af482b9c915e_5501_filter_14705073121015236177.png" width=20 alt="M"> meissa@social.meissa-gmbh.de](https://social.meissa-gmbh.de/@meissa) | [Blog](https://domaindrivenarchitecture.org) | [Website](https://meissa.de)
dda-devops-build integrates all the tools we use to work with clouds & provide some nice functions around.

View file

@ -33,7 +33,7 @@ default_task = "dev"
name = "ddadevops"
MODULE = "not-used"
PROJECT_ROOT_PATH = "."
version = "4.11.10-dev"
version = "4.13.2-dev"
summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud"
description = __doc__
authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")]
@ -97,7 +97,6 @@ def initialize(project):
"mixin_types": ["RELEASE"],
"release_primary_build_file": "build.py",
"release_secondary_build_files": [
"infrastructure/backup/build.py",
"infrastructure/python/build.py",
"infrastructure/dind/build.py",
"infrastructure/ddadevops/build.py",
@ -141,12 +140,6 @@ def lint(project):
shell=True,
check=True,
)
run(
"pylint -d W0511,R0903,C0301,W0614,C0114,C0115,C0116,similarities,W1203,W0702,W0702,"
+ "R0913,R0902,R0914,R1732,R1705,W0707,C0123,W0703,C0103 src/main/python/ddadevops/",
shell=True,
check=True,
)
@task

View file

@ -13,14 +13,17 @@ classDiagram
## Input
| name | description | default |
| ----------------------------- | ----------------------------------------------------------------- | --------- |
| k3s_provision_user | the user used to provision k3s | "root" |
| k3s_letsencrypt_email | email address used for letsencrypt | |
| k3s_letsencrypt_endpoint | letsencrypt endpoint. Valid values are staging, prod | "staging" |
| k3s_app_filename_to_provision | an k8s manifest to apply imediately after k3s setup was sucessful | |
| k3s_enable_echo | provision the echo app on k3s. Valid values are true, false | "false" |
| k3s_provs_template | use a individual template for provs config | None |
| name | description | default |
| --------------------------------- | ----------------------------------------------------------------- | --------- |
| k3s_provision_user | the user used to provision k3s | "root" |
| k3s_letsencrypt_email | email address used for letsencrypt | |
| k3s_letsencrypt_endpoint | letsencrypt endpoint. Valid values are staging, prod | "staging" |
| k3s_app_filename_to_provision | an k8s manifest to apply imediately after k3s setup was sucessful | |
| k3s_enable_echo | provision the echo app on k3s. Valid values are true, false | "false" |
| k3s_provs_template | use a individual template for provs config | None |
| k3s_enable_hetzner_csi | enable hetzner csi | False |
| k3s_hetzner_api_token | hetzner_api_token | None |
| k3s_hetzner_encryption_passphrase | encryption passphrase for volumes | None |
### Credentials Mapping defaults

View file

@ -35,6 +35,7 @@ classDiagram
| release_organisation | Optional: The repository organisation name | |
| release_repository_name | Optional: The repository name name | |
| release_artifacts | Optional: The list of artifacts to publish to the release generated name | [] |
| release_tag_prefix | Optional: Prefix of tag | "" |
## Example Usage just for creating releases

View file

@ -7,7 +7,7 @@ import logging
name = 'dda-backup'
MODULE = 'NOT_SET'
PROJECT_ROOT_PATH = '../..'
version = "4.11.10-dev"
version = "4.12.2-dev"
@init
@ -38,6 +38,11 @@ def image(project):
build = get_devops_build(project)
build.image()
@task
def test(project):
build = get_devops_build(project)
build.test()
@task
def drun(project):
build = get_devops_build(project)

View file

@ -42,3 +42,38 @@ stateDiagram-v2
3. mounten von angelegtem Secret in Pod backup-restore
4. ba*bash*ka Skript in pod starten -> liest Secret ?leer
5. Micha cons.
```mermaid
sequenceDiagram
participant k8s
participant e as entrypoint.sh
participant rm as restic-management.clj
k8s ->> e: cronjob calls
e ->> rm: start-file
rm ->> rm: rotate
activate rm
rm ->> rm: read-backup-repository-state (state)
rm ->> rm: read-secret (backup-secret/restic-password, rotation-credential-secret/rotation-credential)
rm ->> rm: switch
activate rm
rm ->> rm: if init && restic-password != null
activate rm
rm ->> rm: init.sh
rm ->> rm: state init -> backup-ready
deactivate rm
rm ->> rm: if backup-ready && rotation-credential != null
activate rm
rm ->> rm: add-new-password-to-restic-repository.sh
rm ->> rm: state backup-ready -> new-password-added
deactivate rm
rm ->> rm: if new-password-added && rotation-credential == null
activate rm
rm ->> rm: remove-old-password-from-restic-repository.sh
rm ->> rm: state new-password-added -> backup-ready
deactivate rm
deactivate rm
rm ->> rm: store-repository-state (state)
deactivate rm
```

View file

@ -42,6 +42,7 @@ function backup-fs-from-directory() {
}
# Das tut so nicht!
function restore-directory() {
local directory="$1"; shift
local snapshot_id="${1:-latest}"; shift

View file

@ -27,6 +27,7 @@ function main() {
install -m 0400 /tmp/functions.sh /usr/local/lib/
install -m 0400 /tmp/pg-functions.sh /usr/local/lib/
install -m 0400 /tmp/file-functions.sh /usr/local/lib/
install -m 0740 /tmp/restic_management.clj /usr/local/bin/
cleanupDocker
}

View file

@ -26,8 +26,26 @@
(catch RuntimeException e
(printf "Error parsing edn file '%s': %s\n" "backup-repository-state.edn" (.getMessage e)))))
(println (read-backup-repository-state))
(defn read-secret [s]
(slurp (str "/var/run/secrets/" s)))
;"/var/run/secrets/rotation-credential-secret/rotation-credential"))
(println (:state (read-backup-repository-state)))
;(println (read-backup-repository-state))
(println (s/valid? ::backup-repository-state (read-backup-repository-state)))
;(println (:state (read-backup-repository-state)))
;(println (s/valid? ::backup-repository-state (read-backup-repository-state)))
(println (read-secret "rotation-credential-secret/rotation-credential"))
(println (read-secret "backup-secrets/restic-password"))
(s/def ::new-password string?)
(s/def ::old-password string?)
(s/def ::password-state
(s/keys :req-un [::new-password ::old-password]))
(defn rotate []
(let [state {:new-password (read-secret "rotation-credential-secret/rotation-credential")
:old-password (read-secret "backup-secrets/restic-password")}]
(store-backup-repository-state (prn-str state))))
(rotate)

View file

@ -0,0 +1,7 @@
FROM dda-backup:latest
# install it
RUN apt update && apt install -qqy openjdk-17-jre-headless
ADD resources /tmp/
RUN rm -rf /root/.m2
RUN /tmp/install-test.bb

View file

@ -0,0 +1,4 @@
{:deps {org.clojure/spec.alpha {:mvn/version "0.4.233"}
orchestra/orchestra {:mvn/version "2021.01.01-1"}
org.domaindrivenarchitecture/dda-backup {:mvn/version "0.1.1-SNAPSHOT"}}}

View file

@ -0,0 +1,32 @@
#!/usr/bin/env bb
(require '[babashka.tasks :as tasks])
(defn curl-and-check!
[filename artifact-url sha256-url]
(let [filepath (str "/tmp/" filename)]
(tasks/shell "curl" "-SsLo" filepath artifact-url)
(tasks/shell "curl" "-SsLo" "/tmp/checksum" sha256-url)
(tasks/shell "bash" "-c" (str "echo \" " filepath "\"|tee -a /tmp/checksum"))
;(tasks/shell "sha256sum" "-c" "--status" "/tmp/checksum")
))
(defn tar-install!
[filename binname]
(let [filepath (str "/tmp/" filename)]
(tasks/shell "tar" "-C" "/tmp" "-xzf" filepath)
(tasks/shell "install" "-m" "0700" "-o" "root" "-g" "root" (str "/tmp/" binname) "/usr/local/bin/")))
(defn install!
[filename]
(tasks/shell "install" "-m" "0700" "-o" "root" "-g" "root" (str "/tmp/" filename) "/usr/local/bin/"))
(tasks/shell "bb" "/tmp/test.bb")
(curl-and-check!
"provs-syspec.jar"
"https://repo.prod.meissa.de/attachments/0a1da41e-aa5b-4a3e-a3b1-215cf2d5b021"
"https://repo.prod.meissa.de/attachments/f227cf65-cb0f-46a7-a6cd-28f46917412a")
(install! "provs-syspec.jar")
(tasks/shell "apt" "update")
(tasks/shell "apt" "install" "-qqy" "openjdk-17-jre-headless")
(tasks/shell "java" "-jar" "/usr/local/bin/provs-syspec.jar" "local" "-c" "/tmp/spec.yml" )

View file

@ -0,0 +1,7 @@
package:
- name: "restic"
command:
- command: "bb -h"
- command: "/tmp/test.bb"

View file

@ -0,0 +1,27 @@
#!/usr/bin/env bb
(require '[babashka.tasks :as tasks]
'[dda.backup.management :as mgm])
(defn restic-repo-init!
[]
(spit "restic-pwd" "ThePassword")
(mgm/init! {:password-file "restic-pwd"
:restic-repository "restic-repo"}))
(defn restic-backup!
[]
(tasks/shell "mkdir" "test-backup")
(spit "test-backup/file" "I was here")
(tasks/shell "restic" "backup" "--password-file" "restic-pwd" "--repo" "restic-repo" "test-backup"))
(defn restic-restore!
[]
(tasks/shell "mkdir" "test-restore")
(tasks/shell "restic" "restore" "--password-file" "restic-pwd" "--repo" "restic-repo" "--target" "test-restore" "latest")
)
(restic-repo-init!)
(restic-backup!)
(restic-restore!)

View file

@ -6,7 +6,7 @@ from ddadevops import *
name = "ddadevops"
MODULE = "clj-cljs"
PROJECT_ROOT_PATH = "../.."
version = "4.11.10-dev"
version = "4.13.2-dev"
@init
def initialize(project):

View file

@ -6,7 +6,7 @@ from ddadevops import *
name = "ddadevops"
MODULE = "clj"
PROJECT_ROOT_PATH = "../.."
version = "4.11.10-dev"
version = "4.13.2-dev"
@init
def initialize(project):

View file

@ -6,7 +6,7 @@ from ddadevops import *
name = "ddadevops"
MODULE = "ddadevops"
PROJECT_ROOT_PATH = "../.."
version = "4.11.10-dev"
version = "4.13.2-dev"
@init

View file

@ -6,7 +6,7 @@ from ddadevops import *
name = "ddadevops"
MODULE = "dind"
PROJECT_ROOT_PATH = "../.."
version = "4.11.10-dev"
version = "4.13.2-dev"
@init

View file

@ -6,7 +6,7 @@ from ddadevops import *
name = "ddadevops"
MODULE = "kotlin"
PROJECT_ROOT_PATH = "../.."
version = "4.11.10-dev"
version = "4.13.2-dev"
@init

View file

@ -6,7 +6,7 @@ from ddadevops import *
name = "ddadevops"
MODULE = "python"
PROJECT_ROOT_PATH = "../.."
version = "4.11.10-dev"
version = "4.13.2-dev"
@init

View file

@ -53,7 +53,8 @@ class ReleaseService:
bump_version = release_version.create_bump()
release_message = f"release: {release_version.to_string()}"
bump_message = f"bump version to: {bump_version.to_string()}"
self.git_api.tag_annotated(release_version.to_string(), release_message, 0)
release_tag = f"{release.release_tag_prefix}{release_version.to_string()}"
self.git_api.tag_annotated(release_tag, release_message, 0)
self.__set_version_and_commit__(
bump_version,
release.build_files(),

View file

@ -11,6 +11,7 @@ class BuildFileType(Enum):
JS = ".json"
JAVA_GRADLE = ".gradle"
JAVA_CLOJURE = ".clj"
JAVA_CLOJURE_EDN = ".edn"
PYTHON = ".py"
@ -41,6 +42,8 @@ class BuildFile(Validateable):
result = BuildFileType.JAVA_CLOJURE
case ".py":
result = BuildFileType.PYTHON
case ".edn":
result = BuildFileType.JAVA_CLOJURE_EDN
case _:
result = None
return result
@ -48,11 +51,13 @@ class BuildFile(Validateable):
def __get_file_type_regex_str(self, file_type: BuildFileType):
match file_type:
case BuildFileType.JAVA_GRADLE:
return r'(?P<pre_version>\bversion\s?=\s?)\"(?P<version>\d*\.\d*\.\d*(-SNAPSHOT)?)\"'
return r"(?P<pre_version>\bversion\s?=\s?)\"(?P<version>\d*\.\d*\.\d*(-SNAPSHOT)?)\""
case BuildFileType.PYTHON:
return r'(?P<pre_version>\bversion\s?=\s?)\"(?P<version>\d*\.\d*\.\d*(-SNAPSHOT|-dev\d*)?)\"'
return r"(?P<pre_version>\bversion\s?=\s?)\"(?P<version>\d*\.\d*\.\d*(-SNAPSHOT|-dev\d*)?)\""
case BuildFileType.JAVA_CLOJURE:
return r'(?P<pre_version>\(defproject\s(\S)*\s)\"(?P<version>\d*\.\d*\.\d*(-SNAPSHOT)?)\"'
return r"(?P<pre_version>\(defproject\s(\S)*\s)\"(?P<version>\d*\.\d*\.\d*(-SNAPSHOT)?)\""
case BuildFileType.JAVA_CLOJURE_EDN:
return r"(?P<pre_version>\:version\s+)\"(?P<version>\d*\.\d*\.\d*(-SNAPSHOT)?)\""
case _:
return ""
@ -62,8 +67,15 @@ class BuildFile(Validateable):
match build_file_type:
case BuildFileType.JS:
version_str = json.loads(self.content)["version"]
case BuildFileType.JAVA_GRADLE | BuildFileType.PYTHON | BuildFileType.JAVA_CLOJURE:
version_str = re.search(self.__get_file_type_regex_str(build_file_type), self.content).group("version")
case (
BuildFileType.JAVA_GRADLE
| BuildFileType.PYTHON
| BuildFileType.JAVA_CLOJURE
| BuildFileType.JAVA_CLOJURE_EDN
):
version_str = re.search(
self.__get_file_type_regex_str(build_file_type), self.content
).group("version")
except:
raise RuntimeError(f"Version not found in file {self.file_path}")
@ -84,10 +96,15 @@ class BuildFile(Validateable):
json_data = json.loads(self.content)
json_data["version"] = new_version.to_string()
self.content = json.dumps(json_data, indent=4)
case BuildFileType.JAVA_GRADLE | BuildFileType.PYTHON | BuildFileType.JAVA_CLOJURE:
case (
BuildFileType.JAVA_GRADLE
| BuildFileType.PYTHON
| BuildFileType.JAVA_CLOJURE
| BuildFileType.JAVA_CLOJURE_EDN
):
substitute = re.sub(
self.__get_file_type_regex_str(build_file_type),
fr'\g<pre_version>"{new_version.to_string()}"',
rf'\g<pre_version>"{new_version.to_string()}"',
self.content,
1,
)

View file

@ -78,6 +78,12 @@ class DnsRecord(Validateable):
result.append("ipv4 & ipv6 may not both be empty.")
return result
def ip(self) -> str:
if (self.ipv4):
return self.ipv4
else:
return self.ipv6
class Devops(Validateable):
def __init__(

View file

@ -20,6 +20,14 @@ CONFIG_CERTMANAGER = """certmanager:
"""
CONFIG_ECHO = """echo: $echo
"""
CONFIG_HETZNER_CSI = """hetzner:
hcloudApiToken:
source: "PLAIN" # PLAIN, GOPASS or PROMPT
parameter: $hcloud_api # the api key for the hetzner cloud
encryptionPassphrase:
source: "PLAIN" # PLAIN, GOPASS or PROMPT
parameter: $encryption # the encryption passphrase for created volumes
"""
class K3s(Validateable):
@ -28,8 +36,11 @@ class K3s(Validateable):
self.k3s_letsencrypt_email = inp.get("k3s_letsencrypt_email")
self.k3s_letsencrypt_endpoint = inp.get("k3s_letsencrypt_endpoint", "staging")
self.k3s_app_filename_to_provision = inp.get("k3s_app_filename_to_provision")
self.k3s_enable_echo = inp.get("k3s_enable_echo", "false")
self.k3s_enable_echo = inp.get("k3s_enable_echo", None)
self.k3s_provs_template = inp.get("k3s_provs_template", None)
self.k3s_enable_hetzner_csi = inp.get("k3s_enable_hetzner_csi", False)
self.k3s_hetzner_api_token = inp.get("k3s_hetzner_api_token", None)
self.k3s_hetzner_encryption_passphrase = inp.get("k3s_hetzner_encryption_passphrase", None)
self.provision_dns: Optional[DnsRecord] = None
def validate(self) -> List[str]:
@ -37,6 +48,9 @@ class K3s(Validateable):
result += self.__validate_is_not_empty__("k3s_letsencrypt_email")
result += self.__validate_is_not_empty__("k3s_letsencrypt_endpoint")
result += self.__validate_is_not_empty__("k3s_app_filename_to_provision")
if self.k3s_enable_hetzner_csi:
result += self.__validate_is_not_empty__("k3s_hetzner_api_token")
result += self.__validate_is_not_empty__("k3s_hetzner_encryption_passphrase")
if self.provision_dns:
result += self.provision_dns.validate()
return result
@ -61,6 +75,9 @@ class K3s(Validateable):
substitutes["letsencrypt_endpoint"] = self.k3s_letsencrypt_endpoint
if self.k3s_enable_echo is not None:
substitutes["echo"] = self.k3s_enable_echo
if self.k3s_enable_hetzner_csi:
substitutes["hcloud_api"] = self.k3s_hetzner_api_token
substitutes["encryption"] = self.k3s_hetzner_encryption_passphrase
return self.__config_template__().substitute(substitutes)
def command(self, devops: Devops):
@ -69,7 +86,7 @@ class K3s(Validateable):
cmd = [
"provs-server.jar",
"k3s",
f"{self.k3s_provision_user}@{self.provision_dns.fqdn}",
f"{self.k3s_provision_user}@{self.provision_dns.ip()}",
"-c",
f"{devops.build_path()}/out_k3sServerConfig.yaml",
"-a",
@ -89,4 +106,6 @@ class K3s(Validateable):
template_text += CONFIG_IPV4
if self.provision_dns.ipv6 is not None:
template_text += CONFIG_IPV6
if self.k3s_enable_hetzner_csi:
template_text += CONFIG_HETZNER_CSI
return Template(template_text)

View file

@ -24,6 +24,7 @@ class Release(Validateable):
"release_secondary_build_files", []
)
self.version = version
self.release_tag_prefix = inp.get("release_tag_prefix", "")
self.release_artifact_server_url = inp.get("release_artifact_server_url")
self.release_organisation = inp.get("release_organisation")
self.release_repository_name = inp.get("release_repository_name")

View file

@ -183,6 +183,34 @@ def test_should_parse_and_set_version_for_clj():
== sut.content
)
def test_should_parse_and_set_version_for_clj_edn():
sut = BuildFile(
Path("./deps.edn"),
"""
{:project {:name org.domaindrivenarchitecture/dda-backup
:version "1.1.5-SNAPSHOT"}
}
""",
)
assert sut.get_version() == Version.from_str("1.1.5-SNAPSHOT", "SNAPSHOT")
sut = BuildFile(
Path("./deps.edn"),
"""
{:project {:name org.domaindrivenarchitecture/dda-backup
:version "1.1.5-SNAPSHOT"}
}
""",
)
sut.set_version(Version.from_str("1.1.5-SNAPSHOT", "SNAPSHOT").create_major())
assert (
'\n{:project {:name org.domaindrivenarchitecture/dda-backup\n :version "2.0.0"}\n\n}\n'
== sut.content
)
def test_should_throw_for_clj_wrong_version():
sut = BuildFile(
Path("./project.clj"),

View file

@ -24,7 +24,7 @@ def test_should_calculate_command():
assert (
"provs-server.jar "
+ "k3s "
+ "k3s_provision_user@example.org "
+ "k3s_provision_user@::1 "
+ "-c "
+ "root_path/target/name/module/out_k3sServerConfig.yaml "
+ "-a "