Compare commits

...

131 commits
v6.0.2 ... main

Author SHA1 Message Date
bom
f24b7418e6 Add support for ipv4/6 predicates 2024-09-27 11:49:43 +02:00
6a6e51d694 Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/c4k-common 2024-08-06 12:59:00 +02:00
cf0403b9d7 [Skip-CI] Add website to contact info 2024-08-06 12:58:50 +02:00
b37661f155 updated federation 2024-08-05 08:57:16 +02:00
f4f2e97ebb update refactorings 2024-08-02 17:18:35 +02:00
fa6a5011d8 bump version to: 8.0.1-SNAPSHOT 2024-08-02 16:53:57 +02:00
d7e2c2536a release: 8.0.0 2024-08-02 16:53:57 +02:00
94bfec3da9 update refactoring 2024-08-02 16:31:51 +02:00
Clemens
5ae28c6af5 updated refactoring overview 2024-07-19 11:33:18 +02:00
Clemens
a94e8165de bump version to: 7.0.1-SNAPSHOT 2024-07-19 11:08:08 +02:00
Clemens
14fe8d8ee0 release: 7.0.0 2024-07-19 11:08:08 +02:00
gec
485dd40abd Merge pull request 'Split for generation of config and auth objects' (#4) from split-config-auth into main
Reviewed-on: #4
2024-07-19 09:06:51 +00:00
Clemens
fb16a34a75 renaming for generate-configmap 2024-07-19 10:51:47 +02:00
Clemens
1bcbca58c8 updated main function 2024-07-19 10:47:06 +02:00
Clemens
b62fa7b9ec consistent use of config defaults 2024-07-18 09:31:04 +02:00
Clemens
2154bed190 fix: consistent parameter apply for auth-objects 2024-07-18 09:03:38 +02:00
Clemens
57a9340109 breaking: apply config and auth to auth-objects 2024-07-18 08:50:31 +02:00
Clemens
74d5a63bea remove unused parameters 2024-07-17 14:08:33 +02:00
Clemens
7fc4380bb0 added config auth split for postgres generate 2024-07-17 11:31:19 +02:00
Clemens
52e3ba6ae1 fix tests 2024-07-17 11:02:37 +02:00
Clemens
ca8ab8d708 split monitoring into config and auth 2024-07-17 10:48:44 +02:00
0085111abe [Skip-CI] Update forgejo refactorings 2024-07-09 10:56:43 +02:00
19106937a0 [Skip-CI] Update forgejo refactorings 2024-07-09 10:56:02 +02:00
baaf26a0d6 Merge branch 'main' of ssh://repo.prod.meissa.de:2222/meissa/c4k-common 2024-07-09 09:48:52 +02:00
bom
30d7404ce9 Update refactoring table 2024-07-04 16:07:21 +02:00
bom
30be8cf4d3 Update refactoring table 2024-07-02 16:28:01 +02:00
bom
743418e6b1 bump version to: 6.4.2-SNAPSHOT 2024-07-02 14:39:59 +02:00
bom
8d124bbf81 release: 6.4.1 2024-07-02 14:39:59 +02:00
bom
b0faeab079 Fix logic for generating both config and auth
Should trigger if both or neither flag is set
2024-07-02 14:37:07 +02:00
bom
1f9b434154 bump version to: 6.4.1-SNAPSHOT 2024-07-02 14:05:08 +02:00
bom
4743d8f434 release: 6.4.0 2024-07-02 14:05:08 +02:00
bom
d8ed73cb00 Add config/auth only cmd line args 2024-06-28 13:02:59 +00:00
bom
4dfdfdcbb5 Improve help output 2024-06-28 13:02:59 +00:00
bom
4cbaac8d25 Add version command line arg
Requires "project.clj" to be added in resource-path
2024-06-28 13:02:59 +00:00
220aef9d5c update refactoring state 2024-06-07 18:07:53 +02:00
08fc72a406 bump version to: 6.3.2-SNAPSHOT 2024-05-31 17:12:48 +02:00
081f137414 release: 6.3.1 2024-05-31 17:12:48 +02:00
b35caa02f1 update refactorings 2024-05-31 17:06:56 +02:00
7c4dc29651 prepare switch to main 2024-05-31 17:03:04 +02:00
3756a5bc63 update refactorings 2024-05-31 17:01:46 +02:00
acb8b1e7bf replace deprecated fkts 2024-05-31 16:54:11 +02:00
169a51456b bump version to: 6.3.1-SNAPSHOT 2024-05-31 15:38:22 +02:00
a9fa3f21e7 release: 6.3.0 2024-05-31 15:38:22 +02:00
65e9c154a7 update versions 2024-05-31 15:37:21 +02:00
bom
d2bf694e4e Implement burst-rate for simple-ingress 2024-05-17 16:02:35 +02:00
bom
dd897c7364 Fix variable names 2024-05-17 16:00:11 +02:00
bom
0b647ed4d8 Remove old deprecated functions 2024-05-17 15:50:54 +02:00
bom
2008322368 Deprecate replace-all-matching-values-by-new-value
in favor or 'replace-all-matching' as it is shorter
2024-05-17 15:49:31 +02:00
bom
b4eb47d584 Add support for hetzner-csi for PVC 2024-05-17 14:45:15 +02:00
bom
b11f3edd61 bump version to: 6.2.4-SNAPSHOT 2024-03-29 10:26:18 +01:00
bom
f33b86125a release: 6.2.3 2024-03-29 10:26:18 +01:00
bom
b2ce1fa75e Use inline-resource macro again 2024-03-29 10:25:48 +01:00
bom
1c0537f8e9 Add jar-file inlining 2024-03-29 10:21:05 +01:00
bom
50888233e0 Upgrade deps 2024-03-29 10:20:28 +01:00
f2925a2991 update version in refactoring overview 2024-03-06 10:39:40 +01:00
efdb2051c5 bump version to: 6.2.3-SNAPSHOT 2024-02-29 09:53:49 +01:00
704b5cdcc8 release: 6.2.2 2024-02-29 09:53:49 +01:00
4fdf7d7de3 fix ratelimit wiring 2024-02-29 09:24:59 +01:00
468c76e902 bump version to: 6.2.2-SNAPSHOT 2024-02-28 10:07:51 +01:00
99cbc09340 release: 6.2.1 2024-02-28 10:07:51 +01:00
7469fc1cd4 fix macro issue 2024-02-28 09:59:28 +01:00
552dcbd1d8 bump version to: 6.2.1-SNAPSHOT 2024-02-27 14:48:31 +01:00
5d4f022145 release: 6.2.0 2024-02-27 14:48:31 +01:00
Clemens
c56efba544 adjust doc 2024-02-27 12:08:21 +01:00
Clemens
515813ce5c small reformulation 2024-02-27 11:59:00 +01:00
Clemens
4f071f14c8 revised postgres db doc 2024-02-27 11:32:17 +01:00
Clemens
26ca2acb6b revise namespaces and ingress in docs 2024-02-27 11:09:51 +01:00
Clemens
231002017f reformat titles and describe cmd lines in more detail 2024-02-27 09:58:31 +01:00
Clemens
8449548d56 small adjustment in readme 2024-02-26 15:50:44 +01:00
Clemens
3026900b98 minor revision of readme 2024-02-26 15:37:25 +01:00
bom
4bc93285b8 Update refactoring table
Add c4k-taiga changes
2024-02-23 16:48:30 +01:00
f334756d91 update refactoring state 2024-02-21 11:02:39 +01:00
jem
0594cfbec4 Merge pull request 'feature/namespace' (#2) from feature/namespace into master
Reviewed-on: #2
2024-02-20 17:24:37 +00:00
b21e377168 instrument new test 2024-02-20 18:21:55 +01:00
73a9965f96 format 2024-02-20 18:21:21 +01:00
c1f2cefd14 keep compatibility 2024-02-20 18:21:03 +01:00
a1a49e10d0 update doc 2024-02-20 18:15:17 +01:00
0cd4f22823 fix cljs 2024-02-20 18:10:05 +01:00
7e9c813e33 introduce namespace for postgres 2024-02-20 18:05:33 +01:00
674045eba3 add test & fixed some issues 2024-02-20 17:33:22 +01:00
cca93311e4 seperate postgres & postgres-internal 2024-02-20 17:08:39 +01:00
8626b91b16 refactor to dry 2024-02-20 16:14:15 +01:00
a14b7640da update deps 2024-02-20 16:13:36 +01:00
ab2473bdf4 introduce internal for monitoring 2024-02-20 16:04:28 +01:00
b4718500b1 fix cljs for postgresql 2024-02-20 16:03:56 +01:00
6794a24f0f add namespace to ingress 2024-02-20 15:23:59 +01:00
73e2ebd12f introduce namespace 2024-02-20 15:23:42 +01:00
866ef84f12 use load-resource macro 2024-02-20 12:08:49 +01:00
jem
fe43c0ec58 Merge pull request 'add ratelimit to ingress' (#1) from feature/ingress-ratelimit into master
Reviewed-on: #1
2024-02-20 10:51:49 +00:00
781ae8b0c5 remov unused imports 2024-02-20 11:46:59 +01:00
a334dff58b add ratelimit to ingress 2024-02-19 18:30:12 +01:00
c7fab83c39 update ci images 2024-02-17 14:41:39 +01:00
6b79f2599d minor fixes 2024-02-17 14:39:31 +01:00
a68ff0bd14 fix doc 2024-02-17 14:39:20 +01:00
ef4d35b6f5 added native image build refactoring 2024-02-17 12:18:10 +01:00
6b59d288d8 bump version to: 6.1.4-SNAPSHOT 2024-02-16 17:50:55 +01:00
0a84b7ad63 release: 6.1.3 2024-02-16 17:50:55 +01:00
b706139d4f remove need for reflection 2024-02-16 17:35:33 +01:00
bom
a81d5b1efc bump version to: 6.1.3-SNAPSHOT 2024-01-19 12:04:41 +01:00
bom
b5965cee3e release: 6.1.2 2024-01-19 12:04:41 +01:00
bom
0e20df8ff0 Add token env variable to CI 2024-01-19 12:04:00 +01:00
bom
8b4e431418 bump version to: 6.1.2-SNAPSHOT 2024-01-19 11:44:23 +01:00
bom
7c21211eac release: 6.1.1 2024-01-19 11:44:23 +01:00
bom
b8345402d1 Update docker image versions for CI 2024-01-19 11:38:23 +01:00
0caec73a1d updated refactorings 2023-12-15 19:13:20 +01:00
jem
71d0ea7d8e updated refactorings 2023-12-15 18:07:16 +00:00
Clemens
0564dd4850 Merge branch 'master' of ssh://repo.prod.meissa.de:2222/meissa/c4k-common 2023-11-10 13:32:16 +01:00
Clemens
e0e6360853 Added resource-load-macro as refactoring 2023-11-10 13:30:18 +01:00
bom
1227a8a76f Add another inline_resources macro test 2023-11-10 13:26:16 +01:00
bom
d7ed1e00bd bump version to: 6.1.1-SNAPSHOT 2023-10-27 17:41:15 +02:00
bom
c8b3574ef7 release: 6.1.0 2023-10-27 17:41:15 +02:00
bom
bfa2007459 Finish implementing macro
* move macros.clj to common directory, to be included in shadow-cljs
* add cljs macro-passing file
* add tests to check functionality
2023-10-27 17:35:15 +02:00
bom
a9fa5e4a35 Add inline-resources macro
* allows inclusion of a whole folder in the "resources/" directory
2023-10-27 16:29:43 +02:00
bom
abfe467382 Fix typo 2023-10-27 14:53:29 +02:00
bom
52d5263b86 Fix naming to avoid breaking change 2023-10-27 12:37:24 +02:00
bom
0da8216e6c Refactor get-content-from-element to enable tests 2023-10-27 12:21:12 +02:00
bom
dfc65cacc4 Fix build.py 2023-10-06 12:07:09 +02:00
bom
db7e4a391d bump version to: 6.0.4-SNAPSHOT 2023-10-06 12:05:08 +02:00
bom
2c3525cab6 release: 6.0.3 2023-10-06 12:05:08 +02:00
bom
f147947558 Fix get-content-from-element 2023-10-06 11:57:15 +02:00
bom
966ecf1009 Fix get-content-from-element 2023-10-06 11:56:20 +02:00
8189206ec6 [Skip-CI] Add Development and mirrors section 2023-07-28 13:56:02 +02:00
7eae8766aa replace deprecated jira with taiga 2023-07-22 12:42:38 +02:00
d7c2852f28 update refactoring state 2023-07-22 10:47:01 +02:00
4b182a618c use new build container 2023-07-21 17:49:53 +02:00
c549f2d5fd use fixed build containers 2023-07-20 17:29:13 +02:00
6d96d0e466 use pyb for ci 2023-07-20 09:23:19 +02:00
Clemens
f143587c95 Added todo 2023-06-16 13:53:15 +02:00
630ad9b3d6 update refactoring state 2023-06-16 13:00:31 +02:00
9500ed2ebf Update README.md [repo model], Module c4k-website 2023-05-30 15:17:37 +02:00
bom
fdb24a4ad5 Version bump 2023-05-19 10:58:45 +02:00
65 changed files with 1919 additions and 606 deletions

1
.gitignore vendored
View file

@ -27,3 +27,4 @@ my-auth.edn
.clj-kondo/
.lsp/
.eastwood

View file

@ -5,7 +5,7 @@ stages:
- upload
.cljs-job: &cljs
image: domaindrivenarchitecture/shadow-cljs
image: "domaindrivenarchitecture/ddadevops-clj-cljs:4.11.4"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
@ -13,46 +13,49 @@ stages:
- .shadow-cljs/
- .m2
before_script:
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
- echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
- npm install
.clj-job: &clj
image: domaindrivenarchitecture/lein
image: "domaindrivenarchitecture/ddadevops-clj:4.11.4"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .m2
before_script:
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
- mkdir -p /root/.lein
- echo "{:auth {:repository-auth {#\"clojars\" {:username \"${CLOJARS_USER}\" :password \"${CLOJARS_TOKEN_DOMAINDRIVENARCHITECTURE}\" }}}}" > ~/.lein/profiles.clj
.tag_only: &tag_only
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: never
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
test-clj:
<<: *clj
stage: build_and_test
script:
- lein test
- pyb test_clj
test-cljs:
<<: *cljs
stage: build_and_test
script:
- shadow-cljs compile test
- node target/node-tests.js
- pyb test_cljs
upload-clj-release:
<<: *clj
<<: *tag_only
stage: upload
rules:
- if: '$CI_COMMIT_TAG != null'
script:
- lein deploy
- pyb upload_clj
upload-cljs-release:
<<: *clj
<<: *tag_only
stage: upload
rules:
- if: '$CI_COMMIT_TAG != null'
script:
- mv project.clj project-clj.clj && mv project-cljs.clj project.clj
- lein deploy
- pyb upload_cljs

456
README.md
View file

@ -1,86 +1,408 @@
# convention 4 kubernetes: c4k-common
[![Clojars Project](https://img.shields.io/clojars/v/org.domaindrivenarchitecture/c4k-common-clj.svg)](https://clojars.org/org.domaindrivenarchitecture/c4k-common-clj) [![Clojars Project](https://img.shields.io/clojars/v/org.domaindrivenarchitecture/c4k-common-cljs.svg)](https://clojars.org/org.domaindrivenarchitecture/c4k-common-cljs) [![pipeline status](https://gitlab.com/domaindrivenarchitecture/c4k-common/badges/master/pipeline.svg)](https://gitlab.com/domaindrivenarchitecture/c4k-common/-/commits/master)
[<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-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)
## Purpose
c4k-common provides the foundation for all our c4k modules.
It is now possible to generate a working prometheus monitoring file in yaml format.
Your config.edn and your auth.edn should at least contain the following fields:
config.edn - minimal example
```clojure
{:k3s-cluster-name "your-cluster-name"
:k3s-cluster-stage :prod
:grafana-cloud-url "your-url"}
```
auth.edn - minimal example
```clojure
{:grafana-cloud-user "user"
:grafana-cloud-password "password"}
```
call (with jarwrapper installed):
```bash
c4k-common-standalone.jar config.edn auth.edn > monitoring.yaml
```
[<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)
## Rationale
There are many comparable solutions for creating c4k deployments like helm or kustomize. Why do we need another one?
* We like the simplicity of kustomize. Yaml in, yaml out, the ability to lint the result and the option to split large yaml files into objects. But a simple overwriting per environment may not be enough ...
* We like helm packages. A package encapsulates the setup for an application. On the one hand, but on the other hand we don't like the idea of having to program and debug in a template language. We can program much better in real programming languages.
There are many comparable solutions for creating c4k deployments like `helm` or `kustomize`.
`kustomize` is great to manage your k8s manifests by splitting huge files into handy parts.
`helm` is great because of its large community.
Our convention 4 kubernetes c4k-* tools combine the advantages of both approaches:
* Packages for one application
* Programming in clojure
* yaml / edn as input and output, no more magic
* good validation, integration as api, cli or in the browser
Why do we need another one? Why do you continue the reading here?
## Usage
We combine the simplicity of `kustomize` with the ability to do real programming like software developers would do.
c4k-common provides the basic functionality for our c4k-modules.
Following the principle
"Use programming language for programming"
we clearly enjoy writing Kubernetes manifests with Clojure. In comparison to helms templating, things such as business logic, conventions, input validation, versions, dependencies and reuse are much easier and much more reliable to implement with c4k.
By the way, c4k means "convention for kubernetes".
### Features
c4k-common supports the following use cases:
- [convention 4 kubernetes: c4k-common](#convention-4-kubernetes-c4k-common)
- [Rationale](#rationale)
- [Features](#features)
- [Target CLI and Web Frontend](#target-cli-and-web-frontend)
- [Separate Configuration From Credentials](#separate-configuration-from-credentials)
- [Input as EDN or Yaml](#input-as-edn-or-yaml)
- [Inline k8s Resources for Versioning \& Dependencies](#inline-k8s-resources-for-versioning--dependencies)
- [Work on Structured Data Instead of Flat Templating](#work-on-structured-data-instead-of-flat-templating)
- [Validate Your Inputs](#validate-your-inputs)
- [Namespaces](#namespaces)
- [Ingress](#ingress)
- [Postgres Database](#postgres-database)
- [Monitoring With Grafana Cloud](#monitoring-with-grafana-cloud)
- [Refactoring \& Module Overview](#refactoring--module-overview)
- [Development \& Mirrors](#development--mirrors)
- [License](#license)
#### Target CLI and Web Frontend
To create your own c4k module set up your cli analogous to the following:
```clojure
(defn -main [& cmd-args]
(uberjar/main-common
"c4k-forgejo" ;; name of your app
core/config? ;; schema for config validation
core/auth? ;; schema for credential validation
core/config-defaults ;; want to set default values?
core/k8s-objects ;; the function generate the k8s manifest
cmd-args ;; command line arguments given
))
```
The full example can be found here: https://repo.prod.meissa.de/meissa/c4k-forgejo/src/branch/main/src/main/clj/dda/c4k_forgejo/uberjar.clj
With c4k instead of using cli to generate manifests, you can also generate your manifests via web-application (using page local js without server interaction)
```html
<html>
<head>
<link href="bootstrap.min.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="c4k-content"></div>
<script src="js/main.js"></script>
</body>
</html>
```
[![Try it out](doc/tryItOut.png "Try out yourself")](https://domaindrivenarchitecture.org/pages/dda-provision/c4k-forgejo/)
See: https://repo.prod.meissa.de/meissa/c4k-forgejo/src/branch/main/public/index.html
and: https://repo.prod.meissa.de/meissa/c4k-forgejo/src/branch/main/src/main/cljs/dda/c4k_forgejo/browser.cljs
#### Separate Configuration From Credentials
We think it is a good idea to have credentials separated from configuration. All our functions, cli and frontend are following this principle. Thus, for executing separated config and authentication infos have to be provided.
The following command line yields the resulting k8s manifests in `k8s-manifest.yaml`:
```bash
java -jar c4k-common.jar config.edn auth.edn > k8s-manifest.yaml
```
Using the tool `jarwrapper` the command line can even be shortened to:
```bash
c4k-common config.edn auth.edn > k8s-manifest.yaml
```
#### Input as EDN or Yaml
c4k-common supports yaml and edn as format for all of its resources (input and output).
Hence, the following command line will also work:
```bash
c4k-common config.yaml auth.yaml > k8s-manifest.yaml
```
#### Inline k8s Resources for Versioning & Dependencies
We inline all resources used in our libraries & applications. You can generate k8s manifests everywhere without additional external dependencies.
In case of
* java: Resources are included in the jar-file out of the box (see https://repo.prod.meissa.de/meissa/c4k-forgejo/src/branch/main/project.clj#L13).
* js: With a slim macro call we inline resources to the resulting js file (see https://repo.prod.meissa.de/meissa/c4k-forgejo/src/branch/main/src/main/cljc/dda/c4k_forgejo/forgejo.cljc#L72-L74)
* native: On native builds we also inline resources (see https://repo.prod.meissa.de/meissa/c4k-forgejo/src/branch/main/build.py#L126)
#### Work on Structured Data Instead of Flat Templating
To keep things simple, we also do templating. But we convert given k8s resources to structured data.
This allows us to have more control and do unit tests:
k8s-resource:
```yaml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: ratelimit
spec:
rateLimit:
average: AVG
burst: BRS
```
Replace values:
```clojure
(defn-spec generate-rate-limit-middleware pred/map-or-seq?
[config rate-limit-config?]
(let [{:keys [max-rate max-concurrent-requests]} config]
(->
(yaml/load-as-edn "forgejo/middleware-ratelimit.yaml")
(cm/replace-key-value :average max-rate)
(cm/replace-key-value :burst max-concurrent-requests))))
```
Have a unit-test:
```clojure
(deftest should-generate-middleware-ratelimit
(is (= {:apiVersion "traefik.containo.us/v1alpha1",
:kind "Middleware",
:metadata {:name "ratelimit"},
:spec {:rateLimit {:average 10, :burst 5}}}
(cut/generate-rate-limit-middleware {:max-rate 10, :max-concurrent-requests 5}))))
```
#### Validate Your Inputs
Have you recognized the `defn-spec` macro above? We use allover validation, e.g.
```clojure
(def rate-limit-config? (s/keys :req-un [::max-rate
::max-concurrent-requests]))
(defn-spec generate-rate-limit-middleware pred/map-or-seq?
[config rate-limit-config?]
...)
```
#### Namespaces
We support namespaces for ingress & postgres (monitoring lives in it's own namespace `monitoring`).
```clojure
(dda.c4k-common.namespace/generate {:namespace "myapp"})
```
yields:
```clojure
[{:apiVersion "v1"
:kind "Namespace"
:metadata {:name "myapp"}}]
```
which renders to:
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: myapp
```
#### Ingress
In most cases we use `generate-ingress-and-cert` which generates an ingress in combination with letsencrypt cert for a named service.
```clojure
(dda.c4k-common.ingress/generate-ingress-and-cert
{:fqdns ["test.jit.si"]
:service-name "web"
:service-port 80})
```
yields:
```clojure
[{:apiVersion "cert-manager.io/v1",
:kind "Certificate",
...
:spec
{:secretName "web",
:commonName "test.jit.si",
:duration "2160h",
:renewBefore "720h",
:dnsNames ["test.jit.si"],
:issuerRef {:name "staging", :kind "ClusterIssuer"}}}
{:apiVersion "networking.k8s.io/v1",
:kind "Ingress",
...
:spec
{:tls [{:hosts ["test.jit.si"], :secretName "web"}],
:rules
[{:host "test.jit.si",
:http {:paths [{:path "/",
:pathType "Prefix",
:backend
{:service {:name "web",
:port {:number 80}}}}]}}]}}]
```
which renders to:
```yaml
apiVersion: cert-manager.io/v1
kind: Certificate
...
spec:
secretName: web
commonName: test.jit.si
duration: 2160h
renewBefore: 720h
dnsNames:
- test.jit.si
issuerRef:
name: staging
kind: ClusterIssuer
---
apiVersion: networking.k8s.io/v1
kind: Ingress
...
spec:
tls:
- hosts:
- test.jit.si
secretName: web
rules:
- host: test.jit.si
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: web
port:
number: 80
```
#### Postgres Database
If your application needs a database, we often use postgres:
```clojure
(cut/generate-deployment {:postgres-image "postgres:16"})
```
yields:
```clojure
{:apiVersion "apps/v1",
:kind "Deployment",
...
:spec
{:selector {:matchLabels {:app "postgresql"}},
:strategy {:type "Recreate"},
:template
{:metadata {:labels {:app "postgresql"}},
:spec
{:containers
[{:image "postgres:16",
:name "postgresql",
:env
[{:name "POSTGRES_USER", :valueFrom {:secretKeyRef {:name "postgres-secret", :key "postgres-user"}}}
{:name "POSTGRES_PASSWORD", :valueFrom {:secretKeyRef {:name "postgres-secret", :key "postgres-password"}}}
{:name "POSTGRES_DB", :valueFrom {:configMapKeyRef {:name "postgres-config", :key "postgres-db"}}}],
:ports [{:containerPort 5432, :name "postgresql"}],
:volumeMounts
[...],
:volumes
[...]}}}}
```
which renders to:
```yaml
apiVersion: apps/v1
kind: Deployment
...
spec:
selector:
matchLabels:
app: postgresql
strategy:
type: Recreate
template:
metadata:
labels:
app: postgresql
spec:
containers:
- image: postgres:16
name: postgresql
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-password
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: postgres-config
key: postgres-db
ports:
- containerPort: 5432
name: postgresql
volumeMounts:
...
volumes:
...
```
We optimized our db installation to run between 2Gb anf 16Gb Ram usage.
#### Monitoring With Grafana Cloud
With minimal config of
```clojure
(def conf
{:k3s-cluster-name "your-cluster-name"
:k3s-cluster-stage :prod
:grafana-cloud-url "your-url"})
(def auth
{:grafana-cloud-user "user"
:grafana-cloud-password "password"})
(monitoring/generate conf auth)
```
You can connect your application to grafana cloud.
## Refactoring & Module Overview
<!---
1. version
2. configs as EDN and YAML
3. renamed test-helper
4. common load-as-edn
5. standardized uberjar
6. groups for webview
7. use common ingress
-->
| Module | Version | [inline-macro to load resources][macro] | [native build][native] | [namespaces][ns] | [split config and auth][split] |
|---------------|---------|:---------------------------------------:|:----------------------:|:----------------:|:------------------------------:|
| c4k-keycloak | 1.2.1 | x | x | x | |
| c4k-taiga | 1.1.1 | x | x | | |
| c4k-nextcloud | 10.2 | x | x | x | |
| c4k-jitsi | 2.1 | x | x | x | x |
| c4k-forgejo | 3.5.0 | x | x | x | x |
| c4k-shynet | 1.0 | x | x | | x |
| c4k-website | 2.0 | x | x | | |
| Module | Version | [renamed test-helper][th1] | [common load-as-edn][edn1] | [standardized uberjar][ujar1] | [groups for webview][bgrp1] | [use common ingress][ing1] | [use common monitoring][mon1] | [validate examples][val1] | [repo model][repo1] |
|---------------|---------|:--------------------------:|:--------------------------:|:-----------------------------:|:---------------------------:|:--------------------------:|:-----------------------------:|:-------------------------:|:-------------------:|
| c4k-keycloak | 0.2 | - | x | x | x | x | x | x | x |
| c4k-jira | 1.1 | - | | | | | | | x |
| c4k-nextcloud | 4.0 | - | x | x | x | x | x | x | x |
| c4k-jitsi | 1.3 | - | x | x | x | x | x | x | x |
| c4k-forgejo | 2.0 | x | x | x | x | x | x | x | x |
| c4k-shynet | 1.0 | - | | x | | | | | x |
| c4k-website | 1.1 | x | x | x | x | x | x | x | |
[macro]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/61d05ceedb6dcbc6bb96b96fe6f03598e2878195
[native]: https://repo.prod.meissa.de/meissa/c4k-forgejo/pulls/4/files
[split]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/d4fb8ca9e2ab44f9f9923d2e09c81a61e44b39b2
[ns]: https://repo.prod.meissa.de/meissa/c4k-keycloak/commit/3639f3d5e6d5b364822a05b3d5d569bbc556a68b
[th1]: https://gitlab.com/domaindrivenarchitecture/c4k-gitea/-/merge_requests/1
[edn1]: https://gitlab.com/domaindrivenarchitecture/c4k-website/-/merge_requests/1
[ing1]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/214aa41c28662fbf7a49998e17404e7ac9216430
[ujar1]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/b852a74dc561c3ab619e4f4d0748ab51e75edc13
[bgrp1]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/7ea442adaef727d5b48b242fd0baaaf51902d06e
[mon1]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/19e580188ea56ea26ff3a0bfb08ca428b881ad9a
[val1]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/5f08a108072569473463fb8f19150a12e564e54f
[repo1]: https://repo.prod.meissa.de/meissa/c4k-forgejo/commit/e9ee6136f3347d5fccefa6b5b4a02d30c4dc42e1
## Development & Mirrors
Development happens at: https://repo.prod.meissa.de/meissa/c4k-common
Mirrors are:
* https://codeberg.org/meissa/c4k-common (Issues and PR)
* https://gitlab.com/domaindrivenarchitecture/c4k-common (CI)
* https://github.com/DomainDrivenArchitecture/c4k-common
For more details about our repository model see: https://repo.prod.meissa.de/meissa/federate-your-repos
## License
Copyright © 2022 meissa GmbH
Copyright © 2022, 2023, 2024 meissa GmbH
Licensed under the [Apache License, Version 2.0](LICENSE) (the "License")
Pls. find licenses of our subcomponents [here](doc/SUBCOMPONENT_LICENSE)

124
build.py Normal file
View file

@ -0,0 +1,124 @@
from os import environ
from subprocess import run
from pybuilder.core import init, task
from ddadevops import *
default_task = "dev"
name = "c4k-common"
MODULE = "not-used"
PROJECT_ROOT_PATH = "."
@init
def initialize(project):
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": [],
"mixin_types": ["RELEASE"],
"release_primary_build_file": "project.clj",
"release_secondary_build_files": [
"project-cljs.clj",
],
"release_main_branch": "main",
}
build = ReleaseMixin(project, input)
build.initialize_build_dir()
@task
def test_clj(project):
run("lein test", shell=True, check=True)
@task
def test_cljs(project):
run("shadow-cljs compile test", shell=True, check=True)
run("node target/node-tests.js", shell=True, check=True)
@task
def upload_clj(project):
run("lein deploy", shell=True, check=True)
@task
def upload_cljs(project):
run(
"mv project.clj project-clj.clj && mv project-cljs.clj project.clj",
shell=True,
check=True,
)
run("lein deploy", shell=True, check=True)
run(
"mv project.clj project-cljs.clj && mv project-clj.clj project.clj",
shell=True,
check=True,
)
@task
def lint(project):
# TODO: Do proper configuration
"""run(
"lein eastwood",
shell=True,
check=True,
)"""
run(
"lein ancient check",
shell=True,
check=True,
)
@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_clj(project)
test_cljs(project)
lint(project)

View file

@ -49,3 +49,20 @@ C4Context
Rel(app-backup, app-db-storage, "*dbc")
```
# Layout of a component on example of namespace
```mermaid
classDiagram
class namespace {
config? // the external representation
default-config // static defaults
generate(config, auth) seq
}
class namespace-internal {
config? // the internal representation
generate-namespace(config) map
}
```

BIN
doc/tryItOut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -1,4 +1,4 @@
(defproject org.domaindrivenarchitecture/c4k-common-cljs "6.0.2"
(defproject org.domaindrivenarchitecture/c4k-common-cljs "8.0.1-SNAPSHOT"
:description "Contains predicates and tools for c4k"
:url "https://domaindrivenarchitecture.org"
:license {:name "Apache License, Version 2.0"

View file

@ -1,18 +1,19 @@
(defproject org.domaindrivenarchitecture/c4k-common-clj "6.0.2"
(defproject org.domaindrivenarchitecture/c4k-common-clj "8.0.1-SNAPSHOT"
:description "Contains predicates and tools for c4k"
:url "https://domaindrivenarchitecture.org"
:license {:name "Apache License, Version 2.0"
:url "https://www.apache.org/licenses/LICENSE-2.0.html"}
:dependencies [[org.clojure/clojure "1.11.1" :scope "provided"]
[org.clojure/tools.reader "1.3.6"]
:dependencies [[org.clojure/clojure "1.11.3"]
[org.clojure/tools.reader "1.4.2"]
[aero "1.1.6"]
[orchestra "2021.01.01-1"]
[expound "0.9.0"]
[clj-commons/clj-yaml "1.0.26"]]
[clj-commons/clj-yaml "1.0.27"]]
:target-path "target/%s/"
:source-paths ["src/main/cljc"
"src/main/clj"]
:resource-paths ["src/main/resources"]
:resource-paths ["src/main/resources"
"project.clj"]
:repositories [["snapshots" :clojars]
["releases" :clojars]]
:deploy-repositories [["snapshots" {:sign-releases false :url "https://clojars.org/repo"}]
@ -25,17 +26,13 @@
:uberjar {:aot :all
:main dda.c4k-common.uberjar
:uberjar-name "c4k-common-standalone.jar"
:dependencies [[org.clojure/tools.cli "1.0.214"]
[ch.qos.logback/logback-classic "1.4.6"
:dependencies [[org.clojure/tools.cli "1.1.230"]
[ch.qos.logback/logback-classic "1.5.6"
:exclusions [com.sun.mail/javax.mail]]
[org.slf4j/jcl-over-slf4j "2.0.7"]]}}
[org.slf4j/jcl-over-slf4j "2.0.13"]]}}
:release-tasks [["test"]
["vcs" "assert-committed"]
["change" "version" "leiningen.release/bump-version" "release"]
["vcs" "commit"]
["vcs" "tag" "v" "--no-sign"]
["change" "version" "leiningen.release/bump-version"]]
:aliases {"inst" ["shell"
"sh"
"-c"
"lein uberjar && sudo install -m=755 target/uberjar/c4k-common-standalone.jar /usr/local/bin/c4k-common-standalone.jar"]})
["change" "version" "leiningen.release/bump-version"]])

View file

@ -8,10 +8,10 @@
[input string?]
(.encodeToString
(Base64/getEncoder)
(.getBytes input "UTF-8")))
(.getBytes ^String input "UTF-8")))
(defn-spec decode string?
[input string?]
(String.
(.decode (Base64/getDecoder) input)
(.decode (Base64/getDecoder) ^String input)
"UTF-8"))

View file

@ -4,6 +4,7 @@
[clojure.spec.alpha :as s]
[clojure.string :as cs]
[clojure.tools.reader.edn :as edn]
[clojure.java.io :as io]
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.common :as cm]
[dda.c4k-common.core :as core]
@ -13,9 +14,19 @@
(str
"usage:
-v | --version : Shows project version
-h : Shows help
The following options require the use of main-cm instead of main-common
-c | --config : Only generate the config
-a | --auth : Only generate the auth
" name " {your configuraton file} {your authorization file}"))
(s/def ::options (s/* #{"-h"}))
(s/def ::options (s/* #{"-h"
"-v" "--version"
"-c" "--config"
"-a" "--auth"}))
(s/def ::filename (s/and string?
#(not (cs/starts-with? % "-"))))
(s/def ::cmd-args (s/cat :options ::options
@ -28,7 +39,7 @@
(s/explain spec args)
(println (str "Bad commandline arguments\n" (usage name))))
(defn main-common [name config-spec? auth-spec? config-defaults k8s-objects cmd-args]
(defn main-cm [name config-spec? auth-spec? config-defaults config-objects auth-objects cmd-args]
(let [parsed-args-cmd (s/conform ::cmd-args cmd-args)]
(if (= ::s/invalid parsed-args-cmd)
(invalid-args-msg name ::cmd-args cmd-args)
@ -37,6 +48,40 @@
(cond
(some #(= "-h" %) options)
(println (usage name))
(some #(or (= "-v" %) (= "--version" %)) options)
(println (some-> (io/resource "project.clj") slurp edn/read-string (nth 2)))
:else
(let [config-str (slurp config)
auth-str (slurp auth)
config-parse-fn (if (yaml/is-yaml? config) yaml/from-string edn/read-string)
auth-parse-fn (if (yaml/is-yaml? auth) yaml/from-string edn/read-string)
config-edn (config-parse-fn config-str)
auth-edn (auth-parse-fn auth-str)
config-valid? (s/valid? config-spec? config-edn)
auth-valid? (s/valid? auth-spec? auth-edn)
only-config (some #(or (= "-c" %) (= "--config" %)) options)
only-auth (some #(or (= "-a" %) (= "--auth" %)) options)]
(if (and config-valid? auth-valid?)
(println (cm/generate-cm config-edn auth-edn config-defaults config-objects auth-objects only-config only-auth))
(do
(when (not config-valid?)
(println
(expound/expound-str config-spec? config-edn {:print-specs? false})))
(when (not auth-valid?)
(println
(expound/expound-str auth-spec? auth-edn {:print-specs? false})))))))))))
(defn ^{:deprecated "6.3.1"} main-common [name config-spec? auth-spec? config-defaults k8s-objects cmd-args]
(let [parsed-args-cmd (s/conform ::cmd-args cmd-args)]
(if (= ::s/invalid parsed-args-cmd)
(invalid-args-msg name ::cmd-args cmd-args)
(let [{:keys [options args]} parsed-args-cmd
{:keys [config auth]} args]
(cond
(some #(= "-h" %) options)
(println (usage name))
(some #(or (= "-v" %) (= "--version" %)) options)
(println (some-> (io/resource "project.clj") slurp edn/read-string (nth 2)))
:else
(let [config-str (slurp config)
auth-str (slurp auth)
@ -57,9 +102,11 @@
(expound/expound-str auth-spec? auth-edn {:print-specs? false})))))))))))
(defn -main [& cmd-args]
(main-common "c4k-common"
(main-cm
"c4k-common"
core/config?
core/auth?
core/config-defaults
core/k8s-objects
core/config-objects
core/auth-objects
cmd-args))

View file

@ -7,21 +7,6 @@
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.c4k-common.predicate :as cp]))
;; deprecated functions were moved to dda.c4k-common.predicate
(defn ^{:deprecated "0.1"} bash-env-string?
[input]
(and (string? input)
(not (re-matches #".*['\"\$]+.*" input))))
(defn ^{:deprecated "0.1"} fqdn-string?
[input]
(and (string? input)
(some? (re-matches #"(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)" input))))
(defn ^{:deprecated "0.1"} letsencrypt-issuer?
[input]
(contains? #{:prod :staging} input))
(defn-spec replace-named-value cp/map-or-seq?
[coll cp/map-or-seq?
name string?
@ -42,21 +27,47 @@
%)
coll))
(defn-spec replace-all-matching-values-by-new-value cp/map-or-seq?
(defn-spec replace-all-matching cp/map-or-seq?
[coll cp/map-or-seq?
match-value string?
replace-value cp/str-or-number?]
(clojure.walk/postwalk #(if (and (= (type match-value) (type %))
(= match-value %))
replace-value
%)
coll))
(defn-spec ^{:deprecated "6.2.4"} replace-all-matching-values-by-new-value cp/map-or-seq?
"Use replace-all-matching instead"
[coll cp/map-or-seq?
value-to-match string?
value-to-replace cp/str-or-number?]
(clojure.walk/postwalk #(if (and (= (type value-to-match) (type %))
(= value-to-match %))
value-to-replace
%)
coll))
(replace-all-matching coll value-to-match value-to-replace))
(defn-spec concat-vec vector?
[& vs (s/* cp/string-sequence?)]
(into []
(apply concat vs)))
(defn generate-cm
[my-config
my-auth
config-defaults
config-objects
auth-objects
only-config
only-auth]
(let [resulting-config (merge config-defaults my-config)
both (or (and only-config only-auth) (and (not only-config) (not only-auth)))
res-vec (cond
both (concat-vec (config-objects resulting-config) (auth-objects resulting-config my-auth))
only-config (config-objects resulting-config)
only-auth (auth-objects resulting-config my-auth))]
(cs/join
"\n---\n"
res-vec)))
(defn generate-common
[my-config
my-auth

View file

@ -7,15 +7,22 @@
(def config-defaults {})
(def config? (s/keys :req-un []
(def config? (s/keys :req-un [::monitoring/mon-cfg]
:opt-un []))
(def auth? (s/keys :req-un []
(def auth? (s/keys :req-un [::monitoring/mon-auth]
:opt-un []))
(defn k8s-objects [config auth]
(defn config-objects [config]
(let []
(map yaml/to-string
(filter #(not (nil? %))
(cm/concat-vec
(monitoring/generate config auth))))))
(monitoring/generate-config))))))
(defn auth-objects [config auth]
(let []
(map yaml/to-string
(filter #(not (nil? %))
(cm/concat-vec
(monitoring/generate-auth (:mon-cfg config) (:mon-auth auth)))))))

View file

@ -1,84 +1,80 @@
(ns dda.c4k-common.ingress
(:require
[clojure.spec.alpha :as s]
#?(:cljs [shadow.resource :as rc])
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
#?(:clj [clojure.edn :as edn]
:cljs [cljs.reader :as edn])
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.common :as cm]
[dda.c4k-common.predicate :as pred]))
[dda.c4k-common.namespace :as ns]
[dda.c4k-common.ingress.ingress-internal :as int]))
(s/def ::issuer pred/letsencrypt-issuer?)
(s/def ::service-name string?)
(s/def ::app-name string?)
(s/def ::ingress-name string?)
(s/def ::cert-name string?)
(s/def ::service-port pos-int?)
(s/def ::fqdns (s/coll-of pred/fqdn-string?))
(s/def ::issuer ::int/issuer)
(s/def ::service-name ::int/service-name)
(s/def ::app-name ::int/app-name)
(s/def ::ingress-name ::int/ingress-name)
(s/def ::cert-name ::int/cert-name)
(s/def ::service-port ::int/service-port)
(s/def ::fqdns ::int/fqdns)
(s/def ::average-rate ::int/average-rate)
(s/def ::burst-rate ::int/burst-rate)
(def simple-ingress? (s/keys :req-un [::fqdns ::service-name ::service-port]
:opt-un [::issuer]))
:opt-un [::issuer ::average-rate ::burst-rate ::ns/namespace]))
(def ingress? (s/keys :req-un [::fqdns ::app-name ::ingress-name ::service-name ::service-port]
:opt-un [::issuer ::cert-name]))
:opt-un [::issuer ::cert-name ::rate-limit-name ::ns/namespace]))
(def certificate? (s/keys :req-un [::fqdns ::app-name ::cert-name]
:opt-un [::issuer]))
:opt-un [::issuer ::ns/namespace]))
(def ingress-defaults {:issuer "staging"})
(def rate-limit-config? (s/keys :req-un [::rate-limit-name
::average-rate
::burst-rate]))
#?(:cljs
(defmethod yaml/load-resource :ingress [resource-name]
(case resource-name
"ingress/host-rule.yaml" (rc/inline "ingress/host-rule.yaml")
"ingress/certificate.yaml" (rc/inline "ingress/certificate.yaml")
"ingress/ingress.yaml" (rc/inline "ingress/ingress.yaml")
(throw (js/Error. "Undefined Resource!")))))
(def default-config
(merge ns/default-config
{:issuer "staging"
:average-rate 10
:burst-rate 20}))
(defn-spec generate-host-rule pred/map-or-seq?
[service-name ::service-name
service-port ::service-port
fqdn pred/fqdn-string?]
(->
(yaml/load-as-edn "ingress/host-rule.yaml")
(cm/replace-all-matching-values-by-new-value "FQDN" fqdn)
(cm/replace-all-matching-values-by-new-value "SERVICE_PORT" service-port)
(cm/replace-all-matching-values-by-new-value "SERVICE_NAME" service-name)))
(defn-spec generate-ingress pred/map-or-seq?
[config ingress?]
(let [{:keys [ingress-name cert-name service-name service-port fqdns app-name]} config]
(->
(yaml/load-as-edn "ingress/ingress.yaml")
(assoc-in [:metadata :name] ingress-name)
(assoc-in [:metadata :labels :app.kubernetes.part-of] app-name)
(assoc-in [:spec :tls 0 :secretName] cert-name)
(assoc-in [:spec :tls 0 :hosts] fqdns)
(assoc-in [:spec :rules] (mapv (partial generate-host-rule service-name service-port) fqdns)))))
(defn-spec generate-certificate pred/map-or-seq?
(defn-spec generate-certificate map?
[config certificate?]
(let [{:keys [cert-name issuer fqdns app-name]
:or {issuer "staging"}} config
letsencrypt-issuer (name issuer)]
(->
(yaml/load-as-edn "ingress/certificate.yaml")
(assoc-in [:metadata :name] cert-name)
(assoc-in [:metadata :labels :app.kubernetes.part-of] app-name)
(assoc-in [:spec :secretName] cert-name)
(assoc-in [:spec :commonName] (first fqdns))
(assoc-in [:spec :dnsNames] fqdns)
(assoc-in [:spec :issuerRef :name] letsencrypt-issuer))))
(let [final-config (merge default-config
config)]
(int/generate-certificate final-config)))
(defn-spec generate-ingress-and-cert any?
[simple-ingress-config simple-ingress?]
(let [{:keys [service-name]} simple-ingress-config
config (merge {:app-name service-name
(defn-spec generate-ingress map?
[config ingress?]
(let [final-config (merge default-config
config)]
(int/generate-ingress final-config)))
(defn-spec generate-ingress-and-cert seq?
[config simple-ingress?]
(let [{:keys [service-name]} config
final-config (merge {:app-name service-name
:ingress-name service-name
:cert-name service-name}
ingress-defaults
simple-ingress-config)]
[(generate-certificate config)
(generate-ingress config)]))
default-config
config)]
[(int/generate-certificate final-config)
(int/generate-ingress final-config)]))
(defn-spec generate-simple-ingress seq?
[config simple-ingress?]
(let [{:keys [service-name]} config
final-config (merge {:app-name service-name
:ingress-name service-name
:cert-name service-name
:rate-limit-name service-name}
default-config
config)
{:keys [average-rate burst-rate]} final-config]
[(int/generate-certificate final-config)
(int/generate-rate-limit-middleware {:rate-limit-name service-name
:namespace (:namespace final-config)
:average-rate average-rate
:burst-rate burst-rate})
(int/generate-ingress final-config)]))

View file

@ -0,0 +1,108 @@
(ns dda.c4k-common.ingress.ingress-internal
(:require
[clojure.spec.alpha :as s]
#?(:cljs [shadow.resource :as rc])
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.common :as cm]
[dda.c4k-common.namespace :as ns]
[dda.c4k-common.predicate :as pred]))
#?(:cljs
(defmethod yaml/load-resource :ingress [resource-name]
(case resource-name
"ingress/certificate.yaml" (rc/inline "ingress/certificate.yaml")
"ingress/host-rule.yaml" (rc/inline "ingress/host-rule.yaml")
"ingress/ingress.yaml" (rc/inline "ingress/ingress.yaml")
"ingress/middleware-ratelimit.yaml" (rc/inline "ingress/middleware-ratelimit.yaml")
(throw (js/Error. (str "Undefined Resource: " resource-name))))))
(s/def ::issuer pred/letsencrypt-issuer?)
(s/def ::service-name string?)
(s/def ::app-name string?)
(s/def ::ingress-name string?)
(s/def ::cert-name string?)
(s/def ::service-port pos-int?)
(s/def ::fqdns (s/coll-of pred/fqdn-string?))
(s/def ::average-rate pos-int?)
(s/def ::burst-rate pos-int?)
(def ingress? (s/keys :req-un [::ingress-name ::app-name
::ns/namespace
::service-name ::service-port
::issuer ::cert-name
::fqdns]
:opt-un [::rate-limit-name]))
(def certificate? (s/keys :req-un [::fqdns ::app-name ::cert-name ::issuer ::ns/namespace]))
(def rate-limit-config? (s/keys :req-un [::rate-limit-name
::ns/namespace
::average-rate
::burst-rate]))
(defn-spec generate-host-rule map?
[service-name ::service-name
service-port ::service-port
fqdn pred/fqdn-string?]
(->
(yaml/load-as-edn "ingress/host-rule.yaml")
(cm/replace-all-matching "FQDN" fqdn)
(cm/replace-all-matching "SERVICE_PORT" service-port)
(cm/replace-all-matching "SERVICE_NAME" service-name)))
(defn-spec generate-certificate map?
[config certificate?]
(let [{:keys [cert-name issuer fqdns app-name namespace]} config
letsencrypt-issuer (name issuer)]
(->
(yaml/load-as-edn "ingress/certificate.yaml")
(assoc-in [:metadata :name] cert-name)
(assoc-in [:metadata :namespace] namespace)
(assoc-in [:metadata :labels :app.kubernetes.part-of] app-name)
(assoc-in [:spec :secretName] cert-name)
(assoc-in [:spec :commonName] (first fqdns))
(assoc-in [:spec :dnsNames] fqdns)
(assoc-in [:spec :issuerRef :name] letsencrypt-issuer))))
(defn-spec generate-rate-limit-middleware map?
[config rate-limit-config?]
(let [{:keys [rate-limit-name average-rate burst-rate namespace]} config]
(->
(yaml/load-as-edn "ingress/middleware-ratelimit.yaml")
(assoc-in [:metadata :name] (str rate-limit-name "-ratelimit"))
(assoc-in [:metadata :namespace] namespace)
(assoc-in [:spec :rateLimit :average] average-rate)
(assoc-in [:spec :rateLimit :burst] burst-rate))))
(defn-spec generate-ingress map?
[config ingress?]
(let [{:keys [ingress-name cert-name service-name service-port
fqdns app-name rate-limit-name namespace]} config]
(->
(yaml/load-as-edn "ingress/ingress.yaml")
(assoc-in [:metadata :name] ingress-name)
(assoc-in [:metadata :namespace] namespace)
(assoc-in [:metadata :labels :app.kubernetes.part-of] app-name)
(assoc-in [:metadata :annotations]
{:traefik.ingress.kubernetes.io/router.entrypoints
"web, websecure"
:traefik.ingress.kubernetes.io/router.middlewares
(if rate-limit-name
(str "default-redirect-https@kubernetescrd, "
namespace "-" rate-limit-name "-ratelimit@kubernetescrd")
"default-redirect-https@kubernetescrd")
:metallb.universe.tf/address-pool "public"})
(assoc-in [:spec :tls 0 :secretName] cert-name)
(assoc-in [:spec :tls 0 :hosts] fqdns)
(assoc-in [:spec :rules]
(mapv (partial generate-host-rule service-name service-port) fqdns)))))

View file

@ -0,0 +1,34 @@
(ns dda.c4k-common.macros
(:require [clojure.java.io :as io]
[clojure.string :as str])
(:import java.util.jar.JarFile))
(defn inline-resource-file [resource-url relative-resource-folder-path]
(let [files (.listFiles (io/file resource-url))
file-contents (map slurp files)
file-names (map #(str relative-resource-folder-path "/" (.getName %)) files)]
(zipmap file-names file-contents)))
(defn inline-resource-jar [resource-url]
(let [resource-url-string (.toString resource-url)
; Remove jar:file:
start-absolute (str/replace-first resource-url-string "jar:file:" "")
; Split path into jar base and search folder
jar-split (str/split start-absolute #"!/")
absolute-jar-path (first jar-split)
relative-file-path (second jar-split)
jar (JarFile. absolute-jar-path)
files (->> (enumeration-seq (.entries jar))
(filter #(str/starts-with? % relative-file-path))
(filter #(not (.isDirectory %))))
file-names (map #(.getName %) files)
file-contents (map #(slurp (.getInputStream jar %)) files)]
(zipmap file-names file-contents)))
(defmacro inline-resources [resource-path]
(let [resource-url (io/resource resource-path)
resource-protocol (.getProtocol resource-url)]
(case resource-protocol
"file" (inline-resource-file resource-url resource-path)
"jar" (inline-resource-jar resource-url))))

View file

@ -1,128 +1,68 @@
(ns dda.c4k-common.monitoring
(:require
[clojure.spec.alpha :as s]
#?(:cljs [shadow.resource :as rc])
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.predicate :as cp]
[dda.c4k-common.common :as cm]
[clojure.string :as str]))
[dda.c4k-common.monitoring.monitoring-internal :as int]))
(s/def ::grafana-cloud-user cp/bash-env-string?)
(s/def ::grafana-cloud-password cp/bash-env-string?)
(s/def ::grafana-cloud-url string?)
(s/def ::cluster-name string?)
(s/def ::cluster-stage cp/stage?)
(s/def ::pvc-storage-class-name cp/pvc-storage-class-name?)
(s/def ::node-regex string?)
(s/def ::traefik-regex string?)
(s/def ::kube-state-regex string?)
(s/def ::grafana-cloud-user ::int/grafana-cloud-user)
(s/def ::grafana-cloud-password ::int/grafana-cloud-password)
(s/def ::grafana-cloud-url ::int/grafana-cloud-url)
(s/def ::cluster-name ::int/cluster-name)
(s/def ::cluster-stage ::int/cluster-stage)
(s/def ::mon-cfg (s/keys :req-un [::grafana-cloud-url
::cluster-name
::cluster-stage]))
(s/def ::mon-auth (s/keys :req-un [::grafana-cloud-user
::grafana-cloud-password]))
(s/def ::storage (s/keys :opt-un [::pvc-storage-class-name]))
(s/def ::filter-regex (s/keys :req-un [::node-regex
::traefik-regex
::kube-state-regex]))
(def metric-regex {:node-regex
(str "node_cpu_sec.+|node_load[0-9]+|node_memory_Buf.*|node_memory_Mem.*|"
"node_memory_Cached.*|node_disk_[r,w,i].*|node_filesystem_[s,a].*|"
"node_network_receive_bytes_total|node_network_transmit_bytes_total")
:traefik-regex (str "traefik_entrypoint_.*_total|"
"traefik_entrypoint_.*_seconds_count|"
"traefik_router_.*_total|"
"traefik_router_.*_seconds_count|"
"traefik_service_.*_total|"
"traefik_service_.*_seconds_count|"
"traefik_tls_certs_not_after")
:kube-state-regex (str "kube_pod_container_status_restarts_total|"
"kube_pod_status_reason|kube_node_status_capacity|kube_node_status_allocatable|"
"kube_cronjob_status_active|kube_job_status_failed")})
(def filter-regex-string
(str/join "|" (vals metric-regex)))
(def filter-regex-string int/filter-regex-string)
#?(:cljs
(defmethod yaml/load-resource :monitoring [resource-name]
(case resource-name
"monitoring/namespace.yaml" (rc/inline "monitoring/namespace.yaml")
"monitoring/kube-state-metrics/cluster-role-binding.yaml" (rc/inline "monitoring/kube-state-metrics/cluster-role-binding.yaml")
"monitoring/kube-state-metrics/cluster-role.yaml" (rc/inline "monitoring/kube-state-metrics/cluster-role.yaml")
"monitoring/kube-state-metrics/deployment.yaml" (rc/inline "monitoring/kube-state-metrics/deployment.yaml")
"monitoring/kube-state-metrics/service-account.yaml" (rc/inline "monitoring/kube-state-metrics/service-account.yaml")
"monitoring/kube-state-metrics/service.yaml" (rc/inline "monitoring/kube-state-metrics/service.yaml")
"monitoring/node-exporter/cluster-role-binding.yaml" (rc/inline "monitoring/node-exporter/cluster-role-binding.yaml")
"monitoring/node-exporter/cluster-role.yaml" (rc/inline "monitoring/node-exporter/cluster-role.yaml")
"monitoring/node-exporter/daemon-set.yaml" (rc/inline "monitoring/node-exporter/daemon-set.yaml")
"monitoring/node-exporter/service-account.yaml" (rc/inline "monitoring/node-exporter/service-account.yaml")
"monitoring/node-exporter/service.yaml" (rc/inline "monitoring/node-exporter/service.yaml")
"monitoring/prometheus/cluster-role-binding.yaml" (rc/inline "monitoring/prometheus/cluster-role-binding.yaml")
"monitoring/prometheus/cluster-role.yaml" (rc/inline "monitoring/prometheus/cluster-role.yaml")
"monitoring/prometheus/config.yaml" (rc/inline "monitoring/prometheus/config.yaml")
"monitoring/prometheus/deployment.yaml" (rc/inline "monitoring/prometheus/deployment.yaml")
"monitoring/prometheus/prometheus.yaml" (rc/inline "monitoring/prometheus/prometheus.yaml")
"monitoring/prometheus/service-account.yaml" (rc/inline "monitoring/prometheus/service-account.yaml")
"monitoring/prometheus/service.yaml" (rc/inline "monitoring/prometheus/service.yaml")
(throw (js/Error. "Undefined Resource!")))))
(defn-spec generate-stateful-set cp/map-or-seq?
[config ::storage]
(let [{:keys [pvc-storage-class-name]
:or {pvc-storage-class-name :manual}} config]
(->
(yaml/load-as-edn "monitoring/stateful-set.yaml")
(assoc-in [:spec :volumeClaimTemplates 0 :spec :storageClassName] (name pvc-storage-class-name)))))
(defn-spec generate-prometheus-config cp/map-or-seq?
[config ::mon-cfg
auth ::mon-auth]
(let [{:keys [grafana-cloud-url cluster-name cluster-stage]} config
{:keys [grafana-cloud-user grafana-cloud-password]} auth]
(->
(yaml/load-as-edn "monitoring/prometheus/prometheus.yaml")
(assoc-in [:global :external_labels :cluster]
cluster-name)
(assoc-in [:global :external_labels :stage]
cluster-stage)
(assoc-in [:remote_write 0 :url]
grafana-cloud-url)
(assoc-in [:remote_write 0 :basic_auth :username]
grafana-cloud-user)
(assoc-in [:remote_write 0 :basic_auth :password]
grafana-cloud-password)
(cm/replace-all-matching-values-by-new-value "FILTER_REGEX" filter-regex-string))))
(defn-spec generate-config cp/map-or-seq?
[config ::mon-cfg
auth ::mon-auth]
(->
(yaml/load-as-edn "monitoring/prometheus/config.yaml")
(assoc-in [:stringData :prometheus.yaml]
(yaml/to-string
(generate-prometheus-config config auth)))))
(defn-spec generate cp/map-or-seq?
(defn-spec ^{:deprecated "6.4.1"} generate seq?
"use generate-config and generate-auth instead"
[config ::mon-cfg
auth ::mon-auth]
[(yaml/load-as-edn "monitoring/namespace.yaml")
(yaml/load-as-edn "monitoring/prometheus/cluster-role.yaml")
(yaml/load-as-edn "monitoring/prometheus/cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/prometheus/service.yaml")
(yaml/load-as-edn "monitoring/prometheus/service-account.yaml")
(generate-config config auth)
(yaml/load-as-edn "monitoring/prometheus/deployment.yaml")
(yaml/load-as-edn "monitoring/node-exporter/service-account.yaml")
(yaml/load-as-edn "monitoring/node-exporter/cluster-role.yaml")
(yaml/load-as-edn "monitoring/node-exporter/cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/node-exporter/daemon-set.yaml")
(yaml/load-as-edn "monitoring/node-exporter/service.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics/cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics/cluster-role.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics/deployment.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics/service-account.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics/service.yaml")])
(yaml/load-as-edn "monitoring/prometheus-cluster-role.yaml")
(yaml/load-as-edn "monitoring/prometheus-cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/prometheus-service.yaml")
(yaml/load-as-edn "monitoring/prometheus-service-account.yaml")
(int/generate-config-secret config auth)
(yaml/load-as-edn "monitoring/prometheus-deployment.yaml")
(yaml/load-as-edn "monitoring/node-exporter-service-account.yaml")
(yaml/load-as-edn "monitoring/node-exporter-cluster-role.yaml")
(yaml/load-as-edn "monitoring/node-exporter-cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/node-exporter-daemon-set.yaml")
(yaml/load-as-edn "monitoring/node-exporter-service.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-cluster-role.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-deployment.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-service-account.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-service.yaml")])
(defn-spec generate-config seq?
[]
[(yaml/load-as-edn "monitoring/namespace.yaml")
(yaml/load-as-edn "monitoring/prometheus-cluster-role.yaml")
(yaml/load-as-edn "monitoring/prometheus-cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/prometheus-service.yaml")
(yaml/load-as-edn "monitoring/prometheus-service-account.yaml")
(yaml/load-as-edn "monitoring/prometheus-deployment.yaml")
(yaml/load-as-edn "monitoring/node-exporter-service-account.yaml")
(yaml/load-as-edn "monitoring/node-exporter-cluster-role.yaml")
(yaml/load-as-edn "monitoring/node-exporter-cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/node-exporter-daemon-set.yaml")
(yaml/load-as-edn "monitoring/node-exporter-service.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-cluster-role-binding.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-cluster-role.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-deployment.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-service-account.yaml")
(yaml/load-as-edn "monitoring/kube-state-metrics-service.yaml")])
(defn-spec generate-auth seq?
[config ::mon-cfg
auth ::mon-auth]
[(int/generate-config-secret config auth)])

View file

@ -0,0 +1,102 @@
(ns dda.c4k-common.monitoring.monitoring-internal
(:require
[clojure.spec.alpha :as s]
#?(:cljs [shadow.resource :as rc])
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.predicate :as cp]
[dda.c4k-common.common :as cm]
[clojure.string :as str]))
#?(:cljs
(defmethod yaml/load-resource :monitoring [resource-name]
(case resource-name
"monitoring/kube-state-metrics-cluster-role-binding.yaml" (rc/inline "monitoring/kube-state-metrics-cluster-role-binding.yaml")
"monitoring/kube-state-metrics-cluster-role.yaml" (rc/inline "monitoring/kube-state-metrics-cluster-role.yaml")
"monitoring/kube-state-metrics-deployment.yaml" (rc/inline "monitoring/kube-state-metrics-deployment.yaml")
"monitoring/kube-state-metrics-service-account.yaml" (rc/inline "monitoring/kube-state-metrics-service-account.yaml")
"monitoring/kube-state-metrics-service.yaml" (rc/inline "monitoring/kube-state-metrics-service.yaml")
"monitoring/namespace.yaml" (rc/inline "monitoring/namespace.yaml")
"monitoring/node-exporter-cluster-role-binding.yaml" (rc/inline "monitoring/node-exporter-cluster-role-binding.yaml")
"monitoring/node-exporter-cluster-role.yaml" (rc/inline "monitoring/node-exporter-cluster-role.yaml")
"monitoring/node-exporter-daemon-set.yaml" (rc/inline "monitoring/node-exporter-daemon-set.yaml")
"monitoring/node-exporter-service-account.yaml" (rc/inline "monitoring/node-exporter-service-account.yaml")
"monitoring/node-exporter-service.yaml" (rc/inline "monitoring/node-exporter-service.yaml")
"monitoring/prometheus-cluster-role-binding.yaml" (rc/inline "monitoring/prometheus-cluster-role-binding.yaml")
"monitoring/prometheus-cluster-role.yaml" (rc/inline "monitoring/prometheus-cluster-role.yaml")
"monitoring/prometheus-config-secret.yaml" (rc/inline "monitoring/prometheus-config-secret.yaml")
"monitoring/prometheus-deployment.yaml" (rc/inline "monitoring/prometheus-deployment.yaml")
"monitoring/prometheus-prometheus.yaml" (rc/inline "monitoring/prometheus-prometheus.yaml")
"monitoring/prometheus-service-account.yaml" (rc/inline "monitoring/prometheus-service-account.yaml")
"monitoring/prometheus-service.yaml" (rc/inline "monitoring/prometheus-service.yaml")
(throw (js/Error. (str "Undefined Resource: " resource-name))))))
(s/def ::grafana-cloud-user cp/bash-env-string?)
(s/def ::grafana-cloud-password cp/bash-env-string?)
(s/def ::grafana-cloud-url string?)
(s/def ::cluster-name string?)
(s/def ::cluster-stage cp/stage?)
(s/def ::mon-cfg (s/keys :req-un [::grafana-cloud-url
::cluster-name
::cluster-stage]))
(s/def ::mon-auth (s/keys :req-un [::grafana-cloud-user
::grafana-cloud-password]))
(def metric-regex {:node-regex
(str "node_cpu_sec.+|node_load[0-9]+|node_memory_Buf.*|node_memory_Mem.*|"
"node_memory_Cached.*|node_disk_[r,w,i].*|node_filesystem_[s,a].*|"
"node_network_receive_bytes_total|node_network_transmit_bytes_total")
:traefik-regex (str "traefik_entrypoint_.*_total|"
"traefik_entrypoint_.*_seconds_count|"
"traefik_router_.*_total|"
"traefik_router_.*_seconds_count|"
"traefik_service_.*_total|"
"traefik_service_.*_seconds_count|"
"traefik_tls_certs_not_after")
:kube-state-regex (str "kube_pod_container_status_restarts_total|"
"kube_pod_status_reason|kube_node_status_capacity|kube_node_status_allocatable|"
"kube_cronjob_status_active|kube_job_status_failed")})
(def filter-regex-string
(str/join "|" (vals metric-regex)))
(defn-spec generate-prometheus-config map?
[config ::mon-cfg
auth ::mon-auth]
(let [{:keys [grafana-cloud-url cluster-name cluster-stage]} config
{:keys [grafana-cloud-user grafana-cloud-password]} auth]
(->
(yaml/load-as-edn "monitoring/prometheus-prometheus.yaml")
(assoc-in [:global :external_labels :cluster]
cluster-name)
(assoc-in [:global :external_labels :stage]
cluster-stage)
(assoc-in [:remote_write 0 :url]
grafana-cloud-url)
(assoc-in [:remote_write 0 :basic_auth :username]
grafana-cloud-user)
(assoc-in [:remote_write 0 :basic_auth :password]
grafana-cloud-password)
(cm/replace-all-matching "FILTER_REGEX" filter-regex-string))))
(defn-spec generate-config-secret map?
[config ::mon-cfg
auth ::mon-auth]
(->
(yaml/load-as-edn "monitoring/prometheus-config-secret.yaml")
(assoc-in [:stringData :prometheus.yaml]
(yaml/to-string
(generate-prometheus-config config auth)))))
(defn-spec ^{:deprecated "6.4.1"} generate-config map?
"Use generate-config-secret instead"
[config ::mon-cfg
auth ::mon-auth]
(->
(yaml/load-as-edn "monitoring/prometheus-config-secret.yaml")
(assoc-in [:stringData :prometheus.yaml]
(yaml/to-string
(generate-prometheus-config config auth)))))

View file

@ -0,0 +1,19 @@
(ns dda.c4k-common.namespace
(:require
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[clojure.spec.alpha :as s]
[dda.c4k-common.namespace.namespace-internal :as int]))
(s/def ::namespace ::int/namespace)
(def config? (s/keys :req-un []
:opt-un [::namespace]))
(def default-config {:namespace "default"})
(defn-spec generate seq?
[config config?]
(let [final-config (merge default-config
config)]
[(int/generate-namespace final-config)]))

View file

@ -0,0 +1,23 @@
(ns dda.c4k-common.namespace.namespace-internal
(:require
[clojure.spec.alpha :as s]
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.c4k-common.yaml :as yaml]
#?(:cljs [dda.c4k-common.macros :refer-macros [inline-resources]])))
#?(:cljs
(defmethod yaml/load-resource :namespace [resource-name]
(get (inline-resources "namespace") resource-name)))
(s/def ::namespace string?)
(def config? (s/keys :req-un [::namespace]
:opt-un []))
(defn-spec generate-namespace map?
[config config?]
(let [{:keys [namespace]} config]
(->
(yaml/load-as-edn "namespace/namespace.yaml")
(assoc-in [:metadata :name] namespace))))

View file

@ -1,99 +1,111 @@
(ns dda.c4k-common.postgres
(:require
[clojure.spec.alpha :as s]
#?(:cljs [shadow.resource :as rc])
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.base64 :as b64]
[dda.c4k-common.predicate :as cp]
[dda.c4k-common.common :as cm]))
[dda.c4k-common.namespace :as ns]
[dda.c4k-common.postgres.postgres-internal :as int]))
(defn postgres-size?
[input]
(contains? #{:2gb :4gb :8gb :16gb} input))
(def postgres-size? int/postgres-size?)
(defn postgres-image?
[input]
(contains? #{"postgres:13" "postgres:14"} input))
(def postgres-image? int/postgres-image?)
(s/def ::postgres-db-user cp/bash-env-string?)
(s/def ::postgres-db-password cp/bash-env-string?)
(s/def ::postgres-data-volume-path string?)
(s/def ::postgres-size postgres-size?)
(s/def ::db-name cp/bash-env-string?)
(s/def ::pvc-storage-class-name cp/pvc-storage-class-name?)
(s/def ::pv-storage-size-gb pos?)
(s/def ::postgres-db-user ::int/postgres-db-user)
(s/def ::postgres-db-password ::int/postgres-db-password)
(s/def ::postgres-data-volume-path ::int/postgres-data-volume-path)
(s/def ::postgres-size ::int/postgres-size)
(s/def ::db-name ::int/db-name)
(s/def ::pvc-storage-class-name ::int/pvc-storage-class-name)
(s/def ::pv-storage-size-gb ::int/pv-storage-size-gb)
(def pg-config?
(s/keys :opt-un [::postgres-size ::db-name ::postgres-data-volume-path
::pvc-storage-class-name ::pv-storage-size-gb]))
::pvc-storage-class-name ::pv-storage-size-gb ::ns/namespace]))
(def pg-auth?
(s/keys :opt-un [::postgres-db-user ::postgres-db-password]))
(def postgres-function (s/keys :opt-un [::deserializer ::optional]))
#?(:cljs
(defmethod yaml/load-resource :postgres [resource-name]
(case resource-name
"postgres/config-2gb.yaml" (rc/inline "postgres/config-2gb.yaml")
"postgres/config-4gb.yaml" (rc/inline "postgres/config-4gb.yaml")
"postgres/config-8gb.yaml" (rc/inline "postgres/config-8gb.yaml")
"postgres/config-16gb.yaml" (rc/inline "postgres/config-16gb.yaml")
"postgres/deployment.yaml" (rc/inline "postgres/deployment.yaml")
"postgres/persistent-volume.yaml" (rc/inline "postgres/persistent-volume.yaml")
"postgres/pvc.yaml" (rc/inline "postgres/pvc.yaml")
"postgres/secret.yaml" (rc/inline "postgres/secret.yaml")
"postgres/service.yaml" (rc/inline "postgres/service.yaml")
(throw (js/Error. "Undefined Resource!")))))
(def default-config (merge ns/default-config
{:postgres-image "postgres:13"
:postgres-size :2gb
:db-name "postgres"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 10
:pvc-storage-class-name "manual"}))
(defn-spec generate-config cp/map-or-seq?
(defn-spec generate-configmap map?
[& config (s/? pg-config?)]
(let [{:keys [postgres-size db-name]
:or {postgres-size :2gb
db-name "postgres"}} (first config)]
(->
(yaml/from-string (yaml/load-resource
(str "postgres/config-" (name postgres-size) ".yaml")))
(assoc-in [:data :postgres-db] db-name))))
(let [final-config (merge default-config
(first config))]
(int/generate-configmap final-config)))
; TODO: why do we need a sequence of configs?
(defn-spec generate-deployment cp/map-or-seq?
(defn-spec generate-deployment map?
[& config (s/? pg-config?)]
(let [{:keys [postgres-image]
:or {postgres-image "postgres:13"}} (first config)]
(->
(yaml/from-string (yaml/load-resource "postgres/deployment.yaml"))
(assoc-in [:spec :template :spec :containers 0 :image] postgres-image))))
(let [final-config (merge default-config
(first config))]
(int/generate-deployment final-config)))
(defn-spec generate-persistent-volume cp/map-or-seq?
(defn-spec generate-persistent-volume map?
[config pg-config?]
(let [{:keys [postgres-data-volume-path pv-storage-size-gb]
:or {postgres-data-volume-path "/var/postgres"
pv-storage-size-gb 10}} config]
(->
(yaml/from-string (yaml/load-resource "postgres/persistent-volume.yaml"))
(assoc-in [:spec :hostPath :path] postgres-data-volume-path)
(assoc-in [:spec :capacity :storage] (str pv-storage-size-gb "Gi")))))
(let [final-config (merge default-config
config)]
(int/generate-persistent-volume final-config)))
(defn-spec generate-pvc cp/map-or-seq?
(defn-spec generate-pvc map?
[config pg-config?]
(let [{:keys [pv-storage-size-gb pvc-storage-class-name]
:or {pv-storage-size-gb 10
pvc-storage-class-name "manual"}} config]
(->
(yaml/from-string (yaml/load-resource "postgres/pvc.yaml"))
(assoc-in [:spec :resources :requests :storage] (str pv-storage-size-gb "Gi"))
(assoc-in [:spec :storageClassName] (name pvc-storage-class-name)))))
(let [final-config (merge default-config
config)]
(int/generate-pvc final-config)))
(defn-spec generate-secret cp/map-or-seq?
[my-auth any?]
(let [{:keys [postgres-db-user postgres-db-password]} my-auth]
(->
(yaml/from-string (yaml/load-resource "postgres/secret.yaml"))
(cm/replace-key-value :postgres-user (b64/encode postgres-db-user))
(cm/replace-key-value :postgres-password (b64/encode postgres-db-password)))))
(defn-spec generate-service cp/map-or-seq?
[]
(yaml/from-string (yaml/load-resource "postgres/service.yaml")))
(defn-spec generate-secret map?
([auth pg-auth?]
(let [final-config default-config]
(int/generate-secret final-config auth)))
([config pg-config?
auth pg-auth?]
(let [final-config (merge default-config
config)]
(int/generate-secret final-config auth))))
(defn-spec generate-service map?
[config pg-config?]
(let [final-config (merge default-config
config)]
(int/generate-service final-config)))
(defn-spec ^{:deprecated "6.4.1"} generate seq?
"use generate-config and generate-auth instead"
[config pg-config?
auth pg-auth?]
(let [final-config (merge default-config
config)]
[(int/generate-secret final-config auth)
(int/generate-persistent-volume final-config)
(int/generate-configmap final-config)
(int/generate-pvc final-config)
(int/generate-deployment final-config)
(int/generate-service final-config)]))
(defn-spec generate-config seq?
[config pg-config?]
(let [final-config (merge default-config
config)]
[(int/generate-persistent-volume final-config)
(int/generate-configmap final-config)
(int/generate-pvc final-config)
(int/generate-deployment final-config)
(int/generate-service final-config)]))
(defn-spec generate-auth seq?
[config pg-config?
auth pg-auth?]
(let [final-config (merge default-config
config)]
[(int/generate-secret final-config auth)]))

View file

@ -0,0 +1,121 @@
(ns dda.c4k-common.postgres.postgres-internal
(:require
[clojure.spec.alpha :as s]
#?(:cljs [shadow.resource :as rc])
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.base64 :as b64]
[dda.c4k-common.predicate :as cp]
[dda.c4k-common.common :as cm]
[dda.c4k-common.namespace :as ns]))
#?(:cljs
(defmethod yaml/load-resource :postgres [resource-name]
(case resource-name
"postgres/config-2gb.yaml" (rc/inline "postgres/config-2gb.yaml")
"postgres/config-4gb.yaml" (rc/inline "postgres/config-4gb.yaml")
"postgres/config-8gb.yaml" (rc/inline "postgres/config-8gb.yaml")
"postgres/config-16gb.yaml" (rc/inline "postgres/config-16gb.yaml")
"postgres/deployment.yaml" (rc/inline "postgres/deployment.yaml")
"postgres/persistent-volume.yaml" (rc/inline "postgres/persistent-volume.yaml")
"postgres/pvc.yaml" (rc/inline "postgres/pvc.yaml")
"postgres/secret.yaml" (rc/inline "postgres/secret.yaml")
"postgres/service.yaml" (rc/inline "postgres/service.yaml")
(throw (js/Error. (str "Undefined Resource: " resource-name))))))
(defn postgres-size?
[input]
(contains? #{:2gb :4gb :8gb :16gb} input))
(defn postgres-image?
[input]
(contains? #{"postgres:13" "postgres:14" "postgres:15" "postgres:16"} input))
(s/def ::postgres-db-user cp/bash-env-string?)
(s/def ::postgres-db-password cp/bash-env-string?)
(s/def ::postgres-data-volume-path string?)
(s/def ::postgres-size postgres-size?)
(s/def ::db-name cp/bash-env-string?)
(s/def ::pvc-storage-class-name cp/pvc-storage-class-name?)
(s/def ::pv-storage-size-gb pos?)
(def pg-config?
(s/keys :req-un [::postgres-size ::db-name ::postgres-data-volume-path
::pvc-storage-class-name ::pv-storage-size-gb ::ns/namespace]))
(def pg-auth?
(s/keys :req-un [::postgres-db-user ::postgres-db-password]))
(def postgres-function (s/keys :opt-un [::deserializer ::optional]))
(defn-spec generate-configmap map?
[config pg-config?]
(let [{:keys [postgres-size db-name namespace]} config]
(->
(yaml/from-string (yaml/load-resource
(str "postgres/config-" (name postgres-size) ".yaml")))
(assoc-in [:metadata :namespace] namespace)
(assoc-in [:data :postgres-db] db-name))))
(defn-spec ^{:deprecated "6.4.1"} generate-config map?
"use generate-configmap instead"
[config pg-config?]
(let [{:keys [postgres-size db-name namespace]} config]
(->
(yaml/from-string (yaml/load-resource
(str "postgres/config-" (name postgres-size) ".yaml")))
(assoc-in [:metadata :namespace] namespace)
(assoc-in [:data :postgres-db] db-name))))
(defn-spec generate-deployment map?
[config pg-config?]
(let [{:keys [postgres-image namespace]} config]
(->
(yaml/from-string (yaml/load-resource "postgres/deployment.yaml"))
(assoc-in [:metadata :namespace] namespace)
(assoc-in [:spec :template :spec :containers 0 :image] postgres-image))))
(defn-spec generate-persistent-volume map?
[config pg-config?]
(let [{:keys [postgres-data-volume-path pv-storage-size-gb namespace]} config]
(->
(yaml/from-string (yaml/load-resource "postgres/persistent-volume.yaml"))
(assoc-in [:metadata :namespace] namespace)
(assoc-in [:spec :hostPath :path] postgres-data-volume-path)
(assoc-in [:spec :capacity :storage] (str pv-storage-size-gb "Gi")))))
(defn-spec generate-pvc map?
[config pg-config?]
(let [{:keys [pv-storage-size-gb pvc-storage-class-name namespace]} config]
(->
(yaml/from-string (yaml/load-resource "postgres/pvc.yaml"))
(assoc-in [:metadata :namespace] namespace)
(assoc-in [:spec :resources :requests :storage] (str pv-storage-size-gb "Gi"))
(assoc-in [:spec :storageClassName] (name pvc-storage-class-name)))))
(defn-spec generate-secret map?
[config pg-config?
auth pg-auth?]
(let [{:keys [namespace]} config
{:keys [postgres-db-user postgres-db-password]} auth]
(->
(yaml/from-string (yaml/load-resource "postgres/secret.yaml"))
(assoc-in [:metadata :namespace] namespace)
(cm/replace-key-value :postgres-user (b64/encode postgres-db-user))
(cm/replace-key-value :postgres-password (b64/encode postgres-db-password)))))
(defn-spec generate-service map?
[config pg-config?]
(let [{:keys [namespace]} config]
(->
(yaml/from-string (yaml/load-resource "postgres/service.yaml"))
(assoc-in [:metadata :namespace] namespace))))

View file

@ -14,6 +14,16 @@
(and (string? input)
(some? (re-matches #"(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)" input))))
(defn ipv4-string?
[input]
(and (string? input)
(some? (re-matches #"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$" input))))
(defn ipv6-string?
[input]
(and (string? input)
(some? (re-matches #"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" input))))
(defn string-of-separated-by?
[spec-function separator input]
(every? true? (map spec-function (str/split input separator))))
@ -33,7 +43,7 @@
(defn pvc-storage-class-name?
[input]
(contains? #{"manual" "local-path"} input))
(contains? #{"manual" "local-path" "hcloud-volumes" "hcloud-volumes-encrypted"} input))
(defn port-number?
[input]

View file

@ -21,20 +21,27 @@
(s/def ::deserializer fn?)
(s/def ::optional boolean?)
(def dom-function-parameter (s/keys :opt-un [::deserializer ::optional]))
(defn-spec get-content-value-from-element js-object?
[name string?]
(-> (get-element-by-id name)
(.-value)))
(defn-spec deserialize-content js-object?
[content string?
deserializer ::deserializer
optional ::optional]
(cond
(and optional (st/blank? content))
nil
:else
(apply deserializer [content])))
(defn-spec get-content-from-element js-object?
[name string?
& {:keys [deserializer optional]
:or {deserializer nil optional false}} dom-function-parameter]
(let [content (-> (get-element-by-id name)
(.-value))]
(cond
(and optional (some? deserializer))
(when-not (st/blank? content)
(apply deserializer [content]))
(and (false? optional) (some? deserializer))
(apply deserializer [content])
:else
content)))
:or {deserializer identity optional false}} dom-function-parameter]
(-> (get-content-value-from-element name)
(deserialize-content deserializer optional)))
(defn-spec set-validation-result! js-object?
[name string?
@ -50,7 +57,7 @@
[name string?
spec js-object?
& {:keys [deserializer optional]
:or {deserializer nil optional false}} dom-function-parameter]
:or {deserializer identity optional false}} dom-function-parameter]
(let [content (get-content-from-element name :optional optional :deserializer deserializer)]
(if (or (and optional (st/blank? content))
(s/valid? spec content))

View file

@ -0,0 +1,2 @@
(ns dda.c4k-common.macros
(:require-macros [dda.c4k-common.macros]))

View file

@ -3,7 +3,6 @@
["js-yaml" :as yaml]
[clojure.string :as st]
[orchestra.core :refer-macros [defn-spec]]
[shadow.resource :as rc]
[dda.c4k-common.predicate :as cp]))
(defn string-or-keyword? [input]

View file

@ -1,10 +1,7 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: c4k-common-https-ingress
namespace: default
labels:
app.kubernetes.part-of: c4k-common-app
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web, websecure
traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd

View file

@ -0,0 +1,9 @@
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: ratelimit
namespace: default
spec:
rateLimit:
average: AVG
burst: BRS

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: default

View file

@ -0,0 +1,21 @@
(ns dda.c4k-common.macros-test
(:require
[clojure.test :refer [deftest is are testing run-tests]]
[dda.c4k-common.macros :as cut :refer [inline-resources]]))
(deftest should-count-inline-resources
(is (= 3 (count (inline-resources "dda/c4k_common/inline_resources_test")))))
(deftest should-inline-resources
(let [resource-path (fn [name] (str "dda/c4k_common/inline_resources_test/" name))
inlined-resources (inline-resources "dda/c4k_common/inline_resources_test")]
(is (= "1" (get inlined-resources (resource-path "inline_resource_1.yaml"))))
(is (= "2" (get inlined-resources (resource-path "inline_resource_2.yaml"))))
(is (= "3" (get inlined-resources (resource-path "inline_resource_3.yaml"))))))
(deftest should-inline-jar-resources
(let [jar-url (java.net.URL. "jar:file:./src/test/resources/dda/c4k_common/inline_jar_test/test.jar!/inline_resources_test/")
inlined-resources (cut/inline-resource-jar jar-url)]
(is (= "1" (get inlined-resources "inline_resources_test/inline_resource_1.yaml")))
(is (= "2" (get inlined-resources "inline_resources_test/inline_resource_2.yaml")))
(is (= "3" (get inlined-resources "inline_resources_test/inline_resource_3.yaml")))))

View file

@ -1,8 +1,8 @@
(ns dda.c4k-common.monitoring-regex-test
(ns dda.c4k-common.monitoring.monitoring-regex-test
(:require
[clojure.test :refer [deftest is are testing run-tests]]
[data-test :refer :all]
[dda.c4k-common.monitoring :as cut]))
[dda.c4k-common.monitoring.monitoring-internal :as cut]))
(defn filter-by-regex
[regex-str collection]

View file

@ -0,0 +1,22 @@
(ns dda.c4k-common.core-test
(:require
#?(:cljs [shadow.resource :as rc])
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[clojure.spec.alpha :as s]
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.core :as cut]))
#?(:cljs
(defmethod yaml/load-resource :common-test [resource-name]
(case resource-name
"common-test/valid-auth.yaml" (rc/inline "common-test/valid-auth.yaml")
"common-test/valid-config.yaml" (rc/inline "common-test/valid-config.yaml")
(throw (js/Error. "Undefined Resource!")))))
(def conf (yaml/load-as-edn "common-test/valid-config.yaml"))
(def auth (yaml/load-as-edn "common-test/valid-auth.yaml"))
(deftest validate-valid-resources
(is (s/valid? cut/config? conf))
(is (s/valid? cut/auth? auth)))

View file

@ -0,0 +1,135 @@
(ns dda.c4k-common.ingress.ingress-internal-test
(:require
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[clojure.spec.test.alpha :as st]
[dda.c4k-common.ingress.ingress-internal :as cut]))
(st/instrument `cut/generate-host-rule)
(st/instrument `cut/generate-certificate)
(st/instrument `cut/generate-rate-limit-middleware)
(st/instrument `cut/generate-ingress)
(deftest should-generate-rule
(is (= {:host "test.com",
:http
{:paths
[{:pathType "Prefix",
:path "/",
:backend
{:service {:name "myservice", :port {:number 3000}}}}]}}
(cut/generate-host-rule "myservice" 3000 "test.com"))))
(deftest should-generate-certificate
(is (= {:apiVersion "cert-manager.io/v1",
:kind "Certificate",
:metadata {:name "test-io-cert",
:namespace "default",
:labels {:app.kubernetes.part-of "c4k-common-app"}},
:spec
{:secretName "test-io-cert",
:commonName "test.de",
:duration "2160h",
:renewBefore "720h",
:dnsNames ["test.de" "test.org" "www.test.de" "www.test.org"],
:issuerRef {:name "prod", :kind "ClusterIssuer"}}}
(cut/generate-certificate {:fqdns ["test.de" "test.org" "www.test.de" "www.test.org"]
:app-name "c4k-common-app"
:cert-name "test-io-cert"
:issuer "prod"
:namespace "default"})))
(is (= {:apiVersion "cert-manager.io/v1",
:kind "Certificate",
:metadata {:name "test-io-cert",
:namespace "myapp",
:labels {:app.kubernetes.part-of "c4k-common-app"}},
:spec
{:secretName "test-io-cert",
:commonName "test.de",
:duration "2160h",
:renewBefore "720h",
:dnsNames ["test.de" "test.org" "www.test.de" "www.test.org"],
:issuerRef {:name "prod", :kind "ClusterIssuer"}}}
(cut/generate-certificate {:fqdns ["test.de" "test.org" "www.test.de" "www.test.org"]
:app-name "c4k-common-app"
:cert-name "test-io-cert"
:issuer "prod"
:namespace "myapp"}))))
(deftest should-generate-middleware-ratelimit
(is (= {:apiVersion "traefik.containo.us/v1alpha1",
:kind "Middleware",
:metadata {:name "normal-ratelimit"
:namespace "myapp",},
:spec {:rateLimit {:average 10, :burst 5}}}
(cut/generate-rate-limit-middleware {:rate-limit-name "normal"
:namespace "myapp"
:average-rate 10, :burst-rate 5}))))
(deftest should-generate-ingress
(is (= {:apiVersion "networking.k8s.io/v1",
:kind "Ingress",
:metadata
{:namespace "myapp",
:name "test-io-https-ingress",
:labels {:app.kubernetes.part-of "c4k-common-app"},
:annotations {:traefik.ingress.kubernetes.io/router.entrypoints
"web, websecure"
:traefik.ingress.kubernetes.io/router.middlewares
"default-redirect-https@kubernetescrd"
:metallb.universe.tf/address-pool "public"}}}
(dissoc (cut/generate-ingress
{:ingress-name "test-io-https-ingress"
:app-name "c4k-common-app"
:namespace "myapp"
:service-name "test-io-service" :service-port 80
:issuer "prod" :cert-name "noname"
:fqdns ["test.de" "www.test.de" "test-it.de"
"www.test-it.de"]}) :spec)))
(is (= {:name "test-io-https-ingress",
:namespace "default",
:labels {:app.kubernetes.part-of "c4k-common-app"},
:annotations {:traefik.ingress.kubernetes.io/router.entrypoints
"web, websecure"
:traefik.ingress.kubernetes.io/router.middlewares
"default-redirect-https@kubernetescrd, default-normal-ratelimit@kubernetescrd",
:metallb.universe.tf/address-pool "public"}}
(:metadata (cut/generate-ingress
{
:ingress-name "test-io-https-ingress"
:app-name "c4k-common-app"
:namespace "default"
:service-name "test-io-service" :service-port 80
:rate-limit-name "normal"
:issuer "prod" :cert-name "noname"
:fqdns ["test.de"]}))))
(is (= {:tls
[{:hosts
["test.de" "www.test.de" "test-it.de" "www.test-it.de"],
:secretName "test-io-cert"}]
:rules
[{:host "test.de",
:http
{:paths [{:pathType "Prefix", :path "/", :backend {:service {:name "test-io-service", :port {:number 80}}}}]}}
{:host "www.test.de",
:http
{:paths [{:pathType "Prefix", :path "/", :backend {:service {:name "test-io-service", :port {:number 80}}}}]}}
{:host "test-it.de",
:http
{:paths [{:pathType "Prefix", :path "/", :backend {:service {:name "test-io-service", :port {:number 80}}}}]}}
{:host "www.test-it.de",
:http
{:paths [{:pathType "Prefix", :path "/", :backend {:service {:name "test-io-service", :port {:number 80}}}}]}}]}
(:spec (cut/generate-ingress {
:ingress-name "test-io-https-ingress"
:app-name "c4k-common-app"
:namespace "default"
:service-name "test-io-service" :service-port 80
:issuer "prod" :cert-name "test-io-cert"
:fqdns ["test.de" "www.test.de"
"test-it.de"
"www.test-it.de"]})))))

View file

@ -5,67 +5,10 @@
[clojure.spec.test.alpha :as st]
[dda.c4k-common.ingress :as cut]))
(st/instrument `cut/generate-host-rule)
(st/instrument `cut/generate-ingress)
(st/instrument `cut/generate-certificate)
(st/instrument `cut/generate-ingress-and-cert)
(deftest should-generate-rule
(is (= {:host "test.com",
:http
{:paths
[{:pathType "Prefix",
:path "/",
:backend
{:service {:name "myservice", :port {:number 3000}}}}]}}
(cut/generate-host-rule "myservice" 3000 "test.com"))))
(deftest should-generate-ingress
(is (= {:apiVersion "networking.k8s.io/v1",
:kind "Ingress",
:metadata
{:name "test-io-https-ingress",
:namespace "default",
:labels {:app.kubernetes.part-of "c4k-common-app"},
:annotations {:traefik.ingress.kubernetes.io/router.entrypoints
"web, websecure"
:traefik.ingress.kubernetes.io/router.middlewares
"default-redirect-https@kubernetescrd"
:metallb.universe.tf/address-pool "public"}}}
(dissoc (cut/generate-ingress
{:issuer "prod"
:service-name "test-io-service"
:app-name "c4k-common-app"
:service-port 80
:ingress-name "test-io-https-ingress"
:fqdns ["test.de" "www.test.de" "test-it.de"
"www.test-it.de"]}) :spec)))
(is (= {:tls
[{:hosts
["test.de" "www.test.de" "test-it.de" "www.test-it.de"],
:secretName "test-io-cert"}]
:rules
[{:host "test.de",
:http
{:paths [{:pathType "Prefix", :path "/", :backend {:service {:name "test-io-service", :port {:number 80}}}}]}}
{:host "www.test.de",
:http
{:paths [{:pathType "Prefix", :path "/", :backend {:service {:name "test-io-service", :port {:number 80}}}}]}}
{:host "test-it.de",
:http
{:paths [{:pathType "Prefix", :path "/", :backend {:service {:name "test-io-service", :port {:number 80}}}}]}}
{:host "www.test-it.de",
:http
{:paths [{:pathType "Prefix", :path "/", :backend {:service {:name "test-io-service", :port {:number 80}}}}]}}]}
(:spec (cut/generate-ingress {:issuer "prod"
:app-name "c4k-common-app"
:service-name "test-io-service"
:service-port 80
:ingress-name "test-io-https-ingress"
:cert-name "test-io-cert"
:fqdns ["test.de" "www.test.de"
"test-it.de"
"www.test-it.de"]})))))
(st/instrument `cut/generate-simple-ingress)
(deftest should-generate-certificate
(is (= {:apiVersion "cert-manager.io/v1",
@ -78,12 +21,29 @@
:commonName "test.de",
:duration "2160h",
:renewBefore "720h",
:dnsNames ["test.de" "test.org" "www.test.de" "www.test.org"],
:issuerRef {:name "prod", :kind "ClusterIssuer"}}}
(cut/generate-certificate {:fqdns ["test.de" "test.org" "www.test.de" "www.test.org"]
:dnsNames ["test.de"],
:issuerRef {:name "staging", :kind "ClusterIssuer"}}}
(cut/generate-certificate {:fqdns ["test.de"]
:app-name "c4k-common-app"
:cert-name "test-io-cert"
:issuer "prod"}))))
:cert-name "test-io-cert"}))))
(deftest should-generate-ingress
(is (= {:name "test-io-https-ingress",
:namespace "default",
:labels {:app.kubernetes.part-of "c4k-common-app"},
:annotations {:traefik.ingress.kubernetes.io/router.entrypoints
"web, websecure"
:traefik.ingress.kubernetes.io/router.middlewares
"default-redirect-https@kubernetescrd",
:metallb.universe.tf/address-pool "public"}}
(:metadata (cut/generate-ingress
{:ingress-name "test-io-https-ingress"
:app-name "c4k-common-app"
:service-name "test-io-service" :service-port 80
:cert-name "myCert"
:fqdns ["test.de"]})))))
(deftest should-generate-ingress-and-cert
(is (= [{:apiVersion "cert-manager.io/v1",
@ -122,3 +82,46 @@
(cut/generate-ingress-and-cert {:fqdns ["test.jit.si"]
:service-name "web"
:service-port 80}))))
(deftest should-generate-simple-ingress
(is (= [{:apiVersion "cert-manager.io/v1",
:kind "Certificate",
:metadata
{:name "web",
:labels {:app.kubernetes.part-of "web"},
:namespace "default"},
:spec
{:secretName "web",
:commonName "test.jit.si",
:duration "2160h",
:renewBefore "720h",
:dnsNames ["test.jit.si"],
:issuerRef {:name "staging", :kind "ClusterIssuer"}}}
{:apiVersion "traefik.containo.us/v1alpha1",
:kind "Middleware",
:metadata {:name "web-ratelimit"
:namespace "default"},
:spec {:rateLimit {:average 10, :burst 20}}}
{:apiVersion "networking.k8s.io/v1",
:kind "Ingress",
:metadata
{:name "web",
:namespace "default",
:labels {:app.kubernetes.part-of "web"},
:annotations
{:traefik.ingress.kubernetes.io/router.entrypoints "web, websecure",
:traefik.ingress.kubernetes.io/router.middlewares
"default-redirect-https@kubernetescrd, default-web-ratelimit@kubernetescrd",
:metallb.universe.tf/address-pool "public"}},
:spec
{:tls [{:hosts ["test.jit.si"], :secretName "web"}],
:rules
[{:host "test.jit.si",
:http {:paths [{:path "/",
:pathType "Prefix",
:backend
{:service {:name "web",
:port {:number 80}}}}]}}]}}]
(cut/generate-simple-ingress {:fqdns ["test.jit.si"]
:service-name "web"
:service-port 80}))))

View file

@ -0,0 +1,59 @@
(ns dda.c4k-common.monitoring.monitoring-internal-test
(:require
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[clojure.string :as str]
[clojure.spec.test.alpha :as st]
[dda.c4k-common.monitoring.monitoring-internal :as cut]))
(st/instrument `cut/generate-stateful-set)
(st/instrument `cut/generate-agent-config)
(st/instrument `cut/generate-config-secret)
(def conf {:cluster-name "clustername"
:cluster-stage "test"
:grafana-cloud-url "https://some.url/with/path"})
(def auth {:grafana-cloud-user "user"
:grafana-cloud-password "password"
:hetzner-cloud-ro-token "ro-token"})
(def invalid-conf {:cluster-name "clustername"
:cluster-stage "test"
:grafana-clud-url "https://some.url/with/path"})
(def invalid-auth {:grafana-cloud-user "user"
:grafana-clod-password "password"
:hetzner-cloud-ro-token "ro-token"})
(deftest should-not-generate-config
(is (thrown?
#?(:clj Exception :cljs js/Error)
(cut/generate-config-secret invalid-conf auth))))
(deftest should-not-generate-auth
(is (thrown?
#?(:clj Exception :cljs js/Error)
(cut/generate-config-secret conf invalid-auth))))
(deftest should-generate-prometheus-remote-write-auth
(is (= {:username "user",
:password "password"}
(get-in
(cut/generate-prometheus-config conf auth)
[:remote_write 0 :basic_auth]))))
(deftest should-generate-prometheus-external-labels
(is (= {:cluster "clustername",
:stage "test"}
(get-in
(cut/generate-prometheus-config conf auth)
[:global :external_labels]))))
(deftest should-generate-config
(is (str/starts-with?
(get-in
(cut/generate-config-secret conf auth)
[:stringData :prometheus.yaml])
"global:\n scrape_interval:")))

View file

@ -2,16 +2,12 @@
(:require
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[clojure.string :as s]
[clojure.spec.test.alpha :as st]
[dda.c4k-common.monitoring :as cut]
[dda.c4k-common.yaml :as yaml]
[clojure.string :as str]))
[dda.c4k-common.monitoring :as cut]))
(st/instrument `cut/generate)
(st/instrument `cut/generate-stateful-set)
(st/instrument `cut/generate-agent-config)
(st/instrument `cut/generate-config)
(st/instrument `cut/generate-auth)
(def conf {:cluster-name "clustername"
:cluster-stage "test"
@ -21,45 +17,11 @@
:grafana-cloud-password "password"
:hetzner-cloud-ro-token "ro-token"})
(def invalid-conf {:cluster-name "clustername"
:cluster-stage "test"
:grafana-clud-url "https://some.url/with/path"})
(def invalid-auth {:grafana-cloud-user "user"
:grafana-clod-password "password"
:hetzner-cloud-ro-token "ro-token"})
(deftest should-not-generate-config
(is (thrown?
#?(:clj Exception :cljs js/Error)
(cut/generate-config invalid-conf auth))))
(deftest should-not-generate-auth
(is (thrown?
#?(:clj Exception :cljs js/Error)
(cut/generate-config conf invalid-auth))))
(deftest should-generate
(is (= 17
(count (cut/generate conf auth)))))
(deftest should-generate-prometheus-remote-write-auth
(is (= {:username "user",
:password "password"}
(get-in
(cut/generate-prometheus-config conf auth)
[:remote_write 0 :basic_auth]))))
(deftest should-generate-prometheus-external-labels
(is (= {:cluster "clustername",
:stage "test"}
(get-in
(cut/generate-prometheus-config conf auth)
[:global :external_labels]))))
(deftest should-generate-config
(is (s/starts-with?
(get-in
(cut/generate-config conf auth)
[:stringData :prometheus.yaml])
"global:\n scrape_interval:")))
(count (cut/generate conf auth))))
(is (= 16
(count (cut/generate-config))))
(is (= 1
(count (cut/generate-auth conf auth)))))

View file

@ -0,0 +1,18 @@
(ns dda.c4k-common.namespace-test
(:require
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[clojure.spec.test.alpha :as st]
[dda.c4k-common.namespace :as cut]))
(st/instrument `cut/generate)
(deftest should-generate-simple-ingress
(is (= [{:apiVersion "v1"
:kind "Namespace"
:metadata {:name "default"}}]
(cut/generate {})))
(is (= [{:apiVersion "v1"
:kind "Namespace"
:metadata {:name "myapp"}}]
(cut/generate {:namespace "myapp"}))))

View file

@ -0,0 +1,180 @@
(ns dda.c4k-common.postgres.postgres-internal-test
(:require
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[clojure.spec.test.alpha :as st]
[dda.c4k-common.postgres.postgres-internal :as cut]))
(st/instrument `cut/generate-configmap)
(st/instrument `cut/generate-deployment)
(st/instrument `cut/generate-persistent-volume)
(st/instrument `cut/generate-pvc)
(st/instrument `cut/generate-secret)
(st/instrument `cut/generate-service)
(deftest should-generate-configmap
(is (= {:name "postgres-config",
:namespace "default"
:labels {:app "postgres"}}
(:metadata (cut/generate-configmap {:postgres-image "postgres:13"
:postgres-size :2gb
:db-name "postgres"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 10
:pvc-storage-class-name "manual"
:namespace "default"}))))
(is (= {:name "postgres-config",
:namespace "myapp"
:labels {:app "postgres"}}
(:metadata (cut/generate-configmap {:postgres-image "postgres:13"
:postgres-size :2gb
:db-name "postgres"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 10
:pvc-storage-class-name "manual"
:namespace "myapp"}))))
(is (= {:postgres-db "postgres"
:postgresql.conf
"max_connections = 100\nwork_mem = 4MB\nshared_buffers = 512MB\n"}
(:data (cut/generate-configmap {:postgres-image "postgres:13"
:postgres-size :2gb
:db-name "postgres"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 10
:pvc-storage-class-name "manual"
:namespace "default"}))))
(is (= {:postgres-db "postgres"
:postgresql.conf
"max_connections = 700\nwork_mem = 3MB\nshared_buffers = 2048MB\n"}
(:data (cut/generate-configmap {:postgres-image "postgres:13"
:postgres-size :8gb
:db-name "postgres"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 10
:pvc-storage-class-name "manual"
:namespace "default"}))))
(is (= {:postgres-db "test"
:postgresql.conf
"max_connections = 100\nwork_mem = 4MB\nshared_buffers = 512MB\n"}
(:data (cut/generate-configmap {:postgres-image "postgres:13"
:postgres-size :2gb
:db-name "test"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 10
:pvc-storage-class-name "manual"
:namespace "default"}))))
)
(deftest should-generate-deployment
(is (= [{:image "postgres:14"
:name "postgresql"
:env
[{:name "POSTGRES_USER"
:valueFrom
{:secretKeyRef
{:name "postgres-secret", :key "postgres-user"}}}
{:name "POSTGRES_PASSWORD"
:valueFrom
{:secretKeyRef
{:name "postgres-secret", :key "postgres-password"}}}
{:name "POSTGRES_DB"
:valueFrom
{:configMapKeyRef
{:name "postgres-config", :key "postgres-db"}}}]
:ports [{:containerPort 5432, :name "postgresql"}]
:volumeMounts
[{:name "postgres-config-volume"
:mountPath "/etc/postgresql/postgresql.conf"
:subPath "postgresql.conf"
:readOnly true}
{:name "postgre-data-volume"
:mountPath "/var/lib/postgresql/data"}]}]
(get-in (cut/generate-deployment {:postgres-image "postgres:14"
:postgres-size :2gb
:db-name "test"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 10
:pvc-storage-class-name "manual"
:namespace "default"})
[:spec :template :spec :containers])))
(is (= {:name "postgresql",
:namespace "myapp"}
(:metadata (cut/generate-deployment {:postgres-image "postgres:14"
:postgres-size :2gb
:db-name "test"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 10
:pvc-storage-class-name "manual"
:namespace "myapp"})))))
(deftest should-generate-persistent-volume
(is (= {:kind "PersistentVolume"
:apiVersion "v1"
:metadata
{:name "postgres-pv-volume",
:namespace "default"
:labels {:type "local"}}
:spec
{:storageClassName "manual"
:accessModes ["ReadWriteOnce"]
:capacity {:storage "20Gi"}
:hostPath {:path "xx"}}}
(cut/generate-persistent-volume {:postgres-image "postgres:14"
:postgres-size :2gb
:db-name "test"
:pvc-storage-class-name "manual"
:postgres-data-volume-path "xx"
:pv-storage-size-gb 20
:namespace "default"}))))
(deftest should-generate-persistent-volume-claim
(is (= {:apiVersion "v1"
:kind "PersistentVolumeClaim"
:metadata
{:name "postgres-claim",
:namespace "default"
:labels {:app "postgres"}}
:spec
{:storageClassName "local-path"
:accessModes ["ReadWriteOnce"]
:resources {:requests {:storage "20Gi"}}}}
(cut/generate-pvc {:postgres-image "postgres:13"
:postgres-size :2gb
:db-name "postgres"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 20
:pvc-storage-class-name "local-path"
:namespace "default"}))))
(deftest should-generate-secret
(is (= {:apiVersion "v1"
:kind "Secret"
:metadata {:name "postgres-secret" :namespace "default"}
:type "Opaque"
:data
{:postgres-user "eHgtdXM=", :postgres-password "eHgtcHc="}}
(cut/generate-secret {:postgres-image "postgres:13"
:postgres-size :2gb
:db-name "postgres"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 20
:pvc-storage-class-name "local-path"
:namespace "default"}
{:postgres-db-user "xx-us" :postgres-db-password "xx-pw"}))))
(deftest should-generate-service
(is (= {:name "postgresql-service" :namespace "default"}
(:metadata (cut/generate-service
{:postgres-image "postgres:13"
:postgres-size :2gb
:db-name "postgres"
:postgres-data-volume-path "/var/postgres"
:pv-storage-size-gb 20
:pvc-storage-class-name "local-path"
:namespace "default"})))))

View file

@ -3,132 +3,63 @@
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[clojure.spec.test.alpha :as st]
[dda.c4k-common.test-helper :as ct]
[dda.c4k-common.postgres :as cut]))
(st/instrument `cut/generate-config)
(st/instrument `cut/generate-deployment)
(st/instrument `cut/generate-configmap)
(st/instrument `cut/generate-persistent-volume)
(st/instrument `cut/generate-pvc)
(st/instrument `cut/generate-secret)
(st/instrument `cut/generate-service)
(st/instrument `cut/generate)
(st/instrument `cut/generate-config)
(st/instrument `cut/generate-auth)
(deftest should-generate-config
(deftest should-generate-configmap
(is (= {:postgres-db "postgres"
:postgresql.conf
"max_connections = 100\nwork_mem = 4MB\nshared_buffers = 512MB\n"}
(:data (cut/generate-config))))
(is (= {:postgres-db "postgres"
:postgresql.conf
"max_connections = 700\nwork_mem = 3MB\nshared_buffers = 2048MB\n"}
(:data (cut/generate-config {:postgres-size :8gb}))))
(is (= {:postgres-db "test"
:postgresql.conf
"max_connections = 100\nwork_mem = 4MB\nshared_buffers = 512MB\n"}
(:data (cut/generate-config {:db-name "test"}))))
)
(deftest should-generate-config-diff
(is (= {:postgres-db-c1 "postgres",
:postgres-db-c2 "test",
:postgresql.conf-c1 "max_connections = 100\nwork_mem = 4MB\nshared_buffers = 512MB\n",
:postgresql.conf-c2 "max_connections = 700\nwork_mem = 3MB\nshared_buffers = 2048MB\n"}
(ct/map-diff (cut/generate-config) (cut/generate-config {:db-name "test" :postgres-size :8gb})))))
(:data (cut/generate-configmap)))))
(deftest should-generate-persistent-volume
(is (= {:kind "PersistentVolume"
:apiVersion "v1"
:metadata
{:name "postgres-pv-volume", :labels {:type "local"}}
{:name "postgres-pv-volume", :namespace "default" :labels {:type "local"}}
:spec
{:storageClassName "manual"
:accessModes ["ReadWriteOnce"]
:capacity {:storage "10Gi"}
:hostPath {:path "xx"}}}
(cut/generate-persistent-volume {:postgres-data-volume-path "xx"})))
(is (= {:kind "PersistentVolume"
:apiVersion "v1"
:metadata
{:name "postgres-pv-volume", :labels {:type "local"}}
:spec
{:storageClassName "manual"
:accessModes ["ReadWriteOnce"]
:capacity {:storage "20Gi"}
:hostPath {:path "xx"}}}
(cut/generate-persistent-volume {:postgres-data-volume-path "xx"
:pv-storage-size-gb 20}))))
(cut/generate-persistent-volume {:postgres-data-volume-path "xx"}))))
(deftest should-generate-persistent-volume-diff
(is (= {:storage-c1 "10Gi", :storage-c2 "20Gi",
:path-c1 "/var/postgres", :path-c2 "xx"}
(ct/map-diff (cut/generate-persistent-volume {})
(cut/generate-persistent-volume {:postgres-data-volume-path "xx"
:pv-storage-size-gb 20})))))
(deftest should-generate-persistent-volume-claim
(is (= {:apiVersion "v1"
:kind "PersistentVolumeClaim"
:metadata
{:name "postgres-claim", :labels {:app "postgres"}}
{:name "postgres-claim", :namespace "default" :labels {:app "postgres"}}
:spec
{:storageClassName "manual"
:accessModes ["ReadWriteOnce"]
:resources {:requests {:storage "10Gi"}}}}
(cut/generate-pvc {})))
(is (= {:apiVersion "v1"
:kind "PersistentVolumeClaim"
:metadata
{:name "postgres-claim", :labels {:app "postgres"}}
:spec
{:storageClassName "local-path"
:accessModes ["ReadWriteOnce"]
:resources {:requests {:storage "20Gi"}}}}
(cut/generate-pvc {:pv-storage-size-gb 20
:pvc-storage-class-name "local-path"}))))
(cut/generate-pvc {}))))
(deftest should-generate-persistent-volume-claim-diff
(is (= {:storageClassName-c1 "manual", :storageClassName-c2 "local-path",
:storage-c1 "10Gi", :storage-c2 "20Gi"}
(ct/map-diff (cut/generate-pvc {})
(cut/generate-pvc {:pv-storage-size-gb 20
:pvc-storage-class-name "local-path"})))))
(deftest should-generate-secret
(is (= {:apiVersion "v1"
:kind "Secret"
:metadata {:name "postgres-secret"}
:type "Opaque"
:data
{:postgres-user "eHgtdXM=", :postgres-password "eHgtcHc="}}
(is (= {:apiVersion "v1",
:kind "Secret",
:metadata {:name "postgres-secret", :namespace "default"},
:type "Opaque",
:data {:postgres-user "eHgtdXM=", :postgres-password "eHgtcHc="}}
(cut/generate-secret {:postgres-db-user "xx-us" :postgres-db-password "xx-pw"}))))
(deftest should-generate-deployment
(is (= [{:image "postgres:14"
:name "postgresql"
:env
[{:name "POSTGRES_USER"
:valueFrom
{:secretKeyRef
{:name "postgres-secret", :key "postgres-user"}}}
{:name "POSTGRES_PASSWORD"
:valueFrom
{:secretKeyRef
{:name "postgres-secret", :key "postgres-password"}}}
{:name "POSTGRES_DB"
:valueFrom
{:configMapKeyRef
{:name "postgres-config", :key "postgres-db"}}}]
:ports [{:containerPort 5432, :name "postgresql"}]
:volumeMounts
[{:name "postgres-config-volume"
:mountPath "/etc/postgresql/postgresql.conf"
:subPath "postgresql.conf"
:readOnly true}
{:name "postgre-data-volume"
:mountPath "/var/lib/postgresql/data"}]}]
(get-in (cut/generate-deployment {:postgres-image "postgres:14"})
[:spec :template :spec :containers]))))
(deftest should-generate-deployment-diff
(is (= {:image-c1 "postgres:13", :image-c2 "postgres:14"}
(ct/map-diff (cut/generate-deployment) (cut/generate-deployment {:postgres-image "postgres:14"})))))
(deftest should-generate
(is (= 6
(count (cut/generate {}
{:postgres-db-user "user"
:postgres-db-password "password"}))))
(is (= 5
(count (cut/generate-config {}))))
(is (= 1
(count (cut/generate-auth {}
{:postgres-db-user "user"
:postgres-db-password "password"})))))

View file

@ -16,6 +16,18 @@
(is (false? (cut/fqdn-string? "123.456.789")))
(is (false? (cut/fqdn-string? "test&test.de"))))
(deftest test-ipv4-string?
(is (true? (cut/ipv4-string? "127.0.0.1")))
(is (true? (cut/ipv4-string? "192.168.192.168")))
(is (true? (cut/ipv4-string? "1.2.3.4")))
(is (false? (cut/ipv4-string? "1.a.2.b")))
(is (false? (cut/ipv4-string? "f::f::f::f"))))
(deftest test-ipv6-string?
(is (true? (cut/ipv6-string? "2a01:4f8:c012:cb41::1")))
(is (false? (cut/ipv6-string? "1.a.2.b")))
(is (false? (cut/ipv6-string? "f::f::f::f"))))
(deftest test-string-of-separated-by?
(is (true? (cut/string-of-separated-by? cut/bash-env-string? #":" "abcd")))
(is (true? (cut/string-of-separated-by? cut/bash-env-string? #":" "abcd:efgh")))

View file

@ -0,0 +1,34 @@
(ns dda.c4k-common.browser-test
(:require
[cljs.test :refer-macros [deftest is are testing run-tests]]
[clojure.spec.test.alpha :as st]
[dda.c4k-common.browser :as cut]))
(st/instrument `cut/print-debug)
(st/instrument `cut/get-element-by-id)
(st/instrument `cut/get-content-from-element)
(st/instrument `cut/deserialize-content)
(st/instrument `cut/get-content-value-from-element)
(st/instrument `cut/set-validation-result!)
(st/instrument `cut/validate!)
(st/instrument `cut/set-output!)
(st/instrument `cut/set-form-validated!)
(st/instrument `cut/create-js-obj-from-html)
(st/instrument `cut/append-to-c4k-content)
(st/instrument `cut/append-hickory)
(st/instrument `cut/generate-feedback-tag)
(st/instrument `cut/generate-label)
(st/instrument `cut/generate-br)
(st/instrument `cut/generate-input-field)
(st/instrument `cut/generate-text-area)
(st/instrument `cut/generate-button)
(st/instrument `cut/generate-output)
(st/instrument `cut/generate-needs-validation)
(st/instrument `cut/generate-group)
(deftest should-deserialize-content
(is (= (cut/deserialize-content " " identity true) nil))
(is (= (cut/deserialize-content "test" keyword false) :test))
(is (= (cut/deserialize-content "test" identity false) "test"))
(is (= (cut/deserialize-content "test" identity true) "test")))

View file

@ -0,0 +1,14 @@
(ns dda.c4k-common.macros-test
(:require
[cljs.test :refer-macros [deftest is]]
[dda.c4k-common.macros :refer-macros [inline-resources]]))
(deftest should-count-inline-resources
(is (= 4 (count (inline-resources "ingress")))))
(deftest should-inline-resources
(let [resource-path (fn [name] (str "dda/c4k_common/inline_resources_test/" name))
inlined-resources (inline-resources "dda/c4k_common/inline_resources_test")]
(is (= "1" (get inlined-resources (resource-path "inline_resource_1.yaml"))))
(is (= "2" (get inlined-resources (resource-path "inline_resource_2.yaml"))))
(is (= "3" (get inlined-resources (resource-path "inline_resource_3.yaml"))))))

View file

@ -0,0 +1,3 @@
mon-auth:
grafana-cloud-user: "user"
grafana-cloud-password: "password"

View file

@ -0,0 +1,4 @@
mon-cfg:
grafana-cloud-url: "url-for-your-prom-remote-write-endpoint"
cluster-name: "forgejo"
cluster-stage: "test"