Compare commits

..

305 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
4fa849b72b check with version 2024-04-17 09:20:57 +02:00
48bbbe6f6e refactoring clj-cljs image install.sh 2024-04-12 16:02:27 +02:00
bf843edb80 refactoring image install.sh checksum function graalvm kubeconform 2024-04-12 14:54:17 +02:00
5e8c21c521 dev-notes first steps 2024-04-12 11:25:25 +02:00
3bc3a0cd7e Statemachine credRot devnotes 2024-04-05 11:55:57 +02:00
56bc215f26 read / write state 2024-04-05 10:42:54 +02:00
678b75ae6f ignore 2024-04-05 10:05:12 +02:00
d133b281f9 refactoring installing ba*bash*ka 2024-03-26 16:38:12 +01:00
581449fba4 Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/dda-devops-build 2024-03-26 14:05:33 +01:00
b38876d9ef ba*bash*ka binary install to image 2024-03-26 14:04:06 +01:00
bom
90c4d4ec9d bump version to: 4.11.10-dev 2024-03-15 15:42:16 +01:00
bom
c0daa85612 release: 4.11.9 2024-03-15 15:42:16 +01:00
bom
0bdd13cf8a Add restic to base backup image 2024-03-15 15:34:56 +01:00
bom
1bba35963a bump version to: 4.11.9-dev 2024-03-15 13:54:27 +01:00
bom
2b7fe54f76 release: 4.11.8 2024-03-15 13:54:27 +01:00
e96581754c move backup image to devops-build 2024-03-06 10:31:40 +01:00
bom
4034b0022b bump version to: 4.11.8-dev 2024-02-24 12:12:14 +01:00
bom
87cc56cdd7 release: 4.11.7 2024-02-24 12:12:14 +01:00
bom
a9d98a6d0c Use returncode to check if process failed 2024-02-24 12:11:57 +01:00
bom
671a3b8cbb bump version to: 4.11.7-dev 2024-02-24 12:07:53 +01:00
bom
011fc848af release: 4.11.6 2024-02-24 12:07:53 +01:00
bom
c5af5c9198 Raise error if process fails in execute_live 2024-02-24 12:07:31 +01:00
bom
afec1fdd0c bump version to: 4.11.6-dev 2024-02-23 15:22:22 +01:00
bom
6513e00e54 release: 4.11.5 2024-02-23 15:22:22 +01:00
bom
3cc6206d06 Extend execute_live to handle input
use the new functionality for docker drun
2024-02-23 15:21:56 +01:00
48452baac5 bump version to: 4.11.5-dev 2024-02-17 11:53:52 +01:00
5875642777 release: 4.11.4 2024-02-17 11:53:52 +01:00
58d8d46a0c initialize jvm build tools 2024-02-17 11:45:36 +01:00
6e57204ce5 bump version to: 4.11.4-dev 2024-02-17 08:50:09 +01:00
d3f1204932 release: 4.11.3 2024-02-17 08:50:09 +01:00
305c9a6bd0 there is no jdk21 2024-02-17 08:49:38 +01:00
751bc26a21 bump version to: 4.11.3-dev 2024-02-16 18:41:28 +01:00
202c07150c release: 4.11.2 2024-02-16 18:41:28 +01:00
f5c75a31f5 use 4.10.7 2024-02-16 18:40:58 +01:00
7c24477348 java 21 2024-02-16 18:40:33 +01:00
237691f79f bump version to: 4.11.2-dev 2024-02-16 18:38:08 +01:00
c6c8de9b0f release: 4.11.1 2024-02-16 18:38:07 +01:00
a7a00756bc fix the bootstrap 2024-02-16 18:20:23 +01:00
e822e3d0f0 bump version to: 4.11.1-dev 2024-02-16 18:13:32 +01:00
bbd12cda4e release: 4.11.0 2024-02-16 18:13:32 +01:00
adcf93d321 fix java home 2024-02-16 18:12:30 +01:00
c9df6082ae Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/dda-devops-build 2024-02-16 18:05:32 +01:00
5825bcbf47 update graal version 2024-02-16 18:05:27 +01:00
ansgarz
b8f7b72a61 chg README.md 2024-02-02 10:08:21 +01:00
ansgarz
f4d30706b7 fix typo 2024-02-02 10:06:05 +01:00
ansgarz
72cd6a65d2 add link to example project to README.md 2024-01-26 10:37:07 +01:00
ansgarz
fde5061bf3 update docs 2024-01-12 11:34:38 +01:00
0dcb375e15 bump version to: 4.10.9-dev 2024-01-05 16:52:36 +01:00
c6872458e2 release: 4.10.8 2024-01-05 16:52:36 +01:00
a5a653b213 [skip-ci] infrastructure/../test folder removed 2023-12-22 16:54:55 +01:00
f12d43b9bc bump version to: 4.10.8-dev 2023-12-22 15:58:59 +01:00
5f18c7ddb3 release: 4.10.7 2023-12-22 15:58:58 +01:00
c92923f6b9 [skip-ci] fix 2023-12-22 15:58:20 +01:00
2aec5b44d2 fix:: ignore missing image/test folder 2023-12-22 15:46:11 +01:00
52622b1f2d bump version to: 4.10.7-dev 2023-12-22 15:17:49 +01:00
822bebc26f release: 4.10.6 2023-12-22 15:17:49 +01:00
0ba623c560 ignore missing image/test folder 2023-12-22 15:17:36 +01:00
147cb1bd72 bump version to: 4.10.6-dev 2023-12-21 14:22:41 +01:00
56dc19322a release: 4.10.5 2023-12-21 14:22:41 +01:00
ee20925e04 gitlabci.yml use dind/python 4.10.5 2023-12-21 14:22:29 +01:00
2455ea0ff0 bump version to: 4.10.5-dev 2023-12-21 14:10:14 +01:00
739940cdd3 release: 4.10.4 2023-12-21 14:10:14 +01:00
47dc81860a bump version to: 4.10.4-dev 2023-12-15 17:28:45 +01:00
308c86595c release: 4.10.3 2023-12-15 17:28:45 +01:00
45745f3ebd fix gitlab-ci.yml version 2023-12-15 17:26:50 +01:00
5b5249eb55 usage of install_functions_debian/alpine.sh 2023-12-15 17:21:43 +01:00
d2a3e60edf bump version to: 4.10.3-dev 2023-12-15 16:22:54 +01:00
8324727b8a release: 4.10.2 2023-12-15 16:22:53 +01:00
c79c0c1a7f bump version to: 4.10.2-dev 2023-12-15 16:13:59 +01:00
d5c9bae212 release: 4.10.1 2023-12-15 16:13:59 +01:00
2914be8a88 better structuring to docker image building 2023-12-15 16:11:49 +01:00
bfc55293dc Adapt usage ddadevops-dind/python version 2023-12-07 21:04:10 +01:00
9a0573bc33 bump version to: 4.10.1-dev 2023-12-07 20:41:59 +01:00
701047fd4f release: 4.10.0 2023-12-07 20:41:59 +01:00
9638450ef6 Improvements docker image building 2023-12-07 20:38:43 +01:00
d90f68ce1a Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/dda-devops-build 2023-12-01 11:19:18 +01:00
f3f51b165a use execution_api for self checking docker image 2023-12-01 11:18:02 +01:00
bom
29d439e82a bump version to: 4.9.4-dev 2023-12-01 11:10:27 +01:00
bom
4d8961557c release: 4.9.3 2023-12-01 11:10:27 +01:00
bom
3e49e31e66 Add kotlin image to ci 2023-12-01 11:09:39 +01:00
bom
5053b79ad4 Make version regex for python and gradle more concrete
Check that "version" is the start of the string to avoid changing cases like
"kotlin_version = ..."
2023-11-24 22:28:39 +01:00
bom
215a9bf0fe Add regression test for malformed version in clj 2023-11-17 15:04:36 +01:00
bom
97978e9950 bump version to: 4.9.3-dev 2023-11-17 15:01:00 +01:00
bom
7375ba16cc release: 4.9.2 2023-11-17 15:01:00 +01:00
bom
07cf837ac6 Simplify set/get version functions 2023-11-17 15:00:33 +01:00
bom
6399a1dfeb Update regexes for gradle and python files 2023-11-17 14:58:53 +01:00
bom
36df9f486b Cleanup
Fix typos, remove old todos
2023-11-17 14:45:38 +01:00
bom
617d909438 Replace clojure version regex 2023-11-17 14:42:47 +01:00
bom
bcccfd8b9c Move regex string to a function
Avoids having duplicates in similar functions
2023-11-17 13:57:11 +01:00
bom
f7897a574d bump version to: 4.9.2-dev 2023-11-17 13:04:40 +01:00
bom
c6217bd0a2 release: 4.9.1 2023-11-17 13:04:40 +01:00
bom
e5d1203435 Update version suffix to fit build file type
Resolves a bug where when using "build.py" as the primary build file
and some other secondary file like "build.gradle",
would result in the ".gradle" file version having a "-dev" suffix
 instead of "-SNAPSHOT"

Includes regression test
2023-11-17 13:03:09 +01:00
bom
78824ea38b Extend GitApiMock with push_follow_tags 2023-11-17 12:50:20 +01:00
288247be8e bump version to: 4.9.1-dev 2023-10-25 09:26:56 +02:00
bc06c34ea3 release: 4.9.0 2023-10-25 09:26:56 +02:00
03c4229cf0 Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/dda-devops-build 2023-10-25 09:25:22 +02:00
32ae4f2a6f bump version to: 4.8.1-dev 2023-10-25 09:23:51 +02:00
3bbae609d7 release: 4.8.0 2023-10-25 09:23:51 +02:00
jem
b8f6129146 Merge pull request 'kotlin-compile' (#1) from kotlin-compile into main
Reviewed-on: #1
2023-10-25 07:20:12 +00:00
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
00202edecc add kotlin image 2023-08-11 13:35:15 +02:00
31ac285df0 add kotlin image 2023-08-08 09:04:28 +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
2e5e1ea0ee [Skip-Ci] Update process description 2023-08-03 11:49:40 +02:00
483c2b8bba bump version to: 4.3.2-dev 2023-08-02 16:50:02 +02:00
2c2a88eced release: 4.3.1 2023-08-02 16:50:02 +02:00
577c717a87 fix backend init 2023-08-02 16:49:35 +02:00
1585f15582 fix linting 2023-08-02 16:49:22 +02:00
9517e9081e [Skip-CI] Collect and sort functional reqs 2023-08-02 12:23:48 +02:00
355bd477af Add initial artifact_deployment_mixin.py 2023-08-02 11:57:03 +02:00
48f7d9c29e [Skip-CI] Add Development and mirrors section 2023-07-28 14:29:51 +02:00
3cbf18792b bump version to: 4.3.1-dev 2023-07-21 17:42:53 +02:00
e38acb39c1 release: 4.3.0 2023-07-21 17:42:53 +02:00
03e32418e8 graalvm only for clj 2023-07-21 17:40:24 +02:00
1cc5c565f3 mv to new name 2023-07-21 17:40:02 +02:00
5a39ac0970 we will use a dedicated image for graalvm 2023-07-21 08:30:36 +02:00
777dfff541 bump version to: 4.2.1-dev 2023-07-20 13:24:50 +02:00
074e62d16e release: 4.2.0 2023-07-20 13:24:50 +02:00
3ccfa81155 remove deprecated image 2023-07-20 12:15:18 +02:00
9ffbba0765 add pyb to clojure image 2023-07-20 12:14:25 +02:00
4efddcc104 fix the link ? 2023-07-19 14:47:40 +02:00
b7540151fc fix the link ? 2023-07-19 14:44:03 +02:00
ca88c63407 doc update 2023-07-19 14:41:15 +02:00
bom
41c41cf9c3 Fix execute_secure call 2023-07-18 12:22:50 +02:00
bom
a53c28b690 Use execute_secure in dockerhub_login 2023-07-18 12:20:44 +02:00
bom
8ae03f5811 Implement execute_secure
Allows us to sanitize commands involving sensitive information like
passwords, before throwing exceptions
2023-07-18 12:18:57 +02:00
bom
2ead8cc31b Handle failed execution in execute_live 2023-07-18 12:18:09 +02:00
bf2d68bddc Linting 2023-07-18 11:33:44 +02:00
e21155fdec Add f-string 2023-07-18 11:26:46 +02:00
43988291c6 Handle errors in execute 2023-07-18 11:10:09 +02:00
f04477f137 use new images 2023-07-14 14:22:57 +02:00
587c8893f0 bump version to: 4.1.1-dev 2023-07-14 13:40:15 +02:00
aea4a31445 release: 4.1.0 2023-07-14 13:40:15 +02:00
7002683a84 Merge branch 'feature/improve-image-build' into 'main'
improve image names

See merge request domaindrivenarchitecture/dda-devops-build!17
2023-07-14 11:35:49 +00:00
f2fd1e3b07 Merge branch 'main' into feature/improve-image-build 2023-07-14 13:34:02 +02:00
0a40e5e213 make release more simple 2023-07-14 13:27:11 +02:00
146e11a1de update doc 2023-07-14 13:26:36 +02:00
a15030adaa fix ci 2023-07-14 13:13:28 +02:00
3c9a86f67c build all images 2023-07-14 13:11:49 +02:00
cb48674864 fix build 2023-07-14 12:43:08 +02:00
771ffe5229 mv domain logic to domain 2023-07-14 12:42:46 +02:00
355c457d57 add an test 2023-07-14 12:41:54 +02:00
9cb77f395f prepare minor 2023-07-14 12:41:35 +02:00
8ce3c74c34 add some todos 2023-07-14 11:46:06 +02:00
bom
e927ec9fcf Merge release tasks into one 2023-07-14 11:46:06 +02:00
bom
c93875f491 Pin devops-build Image version 2023-07-14 11:45:27 +02:00
bom
459a182388 Remove redundant pip install 2023-07-14 11:45:27 +02:00
bom
1d9674b097 Use correct CI variable for Image tag 2023-07-14 11:45:27 +02:00
bom
0219127416 Add docker service to CI
Enables CI to actually build docker images
2023-07-14 11:44:52 +02:00
bom
5c3d07e58c Update docker ci build 2023-07-14 11:43:52 +02:00
8b11a4fa61 Add execute_handled 2023-07-14 11:43:52 +02:00
9c44bebd5d use the new image_naming 2023-07-14 11:33:01 +02:00
6b1ffb6e99 add some tests 2023-07-13 20:41:52 +02:00
bom
b7acaab2a9 Merge release tasks into one 2023-07-13 13:20:57 +02:00
bom
ac06c71a1a Pin devops-build Image version 2023-07-13 13:12:24 +02:00
bom
039b9fe2f0 Remove redundant pip install 2023-07-13 13:12:02 +02:00
bom
9016592832 Use correct CI variable for Image tag 2023-07-13 13:09:16 +02:00
6332b738e3 bump version to: 4.0.17-dev 2023-07-13 12:13:00 +02:00
55daeb69f1 release: 4.0.16 2023-07-13 12:13:00 +02:00
bom
153d4bbf9a Add docker service to CI
Enables CI to actually build docker images
2023-07-13 12:02:02 +02:00
1ae9593537 Merge branch 'main' of gitlab.com:domaindrivenarchitecture/dda-devops-build 2023-07-13 11:17:11 +02:00
bom
0a44d6e363 bump version to: 4.0.16-dev 2023-07-13 11:10:24 +02:00
bom
9341c147ae release: 4.0.15 2023-07-13 11:10:24 +02:00
a6f6b9715e Add execute_handled 2023-07-13 11:07:59 +02:00
bom
e9c6c95299 Update docker ci build 2023-07-13 10:58:16 +02:00
eb5be21824 add more granulare build images 2023-07-13 08:52:46 +02:00
036eb53972 bump version to: 4.0.15-dev 2023-07-12 15:51:36 +02:00
7ff93b18d5 release: 4.0.14 2023-07-12 15:51:36 +02:00
4bc4af52cd Remove unused import 2023-07-12 15:51:23 +02:00
d2bc3cd9f3 Use execute 2023-07-12 15:50:33 +02:00
1cc7221c44 Add DOCKER_HOST 2023-07-12 15:43:30 +02:00
15544e0ba0 Raise exception when encountered 2023-07-12 15:43:16 +02:00
cf091b64e3 bump version to: 4.0.14-dev 2023-07-12 15:22:07 +02:00
54f0ad2297 release: 4.0.13 2023-07-12 15:22:07 +02:00
b8f55aa10d [Skip-CI] Install newest ddadevops in CI 2023-07-12 15:21:11 +02:00
7a55d0f3a6 [Skip-CI] Install newest ddadevops in CI 2023-07-12 15:20:21 +02:00
93c9a542d9 bump version to: 4.0.13-dev 2023-07-12 15:15:25 +02:00
5755133d89 release: 4.0.12 2023-07-12 15:15:25 +02:00
66c78da0c2 Install linting tools in linting stage 2023-07-12 14:45:09 +02:00
a4cdf051ff Linting 2023-07-12 14:37:21 +02:00
e1639974e3 Specify raised exceptions 2023-07-12 14:37:07 +02:00
186f057b2b Use more verbose error output 2023-07-12 14:21:39 +02:00
82 changed files with 2240 additions and 877 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

3
.gitignore vendored
View file

@ -109,3 +109,6 @@ venv.bak/
.clj-kondo/ .clj-kondo/
.lsp/ .lsp/
.calva/
.cpcache/
infrastructure/backup/image/resources/backup-repository-state.edn

View file

@ -1,17 +1,89 @@
image: "domaindrivenarchitecture/devops-build:4.0.8"
before_script:
- python --version
- python -m pip install --upgrade pip
- pip install -r requirements.txt
- export IMAGE_TAG=$CI_IMAGE_TAG
- export IMAGE_DOCKERHUB_USER=$DOCKERHUB_USER
- export IMAGE_DOCKERHUB_PASSWORD=$DOCKERHUB_PASSWORD
stages: stages:
- lint&test
- upload
- image - image
devops-build-image-test-publish: .py: &py
image: "domaindrivenarchitecture/ddadevops-python:4.10.7"
before_script:
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
- python --version
- pip install -r requirements.txt
.img: &img
image: "domaindrivenarchitecture/ddadevops-dind:4.10.7"
services:
- docker:dind
before_script:
- export IMAGE_DOCKERHUB_USER=$DOCKERHUB_USER
- export IMAGE_DOCKERHUB_PASSWORD=$DOCKERHUB_PASSWORD
- export IMAGE_TAG=$CI_COMMIT_TAG
.tag_only: &tag_only
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: never
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
lint:
<<: *py
stage: lint&test
script:
- pip install -r dev_requirements.txt
- pyb lint
pytest:
<<: *py
stage: lint&test
script:
- pip install -r dev_requirements.txt
- pyb test
pypi-stable:
<<: *py
<<: *tag_only
stage: upload
script:
- pyb -P version=$CI_COMMIT_TAG publish upload publish_artifacts
clj-cljs-image-publish:
<<: *img
<<: *tag_only
stage: image stage: image
script: script:
- cd infrastructure/devops-build && pyb image - cd infrastructure/clj-cljs && pyb image publish
clj-image-publish:
<<: *img
<<: *tag_only
stage: image
script:
- cd infrastructure/clj && pyb image publish
python-image-publish:
<<: *img
<<: *tag_only
stage: image
script:
- cd infrastructure/python && pyb image publish
dind-image-publish:
<<: *img
<<: *tag_only
stage: image
script:
- cd infrastructure/dind && pyb image publish
ddadevops-image-publish:
<<: *img
<<: *tag_only
stage: image
script:
- cd infrastructure/ddadevops && pyb image publish
kotlin-image-publish:
<<: *img
<<: *tag_only
stage: image
script:
- cd infrastructure/kotlin && pyb image publish

View file

@ -1,8 +1,7 @@
# dda-devops-build # 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)
![release prod](https://github.com/DomainDrivenArchitecture/dda-devops-build/workflows/release%20prod/badge.svg)
dda-devops-build integrates all the tools we use to work with clouds & provide some nice functions around. dda-devops-build integrates all the tools we use to work with clouds & provide some nice functions around.
@ -70,12 +69,12 @@ classDiagram
DevopsBuild <|-- ProvsK3sBuild DevopsBuild <|-- ProvsK3sBuild
DevopsBuild <|-- C4kBuild DevopsBuild <|-- C4kBuild
link DevopsBuild "./doc/DevopsBuild.md" link DevopsBuild "dda-devops-build/src/doc/DevopsBuild.md"
link DevopsImageBuild "./doc/DevopsImageBuild.md" link DevopsImageBuild "dda-devops-build/src/doc/DevopsImageBuild.md"
link DevopsTerraformBuild "./doc/DevopsTerraformBuild.md" link DevopsTerraformBuild "dda-devops-build/src/doc/DevopsTerraformBuild.md"
link ReleaseMixin "./doc/ReleaseMixin.md" link ReleaseMixin "dda-devops-build/src/doc/ReleaseMixin.md"
link ProvsK3sBuild "doc/ProvsK3sBuild.md" link ProvsK3sBuild "dda-devops-build/src/doc/ProvsK3sBuild.md"
link C4kBuild "doc/C4kBuild.md" link C4kBuild "dda-devops-build/src/doc/C4kBuild.md"
``` ```
@ -84,6 +83,10 @@ Principles we follow are:
* Seperate build artefacts from version controlled code * Seperate build artefacts from version controlled code
* Domain Driven Design - in order to stay sustainable * Domain Driven Design - in order to stay sustainable
## Example Project
An example project which is using dda-devops-build can be found at: https://repo.prod.meissa.de/meissa/buildtest
## Installation ## Installation
Ensure that yout python3 version is at least Python 3.10 Ensure that yout python3 version is at least Python 3.10
@ -94,17 +97,9 @@ pip3 install -r requirements.txt
export PATH=$PATH:~/.local/bin export PATH=$PATH:~/.local/bin
``` ```
## Reference ## Example Project
* [DevopsBuild](./doc/DevopsBuild.md) An example project which is using dda-devops-build can be found at: https://repo.prod.meissa.de/meissa/buildtest
* [DevopsImageBuild](./doc/DevopsImageBuild.md)
* [DevopsTerraformBuild](./doc/DevopsTerraformBuild.md)
* [AwsProvider](doc/DevopsTerraformBuildWithAwsProvider.md)
* [DigitaloceanProvider](doc/DevopsTerraformBuildWithDigitaloceanProvider.md)
* [HetznerProvider](doc/DevopsTerraformBuildWithHetznerProvider.md)
* [ReleaseMixin](./doc/ReleaseMixin.md)
* [ProvsK3sBuild](doc/ProvsK3sBuild.md)
* [C4kBuild](doc/C4kBuild.md)
## Example Build ## Example Build
@ -186,10 +181,23 @@ def destroy(project):
pyb dev publish upload pyb dev publish upload
pip3 install --upgrade ddadevops --pre pip3 install --upgrade ddadevops --pre
pyb [patch|minor|major] prepare_release tag_bump_and_push_release pyb [patch|minor|major]
pip3 install --upgrade ddadevops pip3 install --upgrade ddadevops
``` ```
## Reference
* [DevopsBuild](./doc/DevopsBuild.md)
* [DevopsImageBuild](./doc/DevopsImageBuild.md)
* [DevopsTerraformBuild](./doc/DevopsTerraformBuild.md)
* [AwsProvider](doc/DevopsTerraformBuildWithAwsProvider.md)
* [DigitaloceanProvider](doc/DevopsTerraformBuildWithDigitaloceanProvider.md)
* [HetznerProvider](doc/DevopsTerraformBuildWithHetznerProvider.md)
* [ReleaseMixin](./doc/ReleaseMixin.md)
* [ProvsK3sBuild](doc/ProvsK3sBuild.md)
* [C4kBuild](doc/C4kBuild.md)
## Development & mirrors ## Development & mirrors
Development happens at: https://repo.prod.meissa.de/meissa/dda-devops-build Development happens at: https://repo.prod.meissa.de/meissa/dda-devops-build
@ -197,6 +205,7 @@ Development happens at: https://repo.prod.meissa.de/meissa/dda-devops-build
Mirrors are: Mirrors are:
* https://gitlab.com/domaindrivenarchitecture/dda-devops-build (issues and PR, CI) * https://gitlab.com/domaindrivenarchitecture/dda-devops-build (issues and PR, CI)
* https://github.com/DomainDrivenArchitecture/dda-devops-build
For more details about our repository model see: https://repo.prod.meissa.de/meissa/federate-your-repos For more details about our repository model see: https://repo.prod.meissa.de/meissa/federate-your-repos
@ -204,8 +213,3 @@ For more details about our repository model see: https://repo.prod.meissa.de/mei
Copyright © 2021 meissa GmbH Copyright © 2021 meissa GmbH
Licensed under the [Apache License, Version 2.0](LICENSE) (the "License") 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")

148
build.py
View file

@ -22,63 +22,71 @@ from ddadevops import *
use_plugin("python.core") use_plugin("python.core")
use_plugin("copy_resources") use_plugin("copy_resources")
use_plugin("filter_resources") use_plugin("filter_resources")
#use_plugin("python.unittest") # use_plugin("python.unittest")
#use_plugin("python.coverage") # use_plugin("python.coverage")
use_plugin("python.distutils") use_plugin("python.distutils")
#use_plugin("python.install_dependencies") # use_plugin("python.install_dependencies")
default_task = "dev" default_task = "dev"
name = "ddadevops" name = "ddadevops"
MODULE = "not-used" MODULE = "not-used"
PROJECT_ROOT_PATH = "." PROJECT_ROOT_PATH = "."
version = "4.0.12-dev" version = "4.13.2-dev"
summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud" summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud"
description = __doc__ description = __doc__
authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")] authors = [Author("meissa GmbH", "buero@meissa-gmbh.de")]
url = "https://repo.prod.meissa.de/meissa/dda-devops-build" url = "https://repo.prod.meissa.de/meissa/dda-devops-build"
requires_python = ">=3.10" # CHECK IF NEW VERSION EXISTS requires_python = ">=3.10" # CHECK IF NEW VERSION EXISTS
license = "Apache Software License" license = "Apache Software License"
@init @init
def initialize(project): def initialize(project):
#project.build_depends_on('mockito') # project.build_depends_on('mockito')
#project.build_depends_on('unittest-xml-reporting') # 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.set_property("verbose", True)
project.get_property("filter_resources_glob").append("main/python/ddadevops/__init__.py") project.get_property("filter_resources_glob").append(
"main/python/ddadevops/__init__.py"
)
project.set_property("dir_source_unittest_python", "src/test/python") project.set_property("dir_source_unittest_python", "src/test/python")
project.set_property("copy_resources_target", "$dir_dist/ddadevops") project.set_property("copy_resources_target", "$dir_dist/ddadevops")
project.get_property("copy_resources_glob").append("LICENSE") 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/terraform/*")
project.get_property("copy_resources_glob").append("src/main/resources/docker/image/resources/*") project.get_property("copy_resources_glob").append(
"src/main/resources/docker/image/resources/*"
)
project.include_file("ddadevops", "LICENSE") project.include_file("ddadevops", "LICENSE")
project.include_file("ddadevops", "src/main/resources/terraform/*") project.include_file("ddadevops", "src/main/resources/terraform/*")
project.include_file("ddadevops", "src/main/resources/docker/image/resources/*") project.include_file("ddadevops", "src/main/resources/docker/image/resources/*")
#project.set_property('distutils_upload_sign', True) # project.set_property('distutils_upload_sign', True)
#project.set_property('distutils_upload_sign_identity', '') # project.set_property('distutils_upload_sign_identity', '')
project.set_property("distutils_readme_description", True) project.set_property("distutils_readme_description", True)
project.set_property("distutils_description_overwrite", True) project.set_property("distutils_description_overwrite", True)
project.set_property("distutils_classifiers", [ project.set_property(
'License :: OSI Approved :: Apache Software License', "distutils_classifiers",
'Programming Language :: Python', [
'Programming Language :: Python :: 3', "License :: OSI Approved :: Apache Software License",
'Programming Language :: Python :: 3.8', "Programming Language :: Python",
'Programming Language :: Python :: 3.10', "Programming Language :: Python :: 3",
'Operating System :: POSIX :: Linux', "Programming Language :: Python :: 3.8",
'Operating System :: OS Independent', "Programming Language :: Python :: 3.10",
'Development Status :: 5 - Production/Stable', "Operating System :: POSIX :: Linux",
'Environment :: Console', "Operating System :: OS Independent",
'Intended Audience :: Developers', "Development Status :: 5 - Production/Stable",
'License :: OSI Approved :: Apache Software License', "Environment :: Console",
'Topic :: Software Development :: Build Tools', "Intended Audience :: Developers",
'Topic :: Software Development :: Quality Assurance', "License :: OSI Approved :: Apache Software License",
'Topic :: Software Development :: Testing' "Topic :: Software Development :: Build Tools",
]) "Topic :: Software Development :: Quality Assurance",
"Topic :: Software Development :: Testing",
],
)
input = { input = {
"name": name, "name": name,
@ -88,59 +96,99 @@ def initialize(project):
"build_types": [], "build_types": [],
"mixin_types": ["RELEASE"], "mixin_types": ["RELEASE"],
"release_primary_build_file": "build.py", "release_primary_build_file": "build.py",
"release_secondary_build_files": [
"infrastructure/python/build.py",
"infrastructure/dind/build.py",
"infrastructure/ddadevops/build.py",
"infrastructure/clj-cljs/build.py",
"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) build = ReleaseMixin(project, input)
build.initialize_build_dir() build.initialize_build_dir()
@task @task
def test(project): def test(project):
run("pytest", check=True) run("pytest", check=True)
@task @task
def lint(project): def lint(project):
run("flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 "+ run(
"--show-source --statistics src/main/python/ddadevops/", shell=True, check=True) "flake8 --max-line-length=120 --count --select=E9,F63,F7,F82 "
run("flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 "+ + "--show-source --statistics src/main/python/ddadevops/",
"--per-file-ignores=\"__init__.py:F401\" "+ shell=True,
"--ignore=E722,W503 --statistics src/main/python/ddadevops/", shell=True, check=True) check=True,
run("python -m mypy src/main/python/ddadevops/ --ignore-missing-imports "+ )
"--disable-error-code=attr-defined --disable-error-code=union-attr", shell=True, check=True) run(
run("pylint -d W0511,R0903,C0301,W0614,C0114,C0115,C0116,similarities,W1203,W0702,W0702,"+ "flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 "
"R0913,R0902,R0914,R1732,R1705,W0707,C0123,W0703,C0103 src/main/python/ddadevops/", shell=True, check=True) + '--per-file-ignores="__init__.py:F401" '
+ "--ignore=E722,W503 --statistics src/main/python/ddadevops/",
shell=True,
check=True,
)
run(
"python -m mypy src/main/python/ddadevops/ --ignore-missing-imports "
+ "--disable-error-code=attr-defined --disable-error-code=union-attr",
shell=True,
check=True,
)
@task @task
def patch(project): def patch(project):
build(project, "PATCH") linttest(project, "PATCH")
release(project)
@task @task
def minor(project): def minor(project):
build(project, "MINOR") linttest(project, "MINOR")
release(project)
@task @task
def major(project): def major(project):
build(project, "MAJOR") linttest(project, "MAJOR")
release(project)
@task @task
def dev(project): def dev(project):
build(project, "NONE") linttest(project, "NONE")
@task @task
def nothing(project): def prepare(project):
pass
@task
def prepare_release(project):
build = get_devops_build(project) build = get_devops_build(project)
build.prepare_release() build.prepare_release()
@task @task
def tag_bump_and_push_release(project): def tag(project):
build = get_devops_build(project) build = get_devops_build(project)
build.tag_bump_and_push_release() build.tag_bump_and_push_release()
def build(project, release_type): @task
def publish_artifacts(project):
build = get_devops_build(project)
build.publish_artifacts()
def release(project):
prepare(project)
tag(project)
def linttest(project, release_type):
build = get_devops_build(project) build = get_devops_build(project)
build.update_release_type(release_type) build.update_release_type(release_type)
test(project) test(project)

View file

@ -35,7 +35,12 @@ classDiagram
| name | name in context of build & ENV | - | | | name | name in context of build & ENV | - | |
## Example Usage ## Example Usage
### build.py
### Example project
A complete example project you can find on: https://repo.prod.meissa.de/meissa/buildtest
### Example of a build.py
```python ```python
from os import environ from os import environ

View file

@ -23,12 +23,9 @@ classDiagram
| build_dir_name | name of dir, build is executed in | target | | 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"] | [] | | 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"] | [] | | 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 ## Example Usage
### build.py ### build.py
```python ```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_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_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_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 ### Credentials Mapping defaults

33
doc/Images.md Normal file
View file

@ -0,0 +1,33 @@
# ddadevops Images
## ddadevops-clojure
Contains
* clojure
* shadowcljs
* lein
* java
* graalvm
* pybuilder, ddadevops
## ddadevops
Contains:
* pybuilder, ddadevops
## devops-build
Image is deprecated.
## ddadevops-dind
Contains:
* docker in docker
* pybuilder, ddadevops
## ddadevops-python
Contains:
* python 3.10
* python linting
* python setup-tools
* pybuilder, ddadevops

View file

@ -13,14 +13,17 @@ classDiagram
## Input ## Input
| name | description | default | | name | description | default |
| ----------------------------- | ----------------------------------------------------------------- | --------- | | --------------------------------- | ----------------------------------------------------------------- | --------- |
| k3s_provision_user | the user used to provision k3s | "root" | | k3s_provision_user | the user used to provision k3s | "root" |
| k3s_letsencrypt_email | email address used for letsencrypt | | | k3s_letsencrypt_email | email address used for letsencrypt | |
| k3s_letsencrypt_endpoint | letsencrypt endpoint. Valid values are staging, prod | "staging" | | 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_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_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_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 ### Credentials Mapping defaults

View file

@ -1,5 +1,15 @@
# ReleaseMixin # 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/) Support for releases following the trunk-based-release flow (see https://trunkbaseddevelopment.com/)
```mermaid ```mermaid
@ -8,6 +18,7 @@ classDiagram
prepare_release() - adjust all build files to carry the correct version & commit locally 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 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 update_release_type (release_type) - change the release type during run time
publish_artifacts() - publish release & artifacts to forgejo/gitea
} }
``` ```
@ -15,13 +26,103 @@ classDiagram
## Input ## Input
| name | description | default | | name | description | default |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------- | | ----------------------------- |-----------------------------------------------------------------------------------------------------------------------| --------------- |
| release_type | one of MAJOR, MINOR, PATCH, NONE | "NONE" | | release_type | one of MAJOR, MINOR, PATCH, NONE | "NONE" |
| release_main_branch | the name of your trank | "main" | | release_main_branch | the name of your trunk | "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_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_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 | [] |
| release_tag_prefix | Optional: Prefix of tag | "" |
## Example Usage ## Example Usage just for creating releases
### build.py
```python
from 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"],
}
build = ReleaseMixin(project, input)
build.initialize_build_dir()
@task
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 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 ### build.py
@ -36,7 +137,7 @@ PROJECT_ROOT_PATH = '..'
@init @init
def initialize(project): def initialize(project):
project.build_depends_on("ddadevops>=4.0.0") project.build_depends_on("ddadevops>=4.7.0")
input = { input = {
"name": name, "name": name,
@ -48,35 +149,23 @@ def initialize(project):
"release_type": "MINOR", "release_type": "MINOR",
"release_primary_build_file": "project.clj", "release_primary_build_file": "project.clj",
"release_secondary_build_files": ["package.json"], "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"],
} }
roject.build_depends_on("ddadevops>=4.0.0-dev")
build = ReleaseMixin(project, input) build = ReleaseMixin(project, input)
build.initialize_build_dir() build.initialize_build_dir()
@task @task
def prepare_release(project): def publish_artifacts(project):
build = get_devops_build(project) build = get_devops_build(project)
build.prepare_release() build.publish_artifacts()
@task
def build(project):
print("do the build")
@task
def publish(project):
print("publish your artefacts")
@task
def after_publish(project):
build = get_devops_build(project)
build.tag_bump_and_push_release()
``` ```
### call the build ### call the build
```bash ```bash
pyb prepare_release build publish after_publish git checkout "4.7.0"
pyb publish_artifacts
``` ```

View file

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

View file

@ -16,35 +16,85 @@ We discussed how we will handle releases in cooperation with gitlab-ci.
### Outcome of Eventstroming: Events ordered by time ### Outcome of Eventstroming: Events ordered by time
1. B: Pulls the latest changes * B: is the human devops
1. B: Possibly merge/rebase with main * S: is the build / ci system
1.
1. B: starts "create release-notes" ```mermaid
1. B: commits his changes with [skip-ci]. stateDiagram-v2
1. state prepare_release {
1. B: starts the release build and specifies major, minor, patch state "B: Pulls the latest changes" as pull
1. state "B: Possibly merge/rebase with main" as merge
1. S: does a git fetch & status and checks if there are no changes at origin state "B: starts 'create release-notes'" as rn
1. S: starts tests state "B: commits his changes with [skip-ci]." as c1
1. S: runs the linting
1. S: possibly does image building and image testing [*] --> pull
1. pull --> merge
1. S: version numbers are adjusted in project.clj/package.json to full version merge --> rn
1. S: change commit is tagged with git tag rn --> c1
1. S: version numbers are adjusted in project.clj/package.json to next snapshot version c1 --> [*]
1. S: makes a bump commit with [skip-ci]. }
1. S: push to gitlab/gitea along with git tags state release {
1. state "B: starts the release build and specifies major, minor, patch" as trigger
1. S: CI starts - for a new tag state "S: does a git fetch & status and checks if there are no changes at origin" as fetch
1. S: CI runs tests state "S: starts tests" as test
1. S: runs the linting state "S: runs the linting" as lint
1. S: makes artifacts state "S: possibly does image building and image testing" as image
1. S: possibly performs image building and image testing state "S: version numbers are adjusted in project.clj/package.json to full version" as vno
1. S: publishes images and artifacts state "S: change commit is tagged with git tag" as c2
1. state "S: version numbers are adjusted in project.clj/package.json to next snapshot " as snap
1. S: CI starts - for push with the last commit state "S: makes a bump commit with [skip-ci]." as c3
1. S: CI runs tests state "S: push to gitlab/gitea along with git tags" as push
1. S: performs the linting
[*] --> trigger
trigger --> fetch
fetch --> lint
fetch --> test
fetch --> image
test --> vno
lint --> vno
image --> vno
vno --> c2
c2 --> snap
snap --> c3
c3 --> push
push --> [*]
}
state ci_tag {
state "S: CI starts - for a new tag" as ct1
state "S: runs the linting" as ct2
state "S: CI runs tests" as ct3
state "S: makes artifacts" as ct4
state "S: possibly performs image building and image testing" as ct5
state "S: publishes images and artifacts" as ct6
[*] --> ct1
ct1 --> ct2
ct2 --> ct3
ct3 --> ct4
ct4 --> ct5
ct5 --> ct6
ct6 --> [*]
}
state ci_version_bump {
state "S: CI starts - for push with the last commit" as cvb1
state "S: CI runs tests" as cvb2
state "S: performs the linting" as cvb3
[*] --> cvb1
cvb1 --> cvb2
cvb2 --> cvb3
cvb3 --> [*]
}
[*] --> prepare_release
prepare_release --> release
release --> ci_tag
release --> ci_version_bump
ci_tag --> [*]
ci_version_bump --> [*]
```
## Consequences ## Consequences

View file

@ -1,8 +0,0 @@
adjust version no in build.py to release version no.
git commit -am "release"
git tag -am "release" [release version no]
git push --follow-tags
increase version no in build.py
git commit -am "version bump"
git push
pip3 install --upgrade ddadevops

View file

@ -0,0 +1,56 @@
from os import environ
from datetime import datetime
from pybuilder.core import task, init
from ddadevops import *
import logging
name = 'dda-backup'
MODULE = 'NOT_SET'
PROJECT_ROOT_PATH = '../..'
version = "4.12.2-dev"
@init
def initialize(project):
image_tag = version
if "dev" in image_tag:
image_tag += datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": ["IMAGE"],
"mixin_types": [],
"image_naming": "NAME_ONLY",
"image_tag": f"{image_tag}",
}
project.build_depends_on("ddadevops>=4.7.0")
build = DevopsImageBuild(project, input)
build.initialize_build_dir()
@task
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)
build.drun()
@task
def publish(project):
build = get_devops_build(project)
build.dockerhub_login()
build.dockerhub_publish()

View file

@ -0,0 +1,79 @@
## Init Statemachine
### Inputs
1. `restic-password: ""`
2. `restic-password-to-rotate: ""`
### Manual init the restic repository for the first time
1. apply backup-and-restore pod:
`kubectl scale deployment backup-restore --replicas=1`
2. exec into pod and execute restore pod (press tab to get your exact pod name)
`kubectl exec -it backup-restore-... -- /usr/local/bin/init.sh`
3. remove backup-and-restore pod:
`kubectl scale deployment backup-restore --replicas=0`
### Password Rotation
1. apply backup-and-restore pod:
`kubectl scale deployment backup-restore --replicas=1`
2. add new password to restic repository
`restic key add ....`
=> Trigger ::
field (1) credential current
filed (2) credential new
3. replace field (1) with (2) & clear (2)
4. remove old key - ???
`restic remove ....`
```mermaid
stateDiagram-v2
[*] --> init
init --> backup_ready: trigger, restic-password !empty
backup_ready --> new_password_added: restic-password !empty && restic-password-to-rotate !empty
new_password_added --> backup_ready: restic-password !empty && restic-password-to-rotate empty
```
### First Steps
1. Cloud Testserver hochfahren
2. Dort backup-restore deployment (leeres Secret mgl.?), neues Secret "rotation-credential-secret" als Daten
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

@ -0,0 +1,5 @@
FROM ubuntu:jammy
# install it
ADD resources /tmp/
RUN /tmp/install.sh

View file

@ -0,0 +1,70 @@
backup_file_path='files'
function init-file-repo() {
if [ -z ${CERTIFICATE_FILE} ];
then
restic -r ${RESTIC_REPOSITORY}/${backup_file_path} -v init
else
restic -r ${RESTIC_REPOSITORY}/${backup_file_path} -v init --cacert ${CERTIFICATE_FILE}
fi
}
# First arg is the directory, second is optional for the path to a certificate file
function backup-directory() {
local directory="$1"; shift
if [ -z ${CERTIFICATE_FILE} ];
then
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} unlock --cleanup-cache
cd ${directory} && restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} backup .
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} forget --group-by '' --keep-last 1 --keep-daily ${RESTIC_DAYS_TO_KEEP} --keep-monthly ${RESTIC_MONTHS_TO_KEEP} --prune
else
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} unlock --cleanup-cache --cacert ${CERTIFICATE_FILE}
cd ${directory} && restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} backup . --cacert ${CERTIFICATE_FILE}
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} forget --group-by '' --keep-last 1 --keep-daily ${RESTIC_DAYS_TO_KEEP} --keep-monthly ${RESTIC_MONTHS_TO_KEEP} --prune --cacert ${CERTIFICATE_FILE}
fi
}
# First arg is the directory, the remaining args are the sub-directories (relative to the first directory) to backup.
function backup-fs-from-directory() {
local directory="$1"; shift
if [ -z ${CERTIFICATE_FILE} ];
then
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} unlock --cleanup-cache
cd ${directory} && restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} backup $@
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} forget --group-by '' --keep-last 1 --keep-daily ${RESTIC_DAYS_TO_KEEP} --keep-monthly ${RESTIC_MONTHS_TO_KEEP} --prune
else
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} unlock --cleanup-cache --cacert ${CERTIFICATE_FILE}
cd ${directory} && restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} backup $@ --cacert ${CERTIFICATE_FILE}
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} forget --group-by '' --keep-last 1 --keep-daily ${RESTIC_DAYS_TO_KEEP} --keep-monthly ${RESTIC_MONTHS_TO_KEEP} --prune --cacert ${CERTIFICATE_FILE}
fi
}
# Das tut so nicht!
function restore-directory() {
local directory="$1"; shift
local snapshot_id="${1:-latest}"; shift
if [ -z ${CERTIFICATE_FILE} ];
then
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} unlock --cleanup-cache
rm -rf ${directory}*
restic -v -r $RESTIC_REPOSITORY/${backup_file_path} restore ${snapshot_id} --target ${directory}
else
restic -v -r ${RESTIC_REPOSITORY}/${backup_file_path} unlock --cleanup-cache --cacert ${CERTIFICATE_FILE}
rm -rf ${directory}*
restic -v -r $RESTIC_REPOSITORY/${backup_file_path} restore ${snapshot_id} --target ${directory} --cacert ${CERTIFICATE_FILE}
fi
}
function list-snapshot-files() {
if [ -z ${CERTIFICATE_FILE} ];
then
restic -r ${RESTIC_REPOSITORY}/${backup_file_path} snapshots
else
restic -r ${RESTIC_REPOSITORY}/${backup_file_path} snapshots --cacert ${CERTIFICATE_FILE}
fi
}

View file

@ -0,0 +1,21 @@
# usage: file_env VAR [DEFAULT]
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
function file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
exit 1
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}

View file

@ -0,0 +1,36 @@
#!/bin/bash
set -exo pipefail
function babashka_install() {
babashka_version="1.3.189"
curl -SsLo /tmp/babashka-${babashka_version}-linux-amd64.tar.gz https://github.com/babashka/babashka/releases/download/v${babashka_version}/babashka-${babashka_version}-linux-amd64.tar.gz
curl -SsLo /tmp/checksum https://github.com/babashka/babashka/releases/download/v${babashka_version}/babashka-${babashka_version}-linux-amd64.tar.gz.sha256
echo " /tmp/babashka-$babashka_version-linux-amd64.tar.gz"|tee -a /tmp/checksum
sha256sum -c --status /tmp/checksum
tar -C /tmp -xzf /tmp/babashka-${babashka_version}-linux-amd64.tar.gz
install -m 0700 -o root -g root /tmp/bb /usr/local/bin/
}
function main() {
{
upgradeSystem
apt-get install -qqy ca-certificates curl gnupg postgresql-client-14 restic
curl -Ss --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/postgresql-common_pgdg_archive_keyring.gpg
sh -c 'echo "deb [signed-by=/etc/apt/trusted.gpg.d/postgresql-common_pgdg_archive_keyring.gpg] https://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
upgradeSystem
babashka_install
} > /dev/null
update-ca-certificates
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
}
source /tmp/install_functions_debian.sh
DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes main

View file

@ -0,0 +1,149 @@
backup_pg_role_path='pg-role'
backup_pg_database_path='pg-database'
function init-command() {
restic -r ${RESTIC_REPOSITORY}/${backup_pg_role_path} -v init $@
}
function init-role-repo() {
if [ -z ${CERTIFICATE_FILE} ];
then
init-command
else
init-command --cacert ${CERTIFICATE_FILE}
fi
}
function init-database-command() {
restic -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} -v init $@
}
function init-database-repo() {
if [ -z ${CERTIFICATE_FILE} ];
then
init-database-command
else
init-database-command --cacert ${CERTIFICATE_FILE}
fi
}
function drop-create-db() {
psql -d template1 -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} \
--no-password -c "DROP DATABASE \"${POSTGRES_DB}\";"
psql -d template1 -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} \
--no-password -c "CREATE DATABASE \"${POSTGRES_DB}\";"
}
function create-pg-pass() {
local pg_host=${POSTGRES_HOST:-localhost}
echo "${pg_host}:${POSTGRES_DB}:${POSTGRES_USER}:${POSTGRES_PASSWORD}" > /root/.pgpass
echo "${POSTGRES_HOST}:template1:${POSTGRES_USER}:${POSTGRES_PASSWORD}" >> /root/.pgpass
chmod 0600 /root/.pgpass
}
function roles-unlock-command() {
restic -v -r ${RESTIC_REPOSITORY}/${backup_pg_role_path} unlock --cleanup-cache $@
}
function roles-forget-command() {
restic -v -r ${RESTIC_REPOSITORY}/${backup_pg_role_path} forget --group-by '' --keep-last 1 --keep-daily ${RESTIC_DAYS_TO_KEEP} --keep-monthly ${RESTIC_MONTHS_TO_KEEP} --prune $@
}
function backup-roles() {
local role_prefix="$1"; shift
if [ -z ${CERTIFICATE_FILE} ];
then
roles-unlock-command
pg_dumpall -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} -U${POSTGRES_USER} --no-password --roles-only | \
grep ${role_prefix} | restic -r ${RESTIC_REPOSITORY}/${backup_pg_role_path} backup --stdin
roles-forget-command
else
roles-unlock-command --cacert ${CERTIFICATE_FILE}
pg_dumpall -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} -U${POSTGRES_USER} --no-password --roles-only | \
grep ${role_prefix} | restic -r ${RESTIC_REPOSITORY}/${backup_pg_role_path} backup --stdin --cacert ${CERTIFICATE_FILE}
roles-forget-command --cacert ${CERTIFICATE_FILE}
fi
}
function db-unlock-command() {
restic -v -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} unlock --cleanup-cache $@
}
function db-forget-command() {
restic -v -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} forget --group-by '' --keep-last 1 --keep-daily ${RESTIC_DAYS_TO_KEEP} --keep-monthly ${RESTIC_MONTHS_TO_KEEP} --prune $@
}
function backup-db-dump() {
if [ -z ${CERTIFICATE_FILE} ];
then
db-unlock-command
pg_dump -d ${POSTGRES_DB} -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} \
-U ${POSTGRES_USER} --no-password --serializable-deferrable | \
restic -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} backup --stdin
db-forget-command
else
db-unlock-command --cacert ${CERTIFICATE_FILE}
pg_dump -d ${POSTGRES_DB} -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} \
-U ${POSTGRES_USER} --no-password --serializable-deferrable | \
restic -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} backup --stdin --cacert ${CERTIFICATE_FILE}
db-forget-command --cacert ${CERTIFICATE_FILE}
fi
}
function restore-roles() {
local snapshot_id="${1:-latest}"; shift
if [ -z ${CERTIFICATE_FILE} ];
then
roles-unlock-command
restic -r ${RESTIC_REPOSITORY}/${backup_pg_role_path} dump ${snapshot_id} stdin | \
psql -d template1 -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} \
--no-password
else
roles-unlock-command --cacert ${CERTIFICATE_FILE}
restic -r ${RESTIC_REPOSITORY}/${backup_pg_role_path} dump ${snapshot_id} stdin --cacert ${CERTIFICATE_FILE} | \
psql -d template1 -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} \
--no-password
fi
}
function restore-db() {
local snapshot_id="${1:-latest}"; shift
if [ -z ${CERTIFICATE_FILE} ];
then
db-unlock-command
restic -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} dump ${snapshot_id} stdin | \
psql -d ${POSTGRES_DB} -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} \
--no-password
else
db-unlock-command --cacert ${CERTIFICATE_FILE}
restic -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} dump ${snapshot_id} stdin --cacert ${CERTIFICATE_FILE} | \
psql -d ${POSTGRES_DB} -h ${POSTGRES_SERVICE} -p ${POSTGRES_PORT} -U ${POSTGRES_USER} \
--no-password
fi
}
function list-snapshot-roles() {
if [ -z ${CERTIFICATE_FILE} ];
then
restic -r ${RESTIC_REPOSITORY}/${backup_pg_role_path} snapshots
else
restic -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} snapshots --cacert ${CERTIFICATE_FILE}
fi
}
function list-snapshot-db() {
if [ -z ${CERTIFICATE_FILE} ];
then
restic -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} snapshots
else
restic -r ${RESTIC_REPOSITORY}/${backup_pg_database_path} snapshots --cacert ${CERTIFICATE_FILE}
fi
}

View file

@ -0,0 +1,51 @@
#! /usr/bin/env bb
(ns restic-management
(:require
[clojure.spec.alpha :as s]
[clojure.java.io :as io]
[clojure.edn :as edn]))
(s/def ::state string?)
(s/def ::backup-repository-state
(s/keys :req-un [::state]))
(def state {:state ""})
(defn store-backup-repository-state [s]
(spit "backup-repository-state.edn" s))
(defn read-backup-repository-state []
(try
(with-open [r (io/reader "backup-repository-state.edn")]
(edn/read (java.io.PushbackReader. r)))
(catch java.io.IOException e
(printf "Couldn't open '%s': %s\n" "backup-repository-state.edn" (.getMessage e)))
(catch RuntimeException e
(printf "Error parsing edn file '%s': %s\n" "backup-repository-state.edn" (.getMessage e)))))
(defn read-secret [s]
(slurp (str "/var/run/secrets/" s)))
;"/var/run/secrets/rotation-credential-secret/rotation-credential"))
;(println (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

@ -0,0 +1,56 @@
from os import environ
from datetime import datetime
from pybuilder.core import task, init
from ddadevops import *
name = "ddadevops"
MODULE = "clj-cljs"
PROJECT_ROOT_PATH = "../.."
version = "4.13.2-dev"
@init
def initialize(project):
image_tag = version
if "dev" in image_tag:
image_tag += datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": ["IMAGE"],
"mixin_types": [],
"image_naming": "NAME_AND_MODULE",
"image_tag": f"{image_tag}",
}
project.build_depends_on("ddadevops>=4.0.0")
build = DevopsImageBuild(project, input)
build.initialize_build_dir()
@task
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)
build.dockerhub_login()
build.dockerhub_publish()

View file

@ -0,0 +1,4 @@
FROM node:lts-bookworm-slim
ADD resources /tmp
RUN /tmp/install.sh

View file

@ -0,0 +1,45 @@
#!/bin/bash
set -exo pipefail
function main() {
{
upgradeSystem
mkdir -p /usr/share/man/man1
apt-get -qqy install curl openjdk-17-jre-headless leiningen
# shadow-cljs
npm install -g npm
npm install -g --save-dev shadow-cljs
# download kubeconform & graalvm
kubeconform_version="0.6.4"
curl -SsLo /tmp/kubeconform-linux-amd64.tar.gz https://github.com/yannh/kubeconform/releases/download/v${kubeconform_version}/kubeconform-linux-amd64.tar.gz
curl -SsLo /tmp/CHECKSUMS https://github.com/yannh/kubeconform/releases/download/v${kubeconform_version}/CHECKSUMS
# checksum kubeconform
checksum
# install kubeconform
tar -C /usr/local/bin -xf /tmp/kubeconform-linux-amd64.tar.gz --exclude=LICENSE
#install pyb
apt-get -qqy install python3 python3-pip git
pip3 install pybuilder 'ddadevops>=4.7.0' deprecation dda-python-terraform boto3 pyyaml inflection --break-system-packages
#check
lein --help
cleanupDocker
} > /dev/null
}
function checksum() {
awk '{print $1 " /tmp/" $2}' /tmp/CHECKSUMS|sed -n '2p' > /tmp/kubeconform-checksum
cat /tmp/kubeconform-checksum
sha256sum -c --status /tmp/kubeconform-checksum
}
source /tmp/install_functions_debian.sh
DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes main

View file

@ -1,14 +1,18 @@
from os import environ from os import environ
from datetime import datetime
from pybuilder.core import task, init from pybuilder.core import task, init
from ddadevops import * from ddadevops import *
name = "clojure" name = "ddadevops"
MODULE = "image" MODULE = "clj"
PROJECT_ROOT_PATH = "../.." PROJECT_ROOT_PATH = "../.."
version = "4.13.2-dev"
@init @init
def initialize(project): def initialize(project):
image_tag = version
if "dev" in image_tag:
image_tag += datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
input = { input = {
"name": name, "name": name,
@ -17,6 +21,8 @@ def initialize(project):
"project_root_path": PROJECT_ROOT_PATH, "project_root_path": PROJECT_ROOT_PATH,
"build_types": ["IMAGE"], "build_types": ["IMAGE"],
"mixin_types": [], "mixin_types": [],
"image_naming": "NAME_AND_MODULE",
"image_tag": f"{image_tag}",
} }
project.build_depends_on("ddadevops>=4.0.0") project.build_depends_on("ddadevops>=4.0.0")

View file

@ -0,0 +1,6 @@
FROM debian:stable-slim
ADD resources /tmp
RUN /tmp/install.sh
ENV LANG=en_US.UTF-8 \
JAVA_HOME=/usr/lib/jvm/graalvm

View file

@ -0,0 +1,57 @@
#!/bin/bash
set -exo pipefail
function main() {
{
upgradeSystem
apt-get -qqy install curl git openjdk-17-jre-headless leiningen build-essential libz-dev zlib1g-dev
# download kubeconform & graalvm
kubeconform_version="0.6.4"
graalvm_jdk_version="21.0.2"
curl -SsLo /tmp/kubeconform-linux-amd64.tar.gz https://github.com/yannh/kubeconform/releases/download/v${kubeconform_version}/kubeconform-linux-amd64.tar.gz
curl -SsLo /tmp/CHECKSUMS https://github.com/yannh/kubeconform/releases/download/v${kubeconform_version}/CHECKSUMS
curl -SsLo /tmp/graalvm-community-jdk.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${graalvm_jdk_version}/graalvm-community-jdk-${graalvm_jdk_version}_linux-x64_bin.tar.gz
curl -SsLo /tmp/graalvm-checksum https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-${graalvm_jdk_version}/graalvm-community-jdk-${graalvm_jdk_version}_linux-x64_bin.tar.gz.sha256
# checksum kubeconform & graalvm-jdk
checksum
# install kubeconform
tar -C /usr/local/bin -xf /tmp/kubeconform-linux-amd64.tar.gz --exclude=LICENSE
# install graalvm
tar -C /usr/lib/jvm/ -xf /tmp/graalvm-community-jdk.tar.gz
dirname_graalvm=$(ls /usr/lib/jvm/|grep -e graa)
ln -s /usr/lib/jvm/$dirname_graalvm /usr/lib/jvm/graalvm
ln -s /usr/lib/jvm/graalvm/bin/gu /usr/local/bin
update-alternatives --install /usr/bin/java java /usr/lib/jvm/graalvm/bin/java 2
ln -s /usr/lib/jvm/graalvm/bin/native-image /usr/local/bin
#install pyb
apt-get -qqy install python3 python3-pip
pip3 install pybuilder 'ddadevops>=4.7.0' deprecation dda-python-terraform boto3 pyyaml inflection --break-system-packages
#check
native-image --version
lein -v
cleanupDocker
} > /dev/null
}
function checksum() {
#kubeconform
awk '{print $1 " /tmp/" $2}' /tmp/CHECKSUMS|sed -n '2p' > /tmp/kubeconform-checksum
sha256sum -c --status /tmp/kubeconform-checksum
#graalvm
echo " /tmp/graalvm-community-jdk.tar.gz"|tee -a /tmp/graalvm-checksum
sha256sum -c --status /tmp/graalvm-checksum
}
source /tmp/install_functions_debian.sh
DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes main

View file

@ -1,2 +0,0 @@
d7a5cb848b783c15119316d716d8a74bf11c9e3ab050f3adf28e0678a6018467 kubeconform-v0.4.7.tar.gz
bbd3e03025168172a76c2a29e6a14c1c37e3476b30774259c3ef5952fb86f470 graalvm-ce-java11-linux-amd64-21.2.0.tar.gz

View file

@ -1,43 +0,0 @@
#!/bin/bash
set -eux
function main() {
upgradeSystem
mkdir -p /usr/share/man/man1
apt -qqy install openjdk-11-jre-headless leiningen curl build-essential libz-dev zlib1g-dev
# shadow-cljs
npm install -g --save-dev shadow-cljs
# download kubeconform & graalvm
curl -Lo /tmp/kubeconform-v0.4.7.tar.gz https://github.com/yannh/kubeconform/releases/download/v0.4.7/kubeconform-linux-amd64.tar.gz
curl -Lo /tmp/graalvm-ce-java11-linux-amd64-21.2.0.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.2.0/graalvm-ce-java11-linux-amd64-21.2.0.tar.gz
# checksum
cd /tmp
sha256sum --check CHECKSUMS
# install kubeconform
tar -xf /tmp/kubeconform-v0.4.7.tar.gz
cp kubeconform /usr/local/bin
# install graalvm
tar -xzf graalvm-ce-java11-linux-amd64-21.2.0.tar.gz
mv graalvm-ce-java11-21.2.0 /usr/lib/jvm/
ln -s /usr/lib/jvm/graalvm-ce-java11-21.2.0 /usr/lib/jvm/graalvm
ln -s /usr/lib/jvm/graalvm/bin/gu /usr/local/bin
update-alternatives --install /usr/bin/java java /usr/lib/jvm/graalvm/bin/java 2
gu install native-image
ln -s /usr/lib/jvm/graalvm/bin/native-image /usr/local/bin
#install lein
/tmp/lein.sh
cleanupDocker
}
source /tmp/install_functions.sh
main

View file

@ -1,423 +0,0 @@
#!/usr/bin/env bash
# Ensure this file is executable via `chmod a+x lein`, then place it
# somewhere on your $PATH, like ~/bin. The rest of Leiningen will be
# installed upon first run into the ~/.lein/self-installs directory.
function msg {
echo "$@" 1>&2
}
export LEIN_VERSION="2.9.6"
# Must be sha256sum, will be replaced by bin/release
export LEIN_CHECKSUM='41c543f73eec4327dc20e60d5d820fc2a9dc772bc671610b9c385d9c4f5970b8'
case $LEIN_VERSION in
*SNAPSHOT) SNAPSHOT="YES" ;;
*) SNAPSHOT="NO" ;;
esac
if [[ "$CLASSPATH" != "" ]]; then
cat <<-'EOS' 1>&2
WARNING: You have $CLASSPATH set, probably by accident.
It is strongly recommended to unset this before proceeding.
EOS
fi
if [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]]; then
delimiter=";"
else
delimiter=":"
fi
if [[ "$OSTYPE" == "cygwin" ]]; then
cygwin=true
else
cygwin=false
fi
function command_not_found {
msg "Leiningen couldn't find $1 in your \$PATH ($PATH), which is required."
exit 1
}
function make_native_path {
# ensure we have native paths
if $cygwin && [[ "$1" == /* ]]; then
echo -n "$(cygpath -wp "$1")"
elif [[ "$OSTYPE" == "msys" && "$1" == /?/* ]]; then
echo -n "$(sh -c "(cd $1 2</dev/null && pwd -W) || echo $1 | sed 's/^\\/\([a-z]\)/\\1:/g'")"
else
echo -n "$1"
fi
}
# usage : add_path PATH_VAR [PATH]...
function add_path {
local path_var="$1"
shift
while [ -n "$1" ];do
# http://bashify.com/?Useful_Techniques:Indirect_Variables:Indirect_Assignment
if [[ -z ${!path_var} ]]; then
export ${path_var}="$(make_native_path "$1")"
else
export ${path_var}="${!path_var}${delimiter}$(make_native_path "$1")"
fi
shift
done
}
function download_failed_message {
cat <<-EOS 1>&2
Failed to download $1 (exit code $2)
It's possible your HTTP client's certificate store does not have the
correct certificate authority needed. This is often caused by an
out-of-date version of libssl. It's also possible that you're behind a
firewall and haven't set HTTP_PROXY and HTTPS_PROXY.
EOS
}
function checksum_failed_message {
cat <<-EOS 1>&2
Failed to properly download $1
The checksum was mismatched. and we could not verify the downloaded
file. We expected a sha256 of
$2 and actually had
$3.
We used '$SHASUM_CMD' to verify the downloaded file.
EOS
}
function self_install {
if [ -r "$LEIN_JAR" ]; then
cat <<-EOS 1>&2
The self-install jar already exists at $LEIN_JAR.
If you wish to re-download, delete it and rerun "$0 self-install".
EOS
exit 1
fi
msg "Downloading Leiningen to $LEIN_JAR now..."
mkdir -p "$(dirname "$LEIN_JAR")"
LEIN_URL="https://github.com/technomancy/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.zip"
$HTTP_CLIENT "$LEIN_JAR.pending" "$LEIN_URL"
local exit_code=$?
if [ $exit_code == 0 ]; then
printf "$LEIN_CHECKSUM $LEIN_JAR.pending\n" > "$LEIN_JAR.pending.shasum"
$SHASUM_CMD -c "$LEIN_JAR.pending.shasum"
if [ $? == 0 ]; then
mv -f "$LEIN_JAR.pending" "$LEIN_JAR"
else
got_sum="$($SHASUM_CMD "$LEIN_JAR.pending" | cut -f 1 -d ' ')"
checksum_failed_message "$LEIN_URL" "$LEIN_CHECKSUM" "$got_sum"
rm "$LEIN_JAR.pending" 2> /dev/null
exit 1
fi
else
rm "$LEIN_JAR.pending" 2> /dev/null
download_failed_message "$LEIN_URL" "$exit_code"
exit 1
fi
}
NOT_FOUND=1
ORIGINAL_PWD="$PWD"
while [ ! -r "$PWD/project.clj" ] && [ "$PWD" != "/" ] && [ $NOT_FOUND -ne 0 ]
do
cd ..
if [ "$(dirname "$PWD")" = "/" ]; then
NOT_FOUND=0
cd "$ORIGINAL_PWD"
fi
done
export LEIN_HOME="${LEIN_HOME:-"$HOME/.lein"}"
for f in "/etc/leinrc" "$LEIN_HOME/leinrc" ".leinrc"; do
if [ -e "$f" ]; then
source "$f"
fi
done
if $cygwin; then
export LEIN_HOME=$(cygpath -w "$LEIN_HOME")
fi
LEIN_JAR="$LEIN_HOME/self-installs/leiningen-$LEIN_VERSION-standalone.jar"
# normalize $0 on certain BSDs
if [ "$(dirname "$0")" = "." ]; then
SCRIPT="$(which "$(basename "$0")")"
if [ -z "$SCRIPT" ]; then
SCRIPT="$0"
fi
else
SCRIPT="$0"
fi
# resolve symlinks to the script itself portably
while [ -h "$SCRIPT" ] ; do
ls=$(ls -ld "$SCRIPT")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT="$(dirname "$SCRIPT"$)/$link"
fi
done
BIN_DIR="$(dirname "$SCRIPT")"
export LEIN_JVM_OPTS="${LEIN_JVM_OPTS-"-Xverify:none -XX:+TieredCompilation -XX:TieredStopAtLevel=1"}"
# This needs to be defined before we call HTTP_CLIENT below
if [ "$HTTP_CLIENT" = "" ]; then
if type -p curl >/dev/null 2>&1; then
if [ "$https_proxy" != "" ]; then
CURL_PROXY="-x $https_proxy"
fi
HTTP_CLIENT="curl $CURL_PROXY -f -L -o"
else
HTTP_CLIENT="wget -O"
fi
fi
# This needs to be defined before we call SHASUM_CMD below
if [ "$SHASUM_CMD" = "" ]; then
if type -p sha256sum >/dev/null 2>&1; then
export SHASUM_CMD="sha256sum"
elif type -p shasum >/dev/null 2>&1; then
export SHASUM_CMD="shasum --algorithm 256"
elif type -p sha256 >/dev/null 2>&1; then
export SHASUM_CMD="sha256 -q"
else
command_not_found sha256sum
fi
fi
# When :eval-in :classloader we need more memory
grep -E -q '^\s*:eval-in\s+:classloader\s*$' project.clj 2> /dev/null && \
export LEIN_JVM_OPTS="$LEIN_JVM_OPTS -Xms64m -Xmx512m"
if [ -r "$BIN_DIR/../src/leiningen/version.clj" ]; then
# Running from source checkout
LEIN_DIR="$(cd $(dirname "$BIN_DIR");pwd -P)"
# Need to use lein release to bootstrap the leiningen-core library (for aether)
if [ ! -r "$LEIN_DIR/leiningen-core/.lein-bootstrap" ]; then
cat <<-'EOS' 1>&2
Leiningen is missing its dependencies.
Please run "lein bootstrap" in the leiningen-core/ directory
with a stable release of Leiningen. See CONTRIBUTING.md for details.
EOS
exit 1
fi
# If project.clj for lein or leiningen-core changes, we must recalculate
LAST_PROJECT_CHECKSUM=$(cat "$LEIN_DIR/.lein-project-checksum" 2> /dev/null)
PROJECT_CHECKSUM=$(sum "$LEIN_DIR/project.clj" "$LEIN_DIR/leiningen-core/project.clj")
if [ "$PROJECT_CHECKSUM" != "$LAST_PROJECT_CHECKSUM" ]; then
if [ -r "$LEIN_DIR/.lein-classpath" ]; then
rm "$LEIN_DIR/.lein-classpath"
fi
fi
# Use bin/lein to calculate its own classpath.
if [ ! -r "$LEIN_DIR/.lein-classpath" ] && [ "$1" != "classpath" ]; then
msg "Recalculating Leiningen's classpath."
cd "$LEIN_DIR"
LEIN_NO_USER_PROFILES=1 "$LEIN_DIR/bin/lein" classpath .lein-classpath
sum "$LEIN_DIR/project.clj" "$LEIN_DIR/leiningen-core/project.clj" > \
.lein-project-checksum
cd -
fi
mkdir -p "$LEIN_DIR/target/classes"
export LEIN_JVM_OPTS="$LEIN_JVM_OPTS -Dclojure.compile.path=$LEIN_DIR/target/classes"
add_path CLASSPATH "$LEIN_DIR/leiningen-core/src/" "$LEIN_DIR/leiningen-core/resources/" \
"$LEIN_DIR/test:$LEIN_DIR/target/classes" "$LEIN_DIR/src" ":$LEIN_DIR/resources"
if [ -r "$LEIN_DIR/.lein-classpath" ]; then
add_path CLASSPATH "$(cat "$LEIN_DIR/.lein-classpath" 2> /dev/null)"
else
add_path CLASSPATH "$(cat "$LEIN_DIR/leiningen-core/.lein-bootstrap" 2> /dev/null)"
fi
else # Not running from a checkout
add_path CLASSPATH "$LEIN_JAR"
if [ "$LEIN_USE_BOOTCLASSPATH" != "no" ]; then
LEIN_JVM_OPTS="-Xbootclasspath/a:$LEIN_JAR $LEIN_JVM_OPTS"
fi
if [ ! -r "$LEIN_JAR" -a "$1" != "self-install" ]; then
self_install
fi
fi
if [ ! -x "$JAVA_CMD" ] && ! type -f java >/dev/null
then
msg "Leiningen couldn't find 'java' executable, which is required."
msg "Please either set JAVA_CMD or put java (>=1.6) in your \$PATH ($PATH)."
exit 1
fi
export LEIN_JAVA_CMD="${LEIN_JAVA_CMD:-${JAVA_CMD:-java}}"
if [[ -z "${DRIP_INIT+x}" && "$(basename "$LEIN_JAVA_CMD")" == *drip* ]]; then
export DRIP_INIT="$(printf -- '-e\n(require (quote leiningen.repl))')"
export DRIP_INIT_CLASS="clojure.main"
fi
# Support $JAVA_OPTS for backwards-compatibility.
export JVM_OPTS="${JVM_OPTS:-"$JAVA_OPTS"}"
# Handle jline issue with cygwin not propagating OSTYPE through java subprocesses: https://github.com/jline/jline2/issues/62
cygterm=false
if $cygwin; then
case "$TERM" in
rxvt* | xterm* | vt*) cygterm=true ;;
esac
fi
if $cygterm; then
LEIN_JVM_OPTS="$LEIN_JVM_OPTS -Djline.terminal=jline.UnixTerminal"
stty -icanon min 1 -echo > /dev/null 2>&1
fi
# TODO: investigate http://skife.org/java/unix/2011/06/20/really_executable_jars.html
# If you're packaging this for a package manager (.deb, homebrew, etc)
# you need to remove the self-install and upgrade functionality or see lein-pkg.
if [ "$1" = "self-install" ]; then
if [ -r "$BIN_DIR/../src/leiningen/version.clj" ]; then
cat <<-'EOS' 1>&2
Running self-install from a checkout is not supported.
See CONTRIBUTING.md for SNAPSHOT-specific build instructions.
EOS
exit 1
fi
msg "Manual self-install is deprecated; it will run automatically when necessary."
self_install
elif [ "$1" = "upgrade" ] || [ "$1" = "downgrade" ]; then
if [ "$LEIN_DIR" != "" ]; then
msg "The upgrade task is not meant to be run from a checkout."
exit 1
fi
if [ $SNAPSHOT = "YES" ]; then
cat <<-'EOS' 1>&2
The upgrade task is only meant for stable releases.
See the "Bootstrapping" section of CONTRIBUTING.md.
EOS
exit 1
fi
if [ ! -w "$SCRIPT" ]; then
msg "You do not have permission to upgrade the installation in $SCRIPT"
exit 1
else
TARGET_VERSION="${2:-stable}"
echo "The script at $SCRIPT will be upgraded to the latest $TARGET_VERSION version."
echo -n "Do you want to continue [Y/n]? "
read RESP
case "$RESP" in
y|Y|"")
echo
msg "Upgrading..."
TARGET="/tmp/lein-${$}-upgrade"
if $cygwin; then
TARGET=$(cygpath -w "$TARGET")
fi
LEIN_SCRIPT_URL="https://github.com/technomancy/leiningen/raw/$TARGET_VERSION/bin/lein"
$HTTP_CLIENT "$TARGET" "$LEIN_SCRIPT_URL"
if [ $? == 0 ]; then
cmp -s "$TARGET" "$SCRIPT"
if [ $? == 0 ]; then
msg "Leiningen is already up-to-date."
fi
mv "$TARGET" "$SCRIPT" && chmod +x "$SCRIPT"
unset CLASSPATH
exec "$SCRIPT" version
else
download_failed_message "$LEIN_SCRIPT_URL"
fi;;
*)
msg "Aborted."
exit 1;;
esac
fi
else
if $cygwin; then
# When running on Cygwin, use Windows-style paths for java
ORIGINAL_PWD=$(cygpath -w "$ORIGINAL_PWD")
fi
# apply context specific CLASSPATH entries
if [ -f .lein-classpath ]; then
add_path CLASSPATH "$(cat .lein-classpath)"
fi
if [ -n "$DEBUG" ]; then
msg "Leiningen's classpath: $CLASSPATH"
fi
if [ -r .lein-fast-trampoline ]; then
export LEIN_FAST_TRAMPOLINE='y'
fi
if [ "$LEIN_FAST_TRAMPOLINE" != "" ] && [ -r project.clj ]; then
INPUTS="$* $(cat project.clj) $LEIN_VERSION $(test -f "$LEIN_HOME/profiles.clj" && cat "$LEIN_HOME/profiles.clj") $(test -f profiles.clj && cat profiles.clj)"
INPUT_CHECKSUM=$(echo "$INPUTS" | $SHASUM_CMD | cut -f 1 -d " ")
# Just don't change :target-path in project.clj, mkay?
TRAMPOLINE_FILE="target/trampolines/$INPUT_CHECKSUM"
else
if hash mktemp 2>/dev/null; then
# Check if mktemp is available before using it
TRAMPOLINE_FILE="$(mktemp /tmp/lein-trampoline-XXXXXXXXXXXXX)"
else
TRAMPOLINE_FILE="/tmp/lein-trampoline-$$"
fi
trap 'rm -f $TRAMPOLINE_FILE' EXIT
fi
if $cygwin; then
TRAMPOLINE_FILE=$(cygpath -w "$TRAMPOLINE_FILE")
fi
if [ "$INPUT_CHECKSUM" != "" ] && [ -r "$TRAMPOLINE_FILE" ]; then
if [ -n "$DEBUG" ]; then
msg "Fast trampoline with $TRAMPOLINE_FILE."
fi
exec sh -c "exec $(cat "$TRAMPOLINE_FILE")"
else
export TRAMPOLINE_FILE
"$LEIN_JAVA_CMD" \
-Dfile.encoding=UTF-8 \
-Dmaven.wagon.http.ssl.easy=false \
-Dmaven.wagon.rto=10000 \
$LEIN_JVM_OPTS \
-Dleiningen.input-checksum="$INPUT_CHECKSUM" \
-Dleiningen.original.pwd="$ORIGINAL_PWD" \
-Dleiningen.script="$SCRIPT" \
-classpath "$CLASSPATH" \
clojure.main -m leiningen.core.main "$@"
EXIT_CODE=$?
if $cygterm ; then
stty icanon echo > /dev/null 2>&1
fi
if [ -r "$TRAMPOLINE_FILE" ] && [ "$LEIN_TRAMPOLINE_WARMUP" = "" ]; then
TRAMPOLINE="$(cat "$TRAMPOLINE_FILE")"
if [ "$INPUT_CHECKSUM" = "" ]; then # not using fast trampoline
rm "$TRAMPOLINE_FILE"
fi
if [ "$TRAMPOLINE" = "" ]; then
exit $EXIT_CODE
else
exec sh -c "exec $TRAMPOLINE"
fi
else
exit $EXIT_CODE
fi
fi
fi

View file

@ -1,11 +0,0 @@
FROM clojure
RUN apt update
RUN apt -yqq --no-install-recommends --yes install curl default-jre-headless
RUN curl -L -o /tmp/serverspec.jar \
https://github.com/DomainDrivenArchitecture/dda-serverspec-crate/releases/download/2.0.0/dda-serverspec-standalone.jar
COPY serverspec.edn /tmp/serverspec.edn
RUN java -jar /tmp/serverspec.jar /tmp/serverspec.edn -v

View file

@ -1 +0,0 @@
{}

View file

@ -0,0 +1,57 @@
from os import environ
from datetime import datetime
from pybuilder.core import task, init
from ddadevops import *
name = "ddadevops"
MODULE = "ddadevops"
PROJECT_ROOT_PATH = "../.."
version = "4.13.2-dev"
@init
def initialize(project):
image_tag = version
if "dev" in image_tag:
image_tag += datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": ["IMAGE"],
"mixin_types": [],
"image_naming": "NAME_ONLY",
"image_tag": f"{image_tag}",
}
project.build_depends_on("ddadevops>=4.9.0")
build = DevopsImageBuild(project, input)
build.initialize_build_dir()
@task
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)
build.dockerhub_login()
build.dockerhub_publish()

View file

@ -0,0 +1,5 @@
FROM python:3.10-alpine
ADD resources /tmp
RUN /tmp/install.sh

View file

@ -0,0 +1,19 @@
#!/bin/sh
set -exo pipefail
function main() {
{
upgradeSystem
apk add --no-cache python3 py3-pip openssl-dev bash git curl
python3 -m pip install -U pip
pip3 install pybuilder ddadevops deprecation dda-python-terraform boto3 pyyaml inflection
cleanupDocker
} > /dev/null
}
source /tmp/install_functions_alpine.sh
main

View file

@ -1,6 +0,0 @@
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 bash git;
RUN python3 -m pip install -U pip;
RUN pip3 install pybuilder ddadevops deprecation dda-python-terraform boto3 pyyaml;

View file

@ -1,11 +0,0 @@
FROM devops-build
RUN apk update
RUN apk add curl openjdk8
RUN curl -L -o /tmp/serverspec.jar \
https://github.com/DomainDrivenArchitecture/dda-serverspec-crate/releases/download/2.0.0/dda-serverspec-standalone.jar
COPY serverspec.edn /tmp/serverspec.edn
RUN java -jar /tmp/serverspec.jar /tmp/serverspec.edn -v

View file

@ -0,0 +1,57 @@
from os import environ
from datetime import datetime
from pybuilder.core import task, init
from ddadevops import *
name = "ddadevops"
MODULE = "dind"
PROJECT_ROOT_PATH = "../.."
version = "4.13.2-dev"
@init
def initialize(project):
image_tag = version
if "dev" in image_tag:
image_tag += datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": ["IMAGE"],
"mixin_types": [],
"image_naming": "NAME_AND_MODULE",
"image_tag": f"{image_tag}",
}
project.build_depends_on("ddadevops>=4.7.0")
build = DevopsImageBuild(project, input)
build.initialize_build_dir()
@task
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)
build.dockerhub_login()
build.dockerhub_publish()

View file

@ -0,0 +1,5 @@
FROM docker:latest
WORKDIR /tmp
ADD resources ./
RUN ./install.sh

View file

@ -0,0 +1,17 @@
#!/bin/sh
set -exo pipefail
function main() {
{
upgradeSystem
apk add --no-cache python3 py3-pip openssl-dev bash git
pip3 install --break-system-packages pybuilder ddadevops deprecation dda-python-terraform boto3 pyyaml inflection
cleanupDocker
} > /dev/null
}
source /tmp/install_functions_alpine.sh
main

View file

@ -0,0 +1,57 @@
from os import environ
from datetime import datetime
from pybuilder.core import task, init
from ddadevops import *
name = "ddadevops"
MODULE = "kotlin"
PROJECT_ROOT_PATH = "../.."
version = "4.13.2-dev"
@init
def initialize(project):
image_tag = version
if "dev" in image_tag:
image_tag += datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": ["IMAGE"],
"mixin_types": [],
"image_naming": "NAME_AND_MODULE",
"image_tag": f"{image_tag}",
}
project.build_depends_on("ddadevops>=4.0.0")
build = DevopsImageBuild(project, input)
build.initialize_build_dir()
@task
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)
build.dockerhub_login()
build.dockerhub_publish()

View file

@ -0,0 +1,4 @@
FROM debian:stable-slim
ADD resources /tmp
RUN /tmp/install.sh

View file

@ -0,0 +1,17 @@
#!/bin/bash
set -exo pipefail
function main() {
{
upgradeSystem
apt-get -qqy install curl git kotlin gradle iputils-ping ssh python3 python3-pip
pip3 install --break-system-packages pybuilder 'ddadevops>=4.7.0' deprecation dda-python-terraform boto3 pyyaml inflection
cleanupDocker
} > /dev/null
}
source /tmp/install_functions_debian.sh
DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes main

View file

@ -1,14 +1,19 @@
from os import environ from os import environ
from datetime import datetime
from pybuilder.core import task, init from pybuilder.core import task, init
from ddadevops import * from ddadevops import *
name = "devops-build" name = "ddadevops"
MODULE = "image" MODULE = "python"
PROJECT_ROOT_PATH = "../.." PROJECT_ROOT_PATH = "../.."
version = "4.13.2-dev"
@init @init
def initialize(project): def initialize(project):
image_tag = version
if "dev" in image_tag:
image_tag += datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
input = { input = {
"name": name, "name": name,
@ -17,10 +22,12 @@ def initialize(project):
"project_root_path": PROJECT_ROOT_PATH, "project_root_path": PROJECT_ROOT_PATH,
"build_types": ["IMAGE"], "build_types": ["IMAGE"],
"mixin_types": [], "mixin_types": [],
"image_naming": "NAME_AND_MODULE",
"image_tag": f"{image_tag}",
} }
project.build_depends_on("ddadevops>=4.0.0") project.build_depends_on("ddadevops>=4.0.0")
build = DevopsImageBuild(project, input) build = DevopsImageBuild(project, input)
build.initialize_build_dir() build.initialize_build_dir()

View file

@ -1,4 +1,4 @@
FROM node:lts-buster-slim FROM python:3.10-alpine
ADD resources /tmp ADD resources /tmp
RUN /tmp/install.sh RUN /tmp/install.sh

View file

@ -0,0 +1,20 @@
#!/bin/sh
set -exo pipefail
function main() {
{
upgradeSystem
apk add --no-cache build-base rust python3 python3-dev py3-pip py3-setuptools py3-wheel libffi-dev openssl-dev cargo bash git curl
python3 -m pip install -U pip
pip3 install pybuilder ddadevops deprecation dda-python-terraform boto3 pyyaml inflection \
coverage flake8 flake8-polyfill mypy mypy-extensions pycodestyle pyflakes pylint pytest pytest-cov pytest-datafiles types-setuptools types-PyYAML
cleanupDocker
} > /dev/null
}
source /tmp/install_functions_alpine.sh
main

View file

@ -4,7 +4,9 @@ from ..infrastructure import FileApi, ResourceApi, ImageApi
class ImageBuildService: class ImageBuildService:
def __init__(self, file_api: FileApi, resource_api: ResourceApi, image_api: ImageApi): def __init__(
self, file_api: FileApi, resource_api: ResourceApi, image_api: ImageApi
):
self.file_api = file_api self.file_api = file_api
self.resource_api = resource_api self.resource_api = resource_api
self.image_api = image_api self.image_api = image_api
@ -18,7 +20,9 @@ class ImageBuildService:
) )
def __copy_build_resource_file_from_package__(self, resource_name, devops: Devops): def __copy_build_resource_file_from_package__(self, resource_name, devops: Devops):
data = self.resource_api.read_resource(f"src/main/resources/docker/{resource_name}") data = self.resource_api.read_resource(
f"src/main/resources/docker/{resource_name}"
)
self.file_api.write_data_to_file( self.file_api.write_data_to_file(
Path(f"{devops.build_path()}/{resource_name}"), data Path(f"{devops.build_path()}/{resource_name}"), data
) )
@ -27,12 +31,16 @@ class ImageBuildService:
self.__copy_build_resource_file_from_package__( self.__copy_build_resource_file_from_package__(
"image/resources/install_functions.sh", devops "image/resources/install_functions.sh", devops
) )
self.__copy_build_resource_file_from_package__(
"image/resources/install_functions_debian.sh", devops
)
self.__copy_build_resource_file_from_package__(
"image/resources/install_functions_alpine.sh", devops
)
def __copy_build_resources_from_dir__(self, devops: Devops): def __copy_build_resources_from_dir__(self, devops: Devops):
image = devops.specialized_builds[BuildType.IMAGE] image = devops.specialized_builds[BuildType.IMAGE]
self.file_api.cp_force( self.file_api.cp_force(image.build_commons_path(), devops.build_path())
image.build_commons_path(), devops.build_path()
)
def initialize_build_dir(self, devops: Devops): def initialize_build_dir(self, devops: Devops):
image = devops.specialized_builds[BuildType.IMAGE] image = devops.specialized_builds[BuildType.IMAGE]
@ -43,13 +51,18 @@ class ImageBuildService:
else: else:
self.__copy_build_resources_from_dir__(devops) self.__copy_build_resources_from_dir__(devops)
self.file_api.cp_recursive("image", build_path) self.file_api.cp_recursive("image", build_path)
self.file_api.cp_recursive("test", build_path) try:
self.file_api.cp_recursive("test", build_path)
except:
print("Folder 'test' not found")
def image(self, devops: Devops): def image(self, devops: Devops):
self.image_api.image(devops.name, devops.build_path()) image = devops.specialized_builds[BuildType.IMAGE]
self.image_api.image(image.image_name(), devops.build_path())
def drun(self, devops: Devops): def drun(self, devops: Devops):
self.image_api.drun(devops.name) image = devops.specialized_builds[BuildType.IMAGE]
self.image_api.drun(image.image_name())
def dockerhub_login(self, devops: Devops): def dockerhub_login(self, devops: Devops):
image = devops.specialized_builds[BuildType.IMAGE] image = devops.specialized_builds[BuildType.IMAGE]
@ -59,9 +72,14 @@ class ImageBuildService:
def dockerhub_publish(self, devops: Devops): def dockerhub_publish(self, devops: Devops):
image = devops.specialized_builds[BuildType.IMAGE] image = devops.specialized_builds[BuildType.IMAGE]
if image.image_tag is not None:
self.image_api.dockerhub_publish(
image.image_name(), image.image_dockerhub_user, image.image_tag
)
self.image_api.dockerhub_publish( self.image_api.dockerhub_publish(
devops.name, image.image_dockerhub_user, image.image_tag image.image_name(), image.image_dockerhub_user, 'latest'
) )
def test(self, devops: Devops): def test(self, devops: Devops):
self.image_api.test(devops.name, devops.build_path()) image = devops.specialized_builds[BuildType.IMAGE]
self.image_api.test(image.image_name(), devops.build_path())

View file

@ -1,18 +1,26 @@
import json
from typing import List from typing import List
from pathlib import Path from pathlib import Path
from ..infrastructure import GitApi, BuildFileRepository from ..infrastructure import GitApi, ArtifactDeploymentApi, BuildFileRepository
from ..domain import Version, Release, ReleaseType from ..domain import Version, Release, ReleaseType, Artifact
class ReleaseService: 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.git_api = git_api
self.artifact_deployment_api = artifact_deployment_api
self.build_file_repository = build_file_repository self.build_file_repository = build_file_repository
@classmethod @classmethod
def prod(cls, base_dir: str): def prod(cls, base_dir: str):
return cls( return cls(
GitApi(), GitApi(),
ArtifactDeploymentApi(),
BuildFileRepository(base_dir), BuildFileRepository(base_dir),
) )
@ -45,7 +53,8 @@ class ReleaseService:
bump_version = release_version.create_bump() bump_version = release_version.create_bump()
release_message = f"release: {release_version.to_string()}" release_message = f"release: {release_version.to_string()}"
bump_message = f"bump version to: {bump_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__( self.__set_version_and_commit__(
bump_version, bump_version,
release.build_files(), release.build_files(),
@ -53,6 +62,41 @@ class ReleaseService:
) )
self.git_api.push_follow_tags() 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__( def __set_version_and_commit__(
self, version: Version, build_file_ids: List[str], message: str self, version: Version, build_file_ids: List[str], message: str
): ):

View file

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

@ -11,6 +11,7 @@ class BuildFileType(Enum):
JS = ".json" JS = ".json"
JAVA_GRADLE = ".gradle" JAVA_GRADLE = ".gradle"
JAVA_CLOJURE = ".clj" JAVA_CLOJURE = ".clj"
JAVA_CLOJURE_EDN = ".edn"
PYTHON = ".py" PYTHON = ".py"
@ -41,42 +42,42 @@ class BuildFile(Validateable):
result = BuildFileType.JAVA_CLOJURE result = BuildFileType.JAVA_CLOJURE
case ".py": case ".py":
result = BuildFileType.PYTHON result = BuildFileType.PYTHON
case ".edn":
result = BuildFileType.JAVA_CLOJURE_EDN
case _: case _:
result = None result = None
return result return result
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)?)\""
case BuildFileType.PYTHON:
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)?)\""
case BuildFileType.JAVA_CLOJURE_EDN:
return r"(?P<pre_version>\:version\s+)\"(?P<version>\d*\.\d*\.\d*(-SNAPSHOT)?)\""
case _:
return ""
def get_version(self) -> Version: def get_version(self) -> Version:
try: try:
match self.build_file_type(): build_file_type = self.build_file_type()
match build_file_type:
case BuildFileType.JS: case BuildFileType.JS:
version_str = json.loads(self.content)["version"] version_str = json.loads(self.content)["version"]
case BuildFileType.JAVA_GRADLE: case (
# TODO: '\nversion = ' will not parse all ?! BuildFileType.JAVA_GRADLE
version_line = re.search("\nversion = .*", self.content) | BuildFileType.PYTHON
version_line_group = version_line.group() | BuildFileType.JAVA_CLOJURE
version_string = re.search( | BuildFileType.JAVA_CLOJURE_EDN
"[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?", version_line_group ):
) version_str = re.search(
version_str = version_string.group() self.__get_file_type_regex_str(build_file_type), self.content
case BuildFileType.PYTHON: ).group("version")
# TODO: '\nversion = ' will not parse all ?!
version_line = re.search("\nversion = .*\n", self.content)
version_line_group = version_line.group()
version_string = re.search(
"[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?(-dev)?[0-9]*",
version_line_group,
)
version_str = version_string.group()
case BuildFileType.JAVA_CLOJURE:
# TODO: unsure about the trailing '\n' !
version_line = re.search("\\(defproject .*\n", self.content)
version_line_group = version_line.group()
version_string = re.search(
"[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?", version_line_group
)
version_str = version_string.group()
except: except:
raise Exception(f"Version not found in file {self.file_path}") raise RuntimeError(f"Version not found in file {self.file_path}")
result = Version.from_str(version_str, self.get_default_suffix()) result = Version.from_str(version_str, self.get_default_suffix())
result.throw_if_invalid() result.throw_if_invalid()
@ -84,38 +85,32 @@ class BuildFile(Validateable):
return result return result
def set_version(self, new_version: Version): def set_version(self, new_version: Version):
# TODO: How can we create regex-pattern constants to use them at both places?
if new_version.is_snapshot():
new_version.snapshot_suffix = self.get_default_suffix()
try: try:
match self.build_file_type(): build_file_type = self.build_file_type()
match build_file_type:
case BuildFileType.JS: case BuildFileType.JS:
json_data = json.loads(self.content) json_data = json.loads(self.content)
json_data["version"] = new_version.to_string() json_data["version"] = new_version.to_string()
self.content = json.dumps(json_data, indent=4) self.content = json.dumps(json_data, indent=4)
case BuildFileType.JAVA_GRADLE: case (
BuildFileType.JAVA_GRADLE
| BuildFileType.PYTHON
| BuildFileType.JAVA_CLOJURE
| BuildFileType.JAVA_CLOJURE_EDN
):
substitute = re.sub( substitute = re.sub(
'\nversion = "[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?"', self.__get_file_type_regex_str(build_file_type),
f'\nversion = "{new_version.to_string()}"', rf'\g<pre_version>"{new_version.to_string()}"',
self.content,
)
self.content = substitute
case BuildFileType.PYTHON:
substitute = re.sub(
'\nversion = "[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?(-dev)?[0-9]*"',
f'\nversion = "{new_version.to_string()}"',
self.content,
)
self.content = substitute
case BuildFileType.JAVA_CLOJURE:
# TODO: we should stick here on defproject instead of first line!
substitute = re.sub(
'"[0-9]*\\.[0-9]*\\.[0-9]*(-SNAPSHOT)?"',
f'"{new_version.to_string()}"',
self.content, self.content,
1, 1,
) )
self.content = substitute self.content = substitute
except: except:
raise Exception(f"Version not found in file {self.file_path}") raise RuntimeError(f"Version not found in file {self.file_path}")
def get_default_suffix(self) -> str: def get_default_suffix(self) -> str:
result = "SNAPSHOT" result = "SNAPSHOT"

View file

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

View file

@ -1,3 +1,4 @@
from enum import Enum
from typing import List, Dict from typing import List, Dict
from .common import ( from .common import (
filter_none, filter_none,
@ -5,15 +6,23 @@ from .common import (
) )
class NamingType(Enum):
NAME_ONLY = 1
NAME_AND_MODULE = 2
class Image(Validateable): class Image(Validateable):
def __init__( def __init__(
self, self,
inp: dict, inp: dict,
): ):
self.module = inp.get("module")
self.name = inp.get("name")
self.image_dockerhub_user = inp.get("image_dockerhub_user") self.image_dockerhub_user = inp.get("image_dockerhub_user")
self.image_dockerhub_password = inp.get("image_dockerhub_password") self.image_dockerhub_password = inp.get("image_dockerhub_password")
self.image_tag = inp.get("image_tag") self.image_tag = inp.get("image_tag")
self.image_build_commons_path = inp.get("image_build_commons_path") self.image_build_commons_path = inp.get("image_build_commons_path")
self.image_naming = NamingType[inp.get("image_naming", "NAME_ONLY")]
self.image_use_package_common_files = inp.get( self.image_use_package_common_files = inp.get(
"image_use_package_common_files", True "image_use_package_common_files", True
) )
@ -23,8 +32,10 @@ class Image(Validateable):
def validate(self) -> List[str]: def validate(self) -> List[str]:
result = [] result = []
result += self.__validate_is_not_empty__("name")
result += self.__validate_is_not_empty__("image_dockerhub_user") result += self.__validate_is_not_empty__("image_dockerhub_user")
result += self.__validate_is_not_empty__("image_dockerhub_password") result += self.__validate_is_not_empty__("image_dockerhub_password")
result += self.__validate_is_not_empty__("image_naming")
if not self.image_use_package_common_files: if not self.image_use_package_common_files:
result += self.__validate_is_not_empty__("image_build_commons_path") result += self.__validate_is_not_empty__("image_build_commons_path")
result += self.__validate_is_not_empty__("image_build_commons_dir_name") result += self.__validate_is_not_empty__("image_build_commons_dir_name")
@ -37,6 +48,16 @@ class Image(Validateable):
] ]
return "/".join(filter_none(commons_path)) + "/" return "/".join(filter_none(commons_path)) + "/"
def image_name(self) -> str:
result: List[str] = [self.name] # type: ignore
if (
self.image_naming == NamingType.NAME_AND_MODULE
and self.module
and self.module != ""
):
result.append(self.module)
return "-".join(result)
@classmethod @classmethod
def get_mapping_default(cls) -> List[Dict[str, str]]: def get_mapping_default(cls) -> List[Dict[str, str]]:
return [ return [

View file

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

View file

@ -36,6 +36,8 @@ class Aws(Validateable, CredentialMappingDefault):
result = {} result = {}
if self.aws_as_backend: if self.aws_as_backend:
result = { result = {
"access_key": self.aws_access_key,
"secret_key": self.aws_secret_key,
"bucket": self.aws_bucket, "bucket": self.aws_bucket,
"key": self.__bucket_key__(), "key": self.__bucket_key__(),
"region": self.aws_region, "region": self.aws_region,

View file

@ -20,6 +20,14 @@ CONFIG_CERTMANAGER = """certmanager:
""" """
CONFIG_ECHO = """echo: $echo 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): class K3s(Validateable):
@ -28,8 +36,11 @@ class K3s(Validateable):
self.k3s_letsencrypt_email = inp.get("k3s_letsencrypt_email") self.k3s_letsencrypt_email = inp.get("k3s_letsencrypt_email")
self.k3s_letsencrypt_endpoint = inp.get("k3s_letsencrypt_endpoint", "staging") 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_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_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 self.provision_dns: Optional[DnsRecord] = None
def validate(self) -> List[str]: 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_email")
result += self.__validate_is_not_empty__("k3s_letsencrypt_endpoint") result += self.__validate_is_not_empty__("k3s_letsencrypt_endpoint")
result += self.__validate_is_not_empty__("k3s_app_filename_to_provision") 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: if self.provision_dns:
result += self.provision_dns.validate() result += self.provision_dns.validate()
return result return result
@ -61,6 +75,9 @@ class K3s(Validateable):
substitutes["letsencrypt_endpoint"] = self.k3s_letsencrypt_endpoint substitutes["letsencrypt_endpoint"] = self.k3s_letsencrypt_endpoint
if self.k3s_enable_echo is not None: if self.k3s_enable_echo is not None:
substitutes["echo"] = self.k3s_enable_echo 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) return self.__config_template__().substitute(substitutes)
def command(self, devops: Devops): def command(self, devops: Devops):
@ -69,7 +86,7 @@ class K3s(Validateable):
cmd = [ cmd = [
"provs-server.jar", "provs-server.jar",
"k3s", "k3s",
f"{self.k3s_provision_user}@{self.provision_dns.fqdn}", f"{self.k3s_provision_user}@{self.provision_dns.ip()}",
"-c", "-c",
f"{devops.build_path()}/out_k3sServerConfig.yaml", f"{devops.build_path()}/out_k3sServerConfig.yaml",
"-a", "-a",
@ -89,4 +106,6 @@ class K3s(Validateable):
template_text += CONFIG_IPV4 template_text += CONFIG_IPV4
if self.provision_dns.ipv6 is not None: if self.provision_dns.ipv6 is not None:
template_text += CONFIG_IPV6 template_text += CONFIG_IPV6
if self.k3s_enable_hetzner_csi:
template_text += CONFIG_HETZNER_CSI
return Template(template_text) return Template(template_text)

View file

@ -1,4 +1,4 @@
from typing import Optional, List from typing import Optional, List, Dict
from pathlib import Path from pathlib import Path
from .common import ( from .common import (
Validateable, Validateable,
@ -7,6 +7,9 @@ from .common import (
from .version import ( from .version import (
Version, Version,
) )
from .artifact import (
Artifact,
)
class Release(Validateable): class Release(Validateable):
@ -21,6 +24,14 @@ class Release(Validateable):
"release_secondary_build_files", [] "release_secondary_build_files", []
) )
self.version = version 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")
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): def update_release_type(self, release_type: ReleaseType):
self.release_type = release_type self.release_type = release_type
@ -53,10 +64,44 @@ class Release(Validateable):
and self.release_type != ReleaseType.NONE and self.release_type != ReleaseType.NONE
and self.release_main_branch != self.release_current_branch 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 return result
def build_files(self) -> List[str]: def build_files(self) -> List[str]:
result = [self.release_primary_build_file] result = [self.release_primary_build_file]
result += self.release_secondary_build_files result += self.release_secondary_build_files
return result 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.snapshot_suffix = snapshot_suffix
self.default_snapshot_suffix = default_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): def is_snapshot(self):
return self.snapshot_suffix is not None return self.snapshot_suffix is not None
@ -139,3 +133,9 @@ class Version(Validateable):
snapshot_suffix=None, snapshot_suffix=None,
version_str=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, CredentialsApi,
GitApi, GitApi,
TerraformApi, TerraformApi,
ArtifactDeploymentApi,
) )
from .repository import DevopsRepository, BuildFileRepository from .repository import DevopsRepository, BuildFileRepository

View file

@ -53,37 +53,27 @@ class ImageApi:
self.execution_api = ExecutionApi() self.execution_api = ExecutionApi()
def image(self, name: str, path: Path): def image(self, name: str, path: Path):
self.execution_api.run_handled( self.execution_api.execute_live(
f"docker build -t {name} --file {path}/image/Dockerfile {path}/image" f"docker build -t {name} --file {path}/image/Dockerfile {path}/image"
) )
def drun(self, name: str): def drun(self, name: str):
self.execution_api.run_handled( self.execution_api.execute_live(
f'docker run -it --entrypoint="" {name} /bin/bash' f'docker run -it {name} /bin/bash'
) )
def dockerhub_login(self, username: str, password: str): def dockerhub_login(self, username: str, password: str):
self.execution_api.run_handled( self.execution_api.execute_secure(
f"docker login --username {username} --password {password}" f"docker login --username {username} --password {password}",
"docker login --username ***** --password *****",
) )
def dockerhub_publish(self, name: str, username: str, tag=None): def dockerhub_publish(self, name: str, username: str, tag: str):
if tag is not None: self.execution_api.execute_live(f"docker tag {name} {username}/{name}:{tag}")
self.execution_api.run_handled( self.execution_api.execute_live(f"docker push {username}/{name}:{tag}")
f"docker tag {name} {username}/{name}:{tag}"
)
self.execution_api.run_handled(
f"docker push {username}/{name}:{tag}"
)
self.execution_api.run_handled(
f"docker tag {name} {username}/{name}:latest"
)
self.execution_api.run_handled(
f"docker push {username}/{name}:latest"
)
def test(self, name: str, path: Path): def test(self, name: str, path: Path):
self.execution_api.run_handled( self.execution_api.execute_live(
f"docker build -t {name} -test --file {path}/test/Dockerfile {path}/test" f"docker build -t {name} -test --file {path}/test/Dockerfile {path}/test"
) )
@ -94,40 +84,58 @@ class ExecutionApi:
if dry_run: if dry_run:
print(command) print(command)
else: else:
# output = check_output(command, encoding="UTF-8", shell=shell) try:
output = run( output = run(
command, encoding="UTF-8", shell=shell, stdout=PIPE, check=check command,
).stdout shell=shell,
output = output.rstrip() check=check,
stdout=PIPE,
stderr=PIPE,
text=True,
).stdout
output = output.rstrip()
except CalledProcessError as exc:
print(
f"Command failed with code: {exc.returncode} and message: {exc.stderr}"
)
raise exc
return output return output
def execute_live(self, command, dry_run=False, shell=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
except CalledProcessError as exc:
sanitized_exc = exc
sanitized_exc.cmd = sanitized_command
raise sanitized_exc
def execute_live(self, command: str, dry_run=False, shell=True):
if dry_run: if dry_run:
print(command) print(command)
else: else:
process = Popen(command, stdout=PIPE, shell=shell) process = Popen(command, shell=shell)
for line in iter(process.stdout.readline, b""): outs, errs = process.communicate()
print(line.decode("utf-8"), end="") while outs is not None:
process.stdout.close() stdout.buffer.write(outs)
process.wait() if process.returncode != 0:
raise RuntimeError(f"Execute live '{command}' failed with code {process.returncode}\nerrs: {errs}")
def run_handled(self, command: str, shell=True, check=True):
try:
run(
command,
shell=shell,
check=check,
capture_output=True,
text=True)
except CalledProcessError as exc:
print("Command failed with code: ", exc.returncode, " and message:", exc.stderr)
class EnvironmentApi: class EnvironmentApi:
def get(self, key): def get(self, key):
return environ.get(key) return environ.get(key)
def is_defined(self, key):
return key in environ
class CredentialsApi: class CredentialsApi:
def __init__(self): def __init__(self):
@ -206,3 +214,53 @@ class GitApi:
class TerraformApi: class TerraformApi:
pass 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) devops = self.devops_repo.get_devops(self.project)
release = devops.mixins[MixinType.RELEASE] release = devops.mixins[MixinType.RELEASE]
self.release_service.tag_bump_and_push_release(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,8 +1,11 @@
#
#deprecated, we recommend to use install_functions_debian.sh instead. We will going to remove install_functions.sh in a future release.
#
function upgradeSystem() { function upgradeSystem() {
export DEBIAN_FRONTEND=noninteractive {
apt-get update > /dev/null apt-get update
apt-get -y install apt-utils > /dev/null apt-get -qqy upgrade
apt-get -qqy dist-upgrade > /dev/null } > /dev/null
} }
function cleanupDocker() { function cleanupDocker() {

View file

@ -0,0 +1,21 @@
function upgradeSystem() {
apk -U upgrade
}
function cleanupDocker() {
rm -f /root/.ssh/authorized_keys
rm -f /root/.ssh/authorized_keys2
apk cache clean
rm -rf /tmp/*
find /var/cache -type f -exec rm -rf {} \;
find /var/log/ -name '*.log' -exec rm -f {} \;
}
function cleanupAmi() {
rm -f /home/ubuntu/.ssh/authorized_keys
rm -f /home/ubuntu/.ssh/authorized_keys2
cleanupDocker
}

View file

@ -0,0 +1,25 @@
function upgradeSystem() {
apt-get update
apt-get -qqy upgrade
}
function cleanupDocker() {
rm -f /root/.ssh/authorized_keys
rm -f /root/.ssh/authorized_keys2
apt-get clean
apt-get -qqy autoremove --purge
apt-get -qqy autoclean
rm -rf /var/lib/apt/lists/
rm -rf /tmp/*
find /var/cache -type f -exec rm -rf {} \;
find /var/log/ -name '*.log' -exec rm -f {} \;
}
function cleanupAmi() {
rm -f /home/ubuntu/.ssh/authorized_keys
rm -f /home/ubuntu/.ssh/authorized_keys2
cleanupDocker
}

View file

@ -1,18 +1,22 @@
import pytest import pytest
from pathlib import Path from pathlib import Path
from src.main.python.ddadevops.domain import ( from src.main.python.ddadevops.domain import (
ReleaseType, ReleaseType,
MixinType, MixinType,
) )
from src.test.python.domain.helper import ( from src.test.python.domain.helper import (
BuildFileRepositoryMock, BuildFileRepositoryMock,
GitApiMock, GitApiMock,
ArtifactDeploymentApiMock,
build_devops, build_devops,
) )
from src.main.python.ddadevops.application import ReleaseService from src.main.python.ddadevops.application import ReleaseService
def test_sould_update_release_type():
sut = ReleaseService(GitApiMock(), BuildFileRepositoryMock("build.py")) def test_should_update_release_type():
sut = ReleaseService(
GitApiMock(), ArtifactDeploymentApiMock(), BuildFileRepositoryMock("build.py")
)
devops = build_devops({}) devops = build_devops({})
release = devops.mixins[MixinType.RELEASE] release = devops.mixins[MixinType.RELEASE]
sut.update_release_type(release, "MAJOR") sut.update_release_type(release, "MAJOR")
@ -20,3 +24,40 @@ def test_sould_update_release_type():
with pytest.raises(Exception): with pytest.raises(Exception):
sut.update_release_type(release, "NOT_EXISTING") sut.update_release_type(release, "NOT_EXISTING")
def test_should_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_should_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_current_branch": "my_feature",
"release_primary_build_file": "./package.json", "release_primary_build_file": "./package.json",
"release_secondary_build_file": [], "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": [ "credentials_mappings": [
{ {
"gopass_path": "a/path", "gopass_path": "a/path",
@ -99,6 +104,9 @@ class EnvironmentApiMock:
def get(self, key): def get(self, key):
return self.mappings.get(key, None) return self.mappings.get(key, None)
def is_defined(self, key):
return key in self.mappings
class CredentialsApiMock: class CredentialsApiMock:
def __init__(self, mappings): def __init__(self, mappings):
@ -148,5 +156,33 @@ class GitApiMock:
def push(self): def push(self):
pass pass
def push_follow_tags(self):
pass
def checkout(self, branch: str): def checkout(self, branch: str):
pass 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_should_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

@ -7,7 +7,7 @@ from src.main.python.ddadevops.domain import (
) )
def test_sould_validate_build_file(): def test_should_validate_build_file():
sut = BuildFile(Path("./project.clj"), "content") sut = BuildFile(Path("./project.clj"), "content")
assert sut.is_valid() assert sut.is_valid()
@ -18,7 +18,7 @@ def test_sould_validate_build_file():
assert not sut.is_valid() assert not sut.is_valid()
def test_sould_calculate_build_type(): def test_should_calculate_build_type():
sut = BuildFile(Path("./project.clj"), "content") sut = BuildFile(Path("./project.clj"), "content")
assert sut.build_file_type() == BuildFileType.JAVA_CLOJURE assert sut.build_file_type() == BuildFileType.JAVA_CLOJURE
@ -29,7 +29,7 @@ def test_sould_calculate_build_type():
assert sut.build_file_type() == BuildFileType.JS assert sut.build_file_type() == BuildFileType.JS
def test_sould_parse_and_set_js(): def test_should_parse_and_set_js():
sut = BuildFile( sut = BuildFile(
Path("./package.json"), Path("./package.json"),
""" """
@ -77,7 +77,7 @@ def test_sould_parse_and_set_js():
) )
def test_sould_parse_and_set_version_for_gradle(): def test_should_parse_and_set_version_for_gradle():
sut = BuildFile( sut = BuildFile(
Path("./build.gradle"), Path("./build.gradle"),
""" """
@ -97,7 +97,7 @@ version = "1.1.5-SNAPSHOT"
assert '\nversion = "2.0.0"\n' == sut.content assert '\nversion = "2.0.0"\n' == sut.content
def test_sould_parse_and_set_version_for_py(): def test_should_parse_and_set_version_for_py():
sut = BuildFile( sut = BuildFile(
Path("./build.py"), Path("./build.py"),
""" """
@ -143,7 +143,7 @@ version = "1.1.5-SNAPSHOT"
assert '\nversion = "2.0.0"\n' == sut.content assert '\nversion = "2.0.0"\n' == sut.content
def test_sould_parse_and_set_version_for_clj(): def test_should_parse_and_set_version_for_clj():
sut = BuildFile( sut = BuildFile(
Path("./project.clj"), Path("./project.clj"),
""" """
@ -182,3 +182,71 @@ def test_sould_parse_and_set_version_for_clj():
'\n(defproject org.domaindrivenarchitecture/c4k-jira "2.0.0"\n:dependencies [[org.clojure/clojure "1.11.0"]]\n)\n ' '\n(defproject org.domaindrivenarchitecture/c4k-jira "2.0.0"\n:dependencies [[org.clojure/clojure "1.11.0"]]\n)\n '
== sut.content == 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"),
"""
(defproject org.domaindrivenarchitecture/c4k-jira "1.1.5-Snapshot"
:description "jira c4k-installation package"
:url "https://domaindrivenarchitecture.org"
)
""",
)
with pytest.raises(RuntimeError):
sut.get_version()
def test_should_ignore_first_version_for_py():
sut = BuildFile(
Path("./build.py"),
"""
from pybuilder.core import init, use_plugin, Author
use_plugin("python.core")
name = "ddadevops"
project_version = "0.0.2-dev1"
version = "1.1.5-dev12"
summary = "tools to support builds combining gopass, terraform, dda-pallet, aws & hetzner-cloud"
""",
)
assert sut.get_version() == Version.from_str("1.1.5-dev12", "dev")
def test_should_ignore_first_version_for_gradle():
sut = BuildFile(
Path("./build.gradle"),
"""
kotlin_version = "3.3.3"
version = "1.1.5-SNAPSHOT"
""",
)
assert sut.get_version() == Version.from_str("1.1.5-SNAPSHOT", "SNAPSHOT")

View file

@ -4,10 +4,11 @@ from src.main.python.ddadevops.domain import (
Version, Version,
BuildType, BuildType,
MixinType, MixinType,
Artifact,
) )
def test_devops_factory(): def test_devops_creation():
with pytest.raises(Exception): with pytest.raises(Exception):
DevopsFactory().build_devops({"build_types": ["NOTEXISTING"]}) DevopsFactory().build_devops({"build_types": ["NOTEXISTING"]})
@ -50,6 +51,7 @@ def test_devops_factory():
assert sut is not None assert sut is not None
assert sut.specialized_builds[BuildType.C4K] is not None assert sut.specialized_builds[BuildType.C4K] is not None
def test_release_devops_creation():
sut = DevopsFactory().build_devops( sut = DevopsFactory().build_devops(
{ {
"stage": "test", "stage": "test",
@ -66,3 +68,32 @@ def test_devops_factory():
) )
assert sut is not None assert sut is not None
assert sut.mixins[MixinType.RELEASE] 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()
assert {'tag': 'inp'} == sut.merge(inp = {'tag': 'inp'}, context = {'tag': 'context'}, authorization={})

View file

@ -1,6 +1,4 @@
from pybuilder.core import Project from pybuilder.core import Project
import logging
from subprocess import Popen, PIPE, run
from pathlib import Path from pathlib import Path
from src.main.python.ddadevops.domain import ( from src.main.python.ddadevops.domain import (
BuildType, BuildType,
@ -14,3 +12,16 @@ def test_devops_build_commons_path():
assert image is not None assert image is not None
assert image.is_valid() assert image.is_valid()
assert "docker/" == image.build_commons_path() assert "docker/" == image.build_commons_path()
def test_should_calculate_image_name():
sut = build_devops({})
image = sut.specialized_builds[BuildType.IMAGE]
assert "name" == image.image_name()
sut = build_devops({'image_naming': "NAME_ONLY"})
image = sut.specialized_builds[BuildType.IMAGE]
assert "name" == image.image_name()
sut = build_devops({'image_naming': "NAME_AND_MODULE"})
image = sut.specialized_builds[BuildType.IMAGE]
assert "name-module" == image.image_name()

View file

@ -41,6 +41,8 @@ def test_should_calculate_backend_config():
{ {
"module": "dns_aws", "module": "dns_aws",
"stage": "prod", "stage": "prod",
"aws_access_key": "aws_access_key",
"aws_secret_key": "aws_secret_key",
"aws_bucket": "meissa-configuration", "aws_bucket": "meissa-configuration",
"aws_bucket_kms_key_id": "arn:aws:kms:eu-central-1:907507348333:alias/meissa-configuration", "aws_bucket_kms_key_id": "arn:aws:kms:eu-central-1:907507348333:alias/meissa-configuration",
"aws_region": "eu-central-1", "aws_region": "eu-central-1",
@ -48,6 +50,8 @@ def test_should_calculate_backend_config():
) )
) )
assert { assert {
"access_key": "aws_access_key",
"secret_key": "aws_secret_key",
"bucket": "meissa-configuration", "bucket": "meissa-configuration",
"key": "prod/dns_aws", "key": "prod/dns_aws",
"kms_key_id": "arn:aws:kms:eu-central-1:907507348333:alias/meissa-configuration", "kms_key_id": "arn:aws:kms:eu-central-1:907507348333:alias/meissa-configuration",

View file

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

View file

@ -1,3 +1,4 @@
import pytest
from pybuilder.core import Project from pybuilder.core import Project
from pathlib import Path from pathlib import Path
from src.main.python.ddadevops.domain import ( from src.main.python.ddadevops.domain import (
@ -14,7 +15,7 @@ from src.main.python.ddadevops.domain import (
from .helper import build_devops, devops_config from .helper import build_devops, devops_config
def test_sould_validate_release(): def test_should_validate_release():
sut = Release( sut = Release(
devops_config( devops_config(
{ {
@ -48,7 +49,7 @@ def test_sould_validate_release():
assert not sut.is_valid() assert not sut.is_valid()
def test_sould_calculate_build_files(): def test_should_calculate_build_files():
sut = Release( sut = Release(
devops_config( devops_config(
{ {
@ -61,3 +62,74 @@ def test_sould_calculate_build_files():
Version.from_str("1.3.1-SNAPSHOT", "SNAPSHOT"), Version.from_str("1.3.1-SNAPSHOT", "SNAPSHOT"),
) )
assert ["project.clj", "package.json"] == sut.build_files() 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

@ -5,7 +5,9 @@ from pybuilder.core import Project
from src.main.python.ddadevops.release_mixin import ReleaseMixin from src.main.python.ddadevops.release_mixin import ReleaseMixin
from src.main.python.ddadevops.domain import Devops, Release from src.main.python.ddadevops.domain import Devops, Release
from .domain.helper import devops_config from src.main.python.ddadevops.application import ReleaseService
from src.main.python.ddadevops.infrastructure import BuildFileRepository
from .domain.helper import devops_config, GitApiMock, ArtifactDeploymentApiMock
from .resource_helper import copy_resource from .resource_helper import copy_resource
@ -14,6 +16,8 @@ def test_release_mixin(tmp_path):
copy_resource(Path("package.json"), tmp_path) copy_resource(Path("package.json"), tmp_path)
project = Project(str_tmp_path, name="name") project = Project(str_tmp_path, name="name")
os.environ["RELEASE_ARTIFACT_TOKEN"] = "ratoken"
sut = ReleaseMixin( sut = ReleaseMixin(
project, project,
devops_config( devops_config(
@ -28,3 +32,37 @@ def test_release_mixin(tmp_path):
sut.initialize_build_dir() sut.initialize_build_dir()
assert sut.build_path() == f"{str_tmp_path}/target/name/release-test" assert sut.build_path() == f"{str_tmp_path}/target/name/release-test"
def test_release_mixin_different_version_suffixes(tmp_path):
str_tmp_path = str(tmp_path)
copy_resource(Path("config.py"), tmp_path)
copy_resource(Path("config.gradle"), tmp_path)
project = Project(str_tmp_path, name="name")
os.environ["RELEASE_ARTIFACT_TOKEN"] = "ratoken"
sut = ReleaseMixin(
project,
devops_config(
{
"project_root_path": str_tmp_path,
"mixin_types": ["RELEASE"],
"build_types": [],
"module": "release-test",
"release_current_branch": "main",
"release_main_branch": "main",
"release_primary_build_file": "config.py",
"release_secondary_build_files": ["config.gradle"],
}
),
)
sut.release_service = ReleaseService(GitApiMock(), ArtifactDeploymentApiMock(), BuildFileRepository(project.basedir))
sut.initialize_build_dir()
sut.update_release_type("PATCH")
sut.prepare_release()
sut.tag_bump_and_push_release()
assert sut.release_service.build_file_repository.get(Path("config.py")).get_version().to_string() == "3.1.5-dev"
assert sut.release_service.build_file_repository.get(Path("config.gradle")).get_version().to_string() == "3.1.5-SNAPSHOT"