Compare commits

..

100 commits

Author SHA1 Message Date
ef2efc40f7 updates for kotlin build 2023-10-25 09:18:41 +02:00
b3a612c938 Merge branch 'main' into kotlin-compile 2023-10-25 09:15:40 +02:00
Clemens
edd2ae5743 bump version to: 4.7.5-dev 2023-10-13 09:51:23 +02:00
Clemens
fc92260a43 release: 4.7.4 2023-10-13 09:51:23 +02:00
7e5e66d933 fix doc 2023-09-29 09:52:37 +02:00
9fdd81d4b0 bump version to: 4.7.4-dev 2023-09-22 18:08:43 +02:00
5d81903870 release: 4.7.3 2023-09-22 18:08:42 +02:00
d643bba325 add some cleanup 2023-09-22 17:49:37 +02:00
4c0524aafe bump version to: 4.7.3-dev 2023-09-22 17:46:58 +02:00
1db263d13f release: 4.7.2 2023-09-22 17:46:58 +02:00
58bfd98af9 bump version to: 4.7.2-dev 2023-09-22 17:38:34 +02:00
d3e8c19f02 release: 4.7.1 2023-09-22 17:38:34 +02:00
bdca4f224f add new function to doc 2023-08-17 18:28:17 +02:00
331c57a952 gitlab ci is no longer used 2023-08-17 18:25:59 +02:00
cea54b0945 added doc for creating artifacts 2023-08-17 18:23:33 +02:00
d8396402b5 bootstrap with dev no longer needed 2023-08-17 17:28:01 +02:00
1afa34dba3 required for releasing 2023-08-17 17:25:55 +02:00
a5923aef5f use released build-container 2023-08-17 17:25:42 +02:00
c669d8f7b5 bump version to: 4.7.1-dev 2023-08-17 17:15:18 +02:00
22397a369e release: 4.7.0 2023-08-17 17:15:18 +02:00
d5c711fa88 use current build container 2023-08-17 17:14:59 +02:00
63a043dfee bump version to: 4.6.1-dev 2023-08-17 17:14:27 +02:00
3ccac186c9 release: 4.6.0 2023-08-17 17:14:27 +02:00
686c703f7d minor cleanup 2023-08-17 17:10:29 +02:00
17d93c34ec adjust for buildtest 2023-08-17 17:10:13 +02:00
95ca351ae8 bump version to: 4.5.5-dev 2023-08-16 18:36:31 +02:00
d15195a8e0 release: 4.5.4 2023-08-16 18:36:30 +02:00
b3841a6801 bump version to: 4.5.4-dev 2023-08-16 18:30:56 +02:00
3d83ec19e1 release: 4.5.3 2023-08-16 18:30:56 +02:00
e7000ec408 Handover the corect token? 2023-08-16 18:29:54 +02:00
bf74fd1a73 bump version to: 4.5.3-dev 2023-08-16 18:19:16 +02:00
954a2f735b release: 4.5.2 2023-08-16 18:19:16 +02:00
f8ec35860a add more debugging info 2023-08-16 18:18:40 +02:00
d67dfb7378 bump version to: 4.5.2-dev 2023-08-16 17:57:45 +02:00
6dc10b77e7 release: 4.5.1 2023-08-16 17:57:45 +02:00
b77a8fd67d add curl to py build image 2023-08-16 17:57:21 +02:00
951e66776d bump version to: 4.5.1-dev 2023-08-16 17:45:51 +02:00
1a946151b7 release: 4.5.0 2023-08-16 17:45:51 +02:00
8132958b16 preparing bugfix release 2023-08-16 17:45:21 +02:00
fea084c09a fix escaping 2023-08-16 17:09:24 +02:00
ad6287aafe bump version to: 4.4.1-dev 2023-08-16 16:31:56 +02:00
509216c001 release: 4.4.0 2023-08-16 16:31:55 +02:00
62e3f58f81 Merge branch 'artifact-mixin' into 'main'
Release creation on forgejo targets

See merge request domaindrivenarchitecture/dda-devops-build!18
2023-08-16 14:28:27 +00:00
898b8e8900 use new version 2023-08-16 16:21:12 +02:00
17ae55f3c0 fix drun & improve env rekognition 2023-08-16 16:10:56 +02:00
ec0844d53b use new ddadevops 2023-08-16 15:42:59 +02:00
b6b2e6d9b5 add env for all jobs 2023-08-16 15:25:57 +02:00
46b8172f11 Use v4.3.1 2023-08-16 14:15:38 +02:00
d160c551e1 Use v4.3.0 2023-08-16 14:13:15 +02:00
1f2e1d9569 Use v4.2.0 2023-08-16 14:07:52 +02:00
66691c9ee9 Add task publish artifacts 2023-08-16 13:57:15 +02:00
dce22ba7b3 Remove publish_artifact task for test purpose 2023-08-16 13:54:28 +02:00
25887841a2 Add missing var def 2023-08-16 12:48:37 +02:00
57b88664d2 Rename vars 2023-08-16 12:11:08 +02:00
ddb173af46 Change attachment type to path 2023-08-16 12:10:47 +02:00
a9d01c5907 Assert that token is a string 2023-08-16 12:10:02 +02:00
46c8a1751c Rename var 2023-08-16 12:09:06 +02:00
dea2d7f3ed Ignore line too long 2023-08-16 10:40:42 +02:00
06a650fd77 use new version for ci 2023-08-15 08:28:15 +02:00
1fff46986e create an empty release 2023-08-15 08:23:00 +02:00
ab8bb7f400 allow empty artifacts 2023-08-15 08:21:06 +02:00
2be0a44aa8 add js 2023-08-15 08:09:27 +02:00
7d2d197cbb fix some linting 2023-08-14 20:41:04 +02:00
2e24e79a4c introduce artifact class 2023-08-14 20:39:59 +02:00
d555b34eef add artifact to domain 2023-08-14 19:06:57 +02:00
f9daf87262 remove no longer neede artifact deployment 2023-08-14 18:58:51 +02:00
7aa45910e9 finish register artifacts 2023-08-14 18:55:30 +02:00
45884d1032 wip: prepare ataching artifacts - fix the test 2023-08-14 09:40:25 +02:00
bfcdcbb78a wip: prepare ataching artifacts 2023-08-14 09:31:01 +02:00
caaf804fd3 add release-endpoint-calculation to domain object 2023-08-14 09:15:46 +02:00
a876fdc799 add error handling for release_id parsing 2023-08-14 09:10:31 +02:00
e0150e6fcc parse the release_id from create response 2023-08-14 08:59:45 +02:00
b861087e9d fix test no longer needs gopass 2023-08-14 08:24:42 +02:00
700a0a2f4f add missing informations for creating release 2023-08-11 16:17:42 +02:00
ee58151a8d initialize default credentials 2023-08-11 16:17:07 +02:00
f30609288a add token & improve artifact validation 2023-08-11 16:16:25 +02:00
2217e5c8d1 add token & improve artifact validation 2023-08-11 16:16:03 +02:00
3cfb453454 improve some linting 2023-08-11 15:54:15 +02:00
5d2596bd3f improve some linting 2023-08-11 15:49:25 +02:00
cb41ad0719 update domain doc 2023-08-11 15:37:07 +02:00
db8b41be19 Add comments for moving functionality 2023-08-11 15:29:07 +02:00
f4be6e0c8b Implement calculation of forgejo release api endpoint url 2023-08-11 15:27:07 +02:00
5f5743354c wip: calculate forgejo_release_api_endpoint 2023-08-11 15:08:21 +02:00
2469fd2f5b [Skip-CI] Rename function 2023-08-11 14:46:25 +02:00
39591c8aa9 [Skip-CI] WIP Implement release publishing 2023-08-11 14:45:43 +02:00
337a790044 Implement checksum calculation 2023-08-11 14:38:41 +02:00
935baa9932 Simpler sha calc command 2023-08-11 14:35:52 +02:00
369f62ff38 Correct var names 2023-08-11 14:25:12 +02:00
074e77196e wip calculate-sha [skip-ci] 2023-08-11 14:24:11 +02:00
88253f49ac use new api in release-service 2023-08-11 14:20:27 +02:00
2fc59f105b artifact publish ist part of release now 2023-08-11 14:08:46 +02:00
0952ec57a8 [Skip-CI] Add initial artifact deployment domain object 2023-08-04 12:10:28 +02:00
911158f882 Remove __get_base_artifact_release_url() 2023-08-04 12:08:23 +02:00
bom
f3bf8cb335 Add skeleton for ArtifactDeploymentService 2023-08-04 11:48:16 +02:00
bom
c02440ac65 Return values of post requests 2023-08-04 11:47:55 +02:00
bom
8d4921ea70 Switch order of function parameters 2023-08-04 11:47:32 +02:00
bom
09bf3f5d44 Switch some quotes and brackets 2023-08-03 13:33:57 +02:00
6dbbb8f2a1 [Skip-CI] Add ArtifactDeploymentApi 2023-08-03 12:46:31 +02:00
bom
e6450b796f Add basic artifact service 2023-08-03 12:15:37 +02:00
bom
c15503b7a0 Update artifact deployment mixin 2023-08-03 12:15:18 +02:00
36 changed files with 581 additions and 201 deletions

View file

@ -1,42 +0,0 @@
name: stable
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
jobs:
build:
name: stable build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use python 3.x
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: build stable release
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_DDA }}
run: |
pyb -P version=${{ github.ref }} publish upload
- name: Create GH Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false

View file

@ -1,30 +0,0 @@
name: unstable
on:
push:
tags:
- '![0-9]+.[0-9]+.[0-9]+'
jobs:
build:
name: unstable
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use python 3.x
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: build unstable release
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_DDA }}
run: |
pyb publish upload

View file

@ -4,13 +4,14 @@ stages:
- image
.py: &py
image: "domaindrivenarchitecture/ddadevops-python:4.1.0"
image: "domaindrivenarchitecture/ddadevops-python:4.7.0"
before_script:
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
- python --version
- pip install -r requirements.txt
.img: &img
image: "domaindrivenarchitecture/ddadevops-dind:4.1.0"
image: "domaindrivenarchitecture/ddadevops-dind:4.7.0"
services:
- docker:dind
before_script:
@ -43,7 +44,7 @@ pypi-stable:
<<: *tag_only
stage: upload
script:
- pyb -P version=$CI_COMMIT_TAG publish upload
- pyb -P version=$CI_COMMIT_TAG publish upload publish_artifacts
clj-cljs-image-publish:
<<: *img
@ -59,7 +60,6 @@ clj-image-publish:
script:
- cd infrastructure/clj && pyb image publish
python-image-publish:
<<: *img
<<: *tag_only

View file

@ -205,8 +205,3 @@ For more details about our repository model see: https://repo.prod.meissa.de/mei
Copyright © 2021 meissa GmbH
Licensed under the [Apache License, Version 2.0](LICENSE) (the "License")
## License
Copyright © 2023 meissa GmbH
Licensed under the [Apache License, Version 2.0](LICENSE) (the "License")

View file

@ -33,7 +33,7 @@ default_task = "dev"
name = "ddadevops"
MODULE = "not-used"
PROJECT_ROOT_PATH = "."
version = "4.3.2-dev"
version = "4.7.5-dev"
summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud"
description = __doc__
authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")]
@ -46,7 +46,7 @@ license = "Apache Software License"
def initialize(project):
# project.build_depends_on('mockito')
# project.build_depends_on('unittest-xml-reporting')
project.build_depends_on("ddadevops>=4.0.0")
project.build_depends_on("ddadevops>=4.7.0")
project.set_property("verbose", True)
project.get_property("filter_resources_glob").append(
@ -104,6 +104,10 @@ def initialize(project):
"infrastructure/clj/build.py",
"infrastructure/kotlin/build.py",
],
"release_artifacts": [],
"release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": "meissa",
"release_repository_name": "dda-devops-build",
}
build = ReleaseMixin(project, input)
@ -179,6 +183,12 @@ def tag(project):
build.tag_bump_and_push_release()
@task
def publish_artifacts(project):
build = get_devops_build(project)
build.publish_artifacts()
def release(project):
prepare(project)
tag(project)

View file

@ -23,12 +23,9 @@ classDiagram
| build_dir_name | name of dir, build is executed in | target |
| build_types | list of special builds used. Valid values are ["IMAGE", "C4K", "K3S", "TERRAFORM"] | [] |
| mixin_types | mixins are orthoganl to builds and represent additional capabilities. Valid Values are ["RELEASE"] | [] |
| module | module name - may result in a hierarchy like name/module | |
| name | dedicated name of the build | module |
| project_root_path | relative path to projects root. Is used to locate the target dir | |
| stage | sth. like test, int, acc or prod | |
## Example Usage
### build.py
```python

View file

@ -30,7 +30,7 @@ classDiagram
| image_dockerhub_user | user to access docker-hub | IMAGE_DOCKERHUB_USER from env or credentials from gopass |
| image_dockerhub_password | password to access docker-hub | IMAGE_DOCKERHUB_PASSWORD from env or credentials from gopass |
| image_tag | tag for publishing the image | IMAGE_TAG from env |
| image_naming | Strategy for calculate the image name. Posible values are [NAME_ONLY,NAME_AND_MODULE] |NAME_ONLY |
### Credentials Mapping defaults

View file

@ -1,5 +1,15 @@
# ReleaseMixin
- [ReleaseMixin](#releasemixin)
- [Input](#input)
- [Example Usage just for creating releases](#example-usage-just-for-creating-releases)
- [build.py](#buildpy)
- [call the build for creating a major release](#call-the-build-for-creating-a-major-release)
- [Example Usage for creating a release on forgejo / gitea \& upload the generated artifacts](#example-usage-for-creating-a-release-on-forgejo--gitea--upload-the-generated-artifacts)
- [build.py](#buildpy-1)
- [call the build](#call-the-build)
Support for releases following the trunk-based-release flow (see https://trunkbaseddevelopment.com/)
```mermaid
@ -8,6 +18,7 @@ classDiagram
prepare_release() - adjust all build files to carry the correct version & commit locally
tag_and_push_release() - tag the git repo and push changes to origin
update_release_type (release_type) - change the release type during run time
publish_artifacts() - publish release & artifacts to forgejo/gitea
}
```
@ -20,8 +31,12 @@ classDiagram
| release_main_branch | the name of your trank | "main" |
| release_primary_build_file | path to the build file having the leading version info (read & write). Valid extensions are .clj, .json, .gradle, .py | "./project.clj" |
| release_secondary_build_files | list of secondary build files, version is written in. | [] |
| release_artifact_server_url | Optional: The base url of your forgejo/gitea instance to publish a release tode | |
| 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 | [] |
## Example Usage
## Example Usage just for creating releases
### build.py
@ -36,7 +51,7 @@ PROJECT_ROOT_PATH = '..'
@init
def initialize(project):
project.build_depends_on("ddadevops>=4.0.0")
project.build_depends_on("ddadevops>=4.7.0")
input = {
"name": name,
@ -48,35 +63,108 @@ def initialize(project):
"release_type": "MINOR",
"release_primary_build_file": "project.clj",
"release_secondary_build_files": ["package.json"],
}
roject.build_depends_on("ddadevops>=4.0.0-dev")
}
build = ReleaseMixin(project, input)
build.initialize_build_dir()
@task
def prepare_release(project):
def patch(project):
linttest(project, "PATCH")
release(project)
@task
def minor(project):
linttest(project, "MINOR")
release(project)
@task
def major(project):
linttest(project, "MAJOR")
release(project)
@task
def dev(project):
linttest(project, "NONE")
@task
def prepare(project):
build = get_devops_build(project)
build.prepare_release()
@task
def build(project):
print("do the build")
@task
def publish(project):
print("publish your artefacts")
@task
def after_publish(project):
def tag(project):
build = get_devops_build(project)
build.tag_bump_and_push_release()
def release(project):
prepare(project)
tag(project)
def linttest(project, release_type):
build = get_devops_build(project)
build.update_release_type(release_type)
#test(project)
#lint(project)
```
### call the build for creating a major release
```bash
pyb major
```
## Example Usage for creating a release on forgejo / gitea & upload the generated artifacts
### build.py
```python
rom os import environ
from pybuilder.core import task, init
from ddadevops import *
name = 'my-project'
MODULE = 'my-module'
PROJECT_ROOT_PATH = '..'
@init
def initialize(project):
project.build_depends_on("ddadevops>=4.7.0")
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": [],
"mixin_types": ["RELEASE"],
"release_type": "MINOR",
"release_primary_build_file": "project.clj",
"release_secondary_build_files": ["package.json"],
"release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": "meissa",
"release_repository_name": "dda-devops-build",
"release_artifacts": ["target/doc.zip"],
}
build = ReleaseMixin(project, input)
build.initialize_build_dir()
@task
def publish_artifacts(project):
build = get_devops_build(project)
build.publish_artifacts()
```
### call the build
```bash
pyb prepare_release build publish after_publish
git checkout "4.7.0"
pyb publish_artifacts
```

View file

@ -88,6 +88,15 @@ classDiagram
release_type
release_main_branch
release_current_branch
release_artifact_server_url
release_organisation
release_repository_name
release_artifact_token
}
class Artifact {
path_str
path()
type()
}
class Credentials {
<<AggregateRoot>>
@ -130,6 +139,7 @@ classDiagram
TerraformDomain *-- "0..1" ProviderAws: providers
Release o-- "0..1" BuildFile: primary_build_file
Release o-- "0..n" BuildFile: secondary_build_files
Release "1" *-- "0..n" Artifact: release_artifacts
Release "1" *-- "1" Version: version
BuildFile *-- "1" Version: version
C4k *-- DnsRecord: dns_record

View file

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

View file

@ -23,7 +23,7 @@ function main() {
#install pyb
apt -qqy install python3 python3-pip git;
pip3 install pybuilder 'ddadevops>=4.2.0' deprecation dda-python-terraform boto3 pyyaml inflection --break-system-packages;
pip3 install pybuilder 'ddadevops>=4.7.0' deprecation dda-python-terraform boto3 pyyaml inflection --break-system-packages;
cleanupDocker
}

View file

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

View file

@ -29,7 +29,7 @@ function main() {
#install pyb
apt -qqy install python3 python3-pip;
pip3 install pybuilder 'ddadevops>=4.2.0' deprecation dda-python-terraform boto3 pyyaml inflection --break-system-packages;
pip3 install pybuilder 'ddadevops>=4.7.0' deprecation dda-python-terraform boto3 pyyaml inflection --break-system-packages;
cleanupDocker
}

View file

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

View file

@ -1,6 +1,6 @@
FROM python:3.10-alpine
RUN set -eux;
RUN apk add --no-cache python3 py3-pip openssl-dev bash git;
RUN apk add --no-cache python3 py3-pip openssl-dev bash git curl;
RUN python3 -m pip install -U pip;
RUN pip3 install pybuilder ddadevops deprecation dda-python-terraform boto3 pyyaml inflection;

View file

@ -6,7 +6,7 @@ from ddadevops import *
name = "ddadevops"
MODULE = "dind"
PROJECT_ROOT_PATH = "../.."
version = "4.3.2-dev"
version = "4.7.5-dev"
@init
@ -26,7 +26,7 @@ def initialize(project):
"image_tag": f"{image_tag}",
}
project.build_depends_on("ddadevops>=4.0.0")
project.build_depends_on("ddadevops>=4.7.0")
build = DevopsImageBuild(project, input)
build.initialize_build_dir()

View file

@ -6,7 +6,8 @@ from ddadevops import *
name = "ddadevops"
MODULE = "kotlin"
PROJECT_ROOT_PATH = "../.."
version = "4.3.2-dev"
version = "4.7.5-dev"
@init
def initialize(project):

View file

@ -8,7 +8,7 @@ function main() {
#install pyb
apt -qqy install python3 python3-pip;
pip3 install pybuilder 'ddadevops>=4.2.0' deprecation dda-python-terraform boto3 pyyaml inflection --break-system-packages;
pip3 install pybuilder 'ddadevops>=4.7.0' deprecation dda-python-terraform boto3 pyyaml inflection --break-system-packages;
cleanupDocker
}

View file

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

View file

@ -1,7 +1,7 @@
FROM python:3.10-alpine
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 bash git;
RUN apk add --no-cache build-base rust python3 python3-dev py3-pip py3-setuptools py3-wheel libffi-dev openssl-dev cargo bash git curl;
RUN python3 -m pip install -U pip;
RUN pip3 install pybuilder ddadevops deprecation dda-python-terraform boto3 pyyaml inflection;
RUN pip3 install coverage flake8 flake8-polyfill mypy mypy-extensions pycodestyle pyflakes pylint pytest pytest-cov pytest-datafiles types-setuptools types-PyYAML;

View file

@ -1,18 +1,26 @@
import json
from typing import List
from pathlib import Path
from ..infrastructure import GitApi, BuildFileRepository
from ..domain import Version, Release, ReleaseType
from ..infrastructure import GitApi, ArtifactDeploymentApi, BuildFileRepository
from ..domain import Version, Release, ReleaseType, Artifact
class ReleaseService:
def __init__(self, git_api: GitApi, build_file_repository: BuildFileRepository):
def __init__(
self,
git_api: GitApi,
artifact_deployment_api: ArtifactDeploymentApi,
build_file_repository: BuildFileRepository,
):
self.git_api = git_api
self.artifact_deployment_api = artifact_deployment_api
self.build_file_repository = build_file_repository
@classmethod
def prod(cls, base_dir: str):
return cls(
GitApi(),
ArtifactDeploymentApi(),
BuildFileRepository(base_dir),
)
@ -53,6 +61,41 @@ class ReleaseService:
)
self.git_api.push_follow_tags()
def publish_artifacts(self, release: Release):
token = str(release.release_artifact_token)
release_id = self.__parse_forgejo_release_id__(
self.artifact_deployment_api.create_forgejo_release(
release.forgejo_release_api_endpoint(),
release.version.to_string(),
token,
)
)
artifacts_sums = []
for artifact in release.release_artifacts:
sha256 = self.artifact_deployment_api.calculate_sha256(artifact.path())
sha512 = self.artifact_deployment_api.calculate_sha512(artifact.path())
artifacts_sums += [Artifact(sha256), Artifact(sha512)]
artifacts = release.release_artifacts + artifacts_sums
print(artifacts)
for artifact in artifacts:
print(str)
self.artifact_deployment_api.add_asset_to_release(
release.forgejo_release_asset_api_endpoint(release_id),
artifact.path(),
artifact.type(),
token,
)
def __parse_forgejo_release_id__(self, release_response: str) -> int:
parsed = json.loads(release_response)
try:
result = parsed["id"]
except:
raise RuntimeError(str(parsed))
return result
def __set_version_and_commit__(
self, version: Version, build_file_ids: List[str], message: str
):

View file

@ -1,56 +0,0 @@
from pybuilder.core import Project
from .devops_build import DevopsBuild
# """
# Functional Req:
# General process for deploying prebuilt (meissa) binaries to our own repo server.
# [-1]
# Building is handled by other entities
# is another pybuilder task
# the binary is reachable with devops.build_path()
# we might need to establish a "build" that does lein builds for us
# we might need to establish a "build" that does gradlew build for us
# same for all other projects that produce binaries
# currently the c4k_build.py just creates the auth and config yamls
# [0]
# get artifact deployment url
# Base url: https://repo.prod.meissa.de/api/v1/repos/
# Changeable: /meissa/provs/
# persitent suffix to url: releases
# name is accessible from input
# [1]
# get release token
# could be an api token for repo.prod.meissa.de
# credential mapping as described in the docs
# [2]
# get release tag
# is the version of the project
# get from gitApi
# [3]
# post a json message containting [2] to [0], watching stdout for answers
# authorized by [1]
# validate if [3] was successful by reading stdout
# or create error message containing ID of release
# [4]
# get release-id from stdout of [3]
# print release-id
# [5]
# generate sha256 sums & generate sha512 sums of results of [-1]
# [6]
# push results of [-1] & [5] to [0]/[4]
# """
class ArtifactDeploymentMixin(DevopsBuild):
def __init__(self, project: Project, inp: dict):
super().__init__(project, inp)

View file

@ -17,6 +17,7 @@ from .provider_hetzner import Hetzner
from .provider_aws import Aws
from .provs_k3s import K3s
from .release import Release
from .artifact import Artifact
from .credentials import Credentials, CredentialMapping, GopassType
from .version import Version
from .build_file import BuildFileType, BuildFile

View file

@ -0,0 +1,46 @@
from enum import Enum
from pathlib import Path
from .common import (
Validateable,
)
class ArtifactType(Enum):
TEXT = 0
JAR = 1
class Artifact(Validateable):
def __init__(self, path: str):
self.path_str = path
def path(self) -> Path:
return Path(self.path_str)
def type(self) -> str:
suffix = self.path().suffix
match suffix:
case ".jar":
return "application/x-java-archive"
case ".js":
return "application/x-javascript"
case _:
return "text/plain"
def validate(self):
result = []
result += self.__validate_is_not_empty__("path_str")
try:
Path(self.path_str)
except Exception as e:
result += [f"path was not a valid: {e}"]
return result
def __str__(self):
return str(self.path())
def __eq__(self, other):
return other and self.__str__() == other.__str__()
def __hash__(self) -> int:
return self.__str__().__hash__()

View file

@ -8,7 +8,7 @@ from .provider_digitalocean import Digitalocean
from .provider_hetzner import Hetzner
from .c4k import C4k
from .image import Image
from .release import ReleaseType
from .release import ReleaseType, Release
from ..infrastructure import BuildFileRepository, CredentialsApi, EnvironmentApi, GitApi
@ -69,6 +69,7 @@ class InitService:
Path(primary_build_file_id)
)
version = primary_build_file.get_version()
default_mappings += Release.get_mapping_default()
credentials = Credentials(inp, default_mappings)
authorization = self.authorization(credentials)
@ -111,9 +112,8 @@ class InitService:
result = {}
for name in credentials.mappings.keys():
mapping = credentials.mappings[name]
env_value = self.environment_api.get(mapping.name_for_environment())
if env_value:
result[name] = env_value
if self.environment_api.is_defined(mapping.name_for_environment()):
result[name] = self.environment_api.get(mapping.name_for_environment())
else:
if mapping.gopass_type() == GopassType.FIELD:
result[name] = self.credentials_api.gopass_field_from_path(

View file

@ -1,4 +1,4 @@
from typing import Optional, List
from typing import Optional, List, Dict
from pathlib import Path
from .common import (
Validateable,
@ -7,6 +7,9 @@ from .common import (
from .version import (
Version,
)
from .artifact import (
Artifact,
)
class Release(Validateable):
@ -21,6 +24,13 @@ class Release(Validateable):
"release_secondary_build_files", []
)
self.version = version
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")
self.release_artifact_token = inp.get("release_artifact_token")
self.release_artifacts = []
for a in inp.get("release_artifacts", []):
self.release_artifacts.append(Artifact(a))
def update_release_type(self, release_type: ReleaseType):
self.release_type = release_type
@ -53,10 +63,44 @@ class Release(Validateable):
and self.release_type != ReleaseType.NONE
and self.release_main_branch != self.release_current_branch
):
result.append(f"Releases are allowed only on {self.release_main_branch}")
result.append(
f"Releases are allowed only on {self.release_main_branch}"
)
return result
def validate_for_artifact(self):
result = []
result += self.__validate_is_not_empty__("release_artifact_server_url")
result += self.__validate_is_not_empty__("release_organisation")
result += self.__validate_is_not_empty__("release_repository_name")
result += self.__validate_is_not_empty__("release_artifact_token")
return result
def build_files(self) -> List[str]:
result = [self.release_primary_build_file]
result += self.release_secondary_build_files
return result
def forgejo_release_api_endpoint(self) -> str:
validation = self.validate_for_artifact()
if validation != []:
raise RuntimeError(f"not valid for creating artifacts: {validation}")
server_url = self.release_artifact_server_url.removeprefix("/").removesuffix(
"/"
)
organisation = self.release_organisation.removeprefix("/").removesuffix("/")
repository = self.release_repository_name.removeprefix("/").removesuffix("/")
return f"{server_url}/api/v1/repos/{organisation}/{repository}/releases"
def forgejo_release_asset_api_endpoint(self, release_id: int) -> str:
return f"{self.forgejo_release_api_endpoint()}/{release_id}/assets"
@classmethod
def get_mapping_default(cls) -> List[Dict[str, str]]:
return [
{
"gopass_path": "server/meissa/repo/buero-rw",
"name": "release_artifact_token",
}
]

View file

@ -32,12 +32,6 @@ class Version(Validateable):
self.snapshot_suffix = snapshot_suffix
self.default_snapshot_suffix = default_snapshot_suffix
def __eq__(self, other):
return other and self.to_string() == other.to_string()
def __hash__(self) -> int:
return self.to_string().__hash__()
def is_snapshot(self):
return self.snapshot_suffix is not None
@ -139,3 +133,9 @@ class Version(Validateable):
snapshot_suffix=None,
version_str=None,
)
def __eq__(self, other):
return other and self.to_string() == other.to_string()
def __hash__(self) -> int:
return self.to_string().__hash__()

View file

@ -7,5 +7,6 @@ from .infrastructure import (
CredentialsApi,
GitApi,
TerraformApi,
ArtifactDeploymentApi,
)
from .repository import DevopsRepository, BuildFileRepository

View file

@ -58,23 +58,21 @@ class ImageApi:
)
def drun(self, name: str):
self.execution_api.execute_live(
f'docker run -it --entrypoint="" {name} /bin/bash'
run(
f'docker run -it --entrypoint="" {name} /bin/bash',
shell=True,
check=True,
)
def dockerhub_login(self, username: str, password: str):
self.execution_api.execute_secure(
f"docker login --username {username} --password {password}",
"docker login --username ***** --password *****"
"docker login --username ***** --password *****",
)
def dockerhub_publish(self, name: str, username: str, tag: str):
self.execution_api.execute_live(
f"docker tag {name} {username}/{name}:{tag}"
)
self.execution_api.execute_live(
f"docker push {username}/{name}:{tag}"
)
self.execution_api.execute_live(f"docker tag {name} {username}/{name}:{tag}")
self.execution_api.execute_live(f"docker push {username}/{name}:{tag}")
def test(self, name: str, path: Path):
self.execution_api.execute_live(
@ -95,14 +93,24 @@ class ExecutionApi:
check=check,
stdout=PIPE,
stderr=PIPE,
text=True).stdout
text=True,
).stdout
output = output.rstrip()
except CalledProcessError as exc:
print(f"Command failed with code: {exc.returncode} and message: {exc.stderr}")
print(
f"Command failed with code: {exc.returncode} and message: {exc.stderr}"
)
raise exc
return output
def execute_secure(self, command: str, sanitized_command: str, dry_run=False, shell=True, check=True):
def execute_secure(
self,
command: str,
sanitized_command: str,
dry_run=False,
shell=True,
check=True,
):
try:
output = self.execute(command, dry_run, shell, check)
return output
@ -128,6 +136,9 @@ class EnvironmentApi:
def get(self, key):
return environ.get(key)
def is_defined(self, key):
return key in environ
class CredentialsApi:
def __init__(self):
@ -206,3 +217,53 @@ class GitApi:
class TerraformApi:
pass
class ArtifactDeploymentApi:
def __init__(self):
self.execution_api = ExecutionApi()
def create_forgejo_release(self, api_endpoint_url: str, tag: str, token: str):
command = (
f'curl -X "POST" "{api_endpoint_url}" '
+ ' -H "accept: application/json" -H "Content-Type: application/json"'
+ f' -d \'{{ "body": "Provides files for release {tag}", "tag_name": "{tag}"}}\''
) # noqa: E501
print(command + ' -H "Authorization: token xxxx"')
return self.execution_api.execute_secure(
command=command + f' -H "Authorization: token {token}"',
sanitized_command=command + ' -H "Authorization: token xxxx"',
)
def add_asset_to_release(
self,
api_endpoint_url: str,
attachment: Path,
attachment_type: str,
token: str,
):
command = (
f'curl -X "POST" "{api_endpoint_url}"'
+ ' -H "accept: application/json"'
+ ' -H "Content-Type: multipart/form-data"'
+ f' -F "attachment=@{attachment};type={attachment_type}"'
) # noqa: E501
print(command + ' -H "Authorization: token xxxx"')
return self.execution_api.execute_secure(
command=command + f' -H "Authorization: token {token}"',
sanitized_command=command + ' -H "Authorization: token xxxx"',
)
def calculate_sha256(self, path: Path):
shasum = f"{path}.sha256"
self.execution_api.execute(
f"sha256sum {path} > {shasum}",
)
return shasum
def calculate_sha512(self, path: Path):
shasum = f"{path}.sha512"
self.execution_api.execute(
f"sha512sum {path} > {shasum}",
)
return shasum

View file

@ -26,3 +26,8 @@ class ReleaseMixin(DevopsBuild):
devops = self.devops_repo.get_devops(self.project)
release = devops.mixins[MixinType.RELEASE]
self.release_service.tag_bump_and_push_release(release)
def publish_artifacts(self):
devops = self.devops_repo.get_devops(self.project)
release = devops.mixins[MixinType.RELEASE]
self.release_service.publish_artifacts(release)

View file

@ -1,18 +1,22 @@
import pytest
from pathlib import Path
from src.main.python.ddadevops.domain import (
ReleaseType,
ReleaseType,
MixinType,
)
from src.test.python.domain.helper import (
BuildFileRepositoryMock,
GitApiMock,
ArtifactDeploymentApiMock,
build_devops,
)
from src.main.python.ddadevops.application import ReleaseService
def test_sould_update_release_type():
sut = ReleaseService(GitApiMock(), BuildFileRepositoryMock("build.py"))
sut = ReleaseService(
GitApiMock(), ArtifactDeploymentApiMock(), BuildFileRepositoryMock("build.py")
)
devops = build_devops({})
release = devops.mixins[MixinType.RELEASE]
sut.update_release_type(release, "MAJOR")
@ -20,3 +24,40 @@ def test_sould_update_release_type():
with pytest.raises(Exception):
sut.update_release_type(release, "NOT_EXISTING")
def test_sould_publish_artifacts():
mock = ArtifactDeploymentApiMock(release='{"id": 2345}')
sut = ReleaseService(GitApiMock(), mock, BuildFileRepositoryMock())
devops = build_devops(
{
"release_artifacts": ["target/art"],
"release_artifact_server_url": "http://repo.test/",
"release_organisation": "orga",
"release_repository_name": "repo",
}
)
release = devops.mixins[MixinType.RELEASE]
sut.publish_artifacts(release)
assert "http://repo.test/api/v1/repos/orga/repo/releases/2345/assets" == mock.add_asset_to_release_api_endpoint
def test_sould_throw_exception_if_there_was_an_error_in_publish_artifacts():
devops = build_devops(
{
"release_artifacts": ["target/art"],
"release_artifact_server_url": "http://repo.test/",
"release_organisation": "orga",
"release_repository_name": "repo",
}
)
release = devops.mixins[MixinType.RELEASE]
with pytest.raises(Exception):
mock = ArtifactDeploymentApiMock(release='')
sut = ReleaseService(GitApiMock(), mock, BuildFileRepositoryMock())
sut.publish_artifacts(release)
with pytest.raises(Exception):
mock = ArtifactDeploymentApiMock(release='{"message": "there was an error", "url":"some-url"}')
sut = ReleaseService(GitApiMock(), mock, BuildFileRepositoryMock())
sut.publish_artifacts(release)

View file

@ -53,6 +53,11 @@ def devops_config(overrides: dict) -> dict:
"release_current_branch": "my_feature",
"release_primary_build_file": "./package.json",
"release_secondary_build_file": [],
"release_artifacts": [],
"release_artifact_token": "release_artifact_token",
"release_artifact_server_url": None,
"release_organisation": None,
"release_repository_name": None,
"credentials_mappings": [
{
"gopass_path": "a/path",
@ -99,6 +104,9 @@ class EnvironmentApiMock:
def get(self, key):
return self.mappings.get(key, None)
def is_defined(self, key):
return key in self.mappings
class CredentialsApiMock:
def __init__(self, mappings):
@ -150,3 +158,28 @@ class GitApiMock:
def checkout(self, branch: str):
pass
class ArtifactDeploymentApiMock:
def __init__(self, release=""):
self.release = release
self.create_forgejo_release_count = 0
self.add_asset_to_release_count = 0
self.add_asset_to_release_api_endpoint = ""
def create_forgejo_release(self, api_endpoint: str, tag: str, token: str):
self.create_forgejo_release_count += 1
return self.release
def add_asset_to_release(
self, api_endpoint: str, attachment: str, attachment_type: str, token: str
):
self.add_asset_to_release_api_endpoint = api_endpoint
self.add_asset_to_release_count += 1
pass
def calculate_sha256(self, path: Path):
return f"{path}.sha256"
def calculate_sha512(self, path: Path):
return f"{path}.sha512"

View file

@ -0,0 +1,32 @@
import pytest
from pybuilder.core import Project
from pathlib import Path
from src.main.python.ddadevops.domain import (
Validateable,
DnsRecord,
Devops,
BuildType,
MixinType,
Artifact,
Image,
)
from .helper import build_devops, devops_config
def test_sould_validate_release():
sut = Artifact("x")
assert sut.is_valid()
sut = Artifact(None)
assert not sut.is_valid()
def test_should_calculate_type():
sut = Artifact("x.jar")
assert "application/x-java-archive" == sut.type()
sut = Artifact("x.js")
assert "application/x-javascript" == sut.type()
sut = Artifact("x.jar.sha256")
assert "text/plain" == sut.type()

View file

@ -4,6 +4,7 @@ from src.main.python.ddadevops.domain import (
Version,
BuildType,
MixinType,
Artifact,
)
@ -50,6 +51,7 @@ def test_devops_creation():
assert sut is not None
assert sut.specialized_builds[BuildType.C4K] is not None
def test_release_devops_creation():
sut = DevopsFactory().build_devops(
{
"stage": "test",
@ -67,6 +69,30 @@ def test_devops_creation():
assert sut is not None
assert sut.mixins[MixinType.RELEASE] is not None
sut = DevopsFactory().build_devops(
{
"stage": "test",
"name": "mybuild",
"module": "test_image",
"project_root_path": "../../..",
"build_types": [],
"mixin_types": ["RELEASE"],
"release_main_branch": "main",
"release_current_branch": "my_feature",
"release_config_file": "project.clj",
"release_artifacts": ["x.jar"],
"release_artifact_token": "y",
"release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": "meissa",
"release_repository_name": "provs",
},
Version.from_str("1.0.0", "SNAPSHOT"),
)
release = sut.mixins[MixinType.RELEASE]
assert release is not None
assert Artifact("x.jar") == release.release_artifacts[0]
def test_on_merge_input_should_win():
sut = DevopsFactory()

View file

@ -1,3 +1,4 @@
import pytest
from pybuilder.core import Project
from pathlib import Path
from src.main.python.ddadevops.domain import (
@ -61,3 +62,74 @@ def test_sould_calculate_build_files():
Version.from_str("1.3.1-SNAPSHOT", "SNAPSHOT"),
)
assert ["project.clj", "package.json"] == sut.build_files()
def test_should_calculate_forgejo_release_api_endpoint():
sut = Release(
devops_config(
{
"release_artifacts": [],
"release_artifact_token": "y",
"release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": "meissa",
"release_repository_name": "provs",
}
),
Version.from_str("1.3.1-SNAPSHOT", "SNAPSHOT"),
)
assert (
"https://repo.prod.meissa.de/api/v1/repos/meissa/provs/releases"
== sut.forgejo_release_api_endpoint()
)
sut = Release(
devops_config(
{
"release_artifacts": ["x"],
"release_artifact_token": "y",
"release_artifact_server_url": "https://repo.prod.meissa.de/",
"release_organisation": "/meissa/",
"release_repository_name": "provs",
}
),
Version.from_str("1.3.1-SNAPSHOT", "SNAPSHOT"),
)
assert (
"https://repo.prod.meissa.de/api/v1/repos/meissa/provs/releases"
== sut.forgejo_release_api_endpoint()
)
assert(
"/meissa/"
== sut.release_organisation
)
with pytest.raises(Exception):
sut = Release(
devops_config(
{
"release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": None,
"release_repository_name": "provs",
}
),
Version.from_str("1.3.1-SNAPSHOT", "SNAPSHOT"),
)
sut.forgejo_release_api_endpoint()
def test_should_calculate_forgejo_release_asset_api_endpoint():
sut = Release(
devops_config(
{
"release_artifacts": ["x"],
"release_artifact_token": "y",
"release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": "meissa",
"release_repository_name": "provs",
}
),
Version.from_str("1.3.1-SNAPSHOT", "SNAPSHOT"),
)
assert (
"https://repo.prod.meissa.de/api/v1/repos/meissa/provs/releases/123/assets"
== sut.forgejo_release_asset_api_endpoint(123)
)

View file

@ -14,6 +14,8 @@ def test_release_mixin(tmp_path):
copy_resource(Path("package.json"), tmp_path)
project = Project(str_tmp_path, name="name")
os.environ["RELEASE_ARTIFACT_TOKEN"] = "ratoken"
sut = ReleaseMixin(
project,
devops_config(