Compare commits

...

64 Commits

Author SHA1 Message Date
Michael Jerger 7c4dc29651 prepare switch to main 2 weeks ago
Michael Jerger 3756a5bc63 update refactorings 2 weeks ago
Michael Jerger acb8b1e7bf replace deprecated fkts 2 weeks ago
Michael Jerger 169a51456b bump version to: 6.3.1-SNAPSHOT 2 weeks ago
Michael Jerger a9fa3f21e7 release: 6.3.0 2 weeks ago
Michael Jerger 65e9c154a7 update versions 2 weeks ago
bom d2bf694e4e Implement burst-rate for simple-ingress 1 month ago
bom dd897c7364 Fix variable names 1 month ago
bom 0b647ed4d8 Remove old deprecated functions 1 month ago
bom 2008322368 Deprecate replace-all-matching-values-by-new-value
in favor or 'replace-all-matching' as it is shorter
1 month ago
bom b4eb47d584 Add support for hetzner-csi for PVC 1 month ago
bom b11f3edd61 bump version to: 6.2.4-SNAPSHOT 3 months ago
bom f33b86125a release: 6.2.3 3 months ago
bom b2ce1fa75e Use inline-resource macro again 3 months ago
bom 1c0537f8e9 Add jar-file inlining 3 months ago
bom 50888233e0 Upgrade deps 3 months ago
Michael Jerger f2925a2991 update version in refactoring overview 3 months ago
Michael Jerger efdb2051c5 bump version to: 6.2.3-SNAPSHOT 4 months ago
Michael Jerger 704b5cdcc8 release: 6.2.2 4 months ago
Michael Jerger 4fdf7d7de3 fix ratelimit wiring 4 months ago
Michael Jerger 468c76e902 bump version to: 6.2.2-SNAPSHOT 4 months ago
Michael Jerger 99cbc09340 release: 6.2.1 4 months ago
Michael Jerger 7469fc1cd4 fix macro issue 4 months ago
Michael Jerger 552dcbd1d8 bump version to: 6.2.1-SNAPSHOT 4 months ago
Michael Jerger 5d4f022145 release: 6.2.0 4 months ago
Clemens c56efba544 adjust doc 4 months ago
Clemens 515813ce5c small reformulation 4 months ago
Clemens 4f071f14c8 revised postgres db doc 4 months ago
Clemens 26ca2acb6b revise namespaces and ingress in docs 4 months ago
Clemens 231002017f reformat titles and describe cmd lines in more detail 4 months ago
Clemens 8449548d56 small adjustment in readme 4 months ago
Clemens 3026900b98 minor revision of readme 4 months ago
bom 4bc93285b8 Update refactoring table
Add c4k-taiga changes
4 months ago
Michael Jerger f334756d91 update refactoring state 4 months ago
jem 0594cfbec4 Merge pull request 'feature/namespace' (#2) from feature/namespace into master
Reviewed-on: #2
4 months ago
Michael Jerger b21e377168 instrument new test 4 months ago
Michael Jerger 73a9965f96 format 4 months ago
Michael Jerger c1f2cefd14 keep compatibility 4 months ago
Michael Jerger a1a49e10d0 update doc 4 months ago
Michael Jerger 0cd4f22823 fix cljs 4 months ago
Michael Jerger 7e9c813e33 introduce namespace for postgres 4 months ago
Michael Jerger 674045eba3 add test & fixed some issues 4 months ago
Michael Jerger cca93311e4 seperate postgres & postgres-internal 4 months ago
Michael Jerger 8626b91b16 refactor to dry 4 months ago
Michael Jerger a14b7640da update deps 4 months ago
Michael Jerger ab2473bdf4 introduce internal for monitoring 4 months ago
Michael Jerger b4718500b1 fix cljs for postgresql 4 months ago
Michael Jerger 6794a24f0f add namespace to ingress 4 months ago
Michael Jerger 73e2ebd12f introduce namespace 4 months ago
Michael Jerger 866ef84f12 use load-resource macro 4 months ago
jem fe43c0ec58 Merge pull request 'add ratelimit to ingress' (#1) from feature/ingress-ratelimit into master
Reviewed-on: #1
4 months ago
Michael Jerger 781ae8b0c5 remov unused imports 4 months ago
Michael Jerger a334dff58b add ratelimit to ingress 4 months ago
Michael Jerger c7fab83c39 update ci images 4 months ago
Michael Jerger 6b79f2599d minor fixes 4 months ago
Michael Jerger a68ff0bd14 fix doc 4 months ago
Michael Jerger ef4d35b6f5 added native image build refactoring 4 months ago
Michael Jerger 6b59d288d8 bump version to: 6.1.4-SNAPSHOT 4 months ago
Michael Jerger 0a84b7ad63 release: 6.1.3 4 months ago
Michael Jerger b706139d4f remove need for reflection 4 months ago
bom a81d5b1efc bump version to: 6.1.3-SNAPSHOT 5 months ago
bom b5965cee3e release: 6.1.2 5 months ago
bom 0e20df8ff0 Add token env variable to CI 5 months ago
bom 8b4e431418 bump version to: 6.1.2-SNAPSHOT 5 months ago

@ -5,7 +5,7 @@ stages:
- upload
.cljs-job: &cljs
image: "domaindrivenarchitecture/ddadevops-clj-cljs:4.10.7"
image: "domaindrivenarchitecture/ddadevops-clj-cljs:4.11.4"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
@ -13,16 +13,18 @@ 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/ddadevops-clj-cljs:4.10.7"
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

@ -3,72 +3,386 @@
[<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
## Rationale
c4k-common provides the foundation for all our c4k modules.
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.
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:
Why do we need another one? Why do you continue the reading here?
config.edn - minimal example
We combine the simplicity of `kustomize` with the ability to do real programming like software developers would do.
```clojure
{:k3s-cluster-name "your-cluster-name"
:k3s-cluster-stage :prod
:grafana-cloud-url "your-url"}
```
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
auth.edn - minimal example
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
{:grafana-cloud-user "user"
:grafana-cloud-password "password"}
```
(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)
call (with jarwrapper installed):
```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
c4k-common-standalone.jar config.edn auth.edn > monitoring.yaml
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
```
## Rationale
#### Input as EDN or Yaml
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.
c4k-common supports yaml and edn as format for all of its resources (input and output).
Hence, the following command line will also work:
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
```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
## Usage
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)
```
c4k-common provides the basic functionality for our c4k-modules.
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 | [common load-as-edn][edn1] | [groups for webview][bgrp1] | [use common ingress][ing1] | [use common monitoring][mon1] | [validate examples][val1] | [ci with pyb][cipyb] | [inline-macro to load resources][macro] |
| ------------- |---------| :------------------------: | :-------------------------: | :------------------------: | :---------------------------: | :-----------------------: |:--------------------:| :-------------------------------------: |
| c4k-keycloak | 0.2 | x | x | x | x | x | | |
| c4k-taiga | 0.1 | | | | | | | |
| c4k-nextcloud | 4.0 | x | x | x | x | x | | |
| c4k-jitsi | 1.6 | x | x | x | x | x | x | x |
| c4k-forgejo | 3.0 | x | x | x | x | x | x | x |
| c4k-shynet | 1.0 | | | | | | | |
| c4k-website | 1.1 | x | x | x | x | x | | |
| Module | Version | [common load-as-edn][edn1] | [groups for webview][bgrp1] | [use common ingress][ing1] | [use common monitoring][mon1] | [validate examples][val1] | [ci with pyb][cipyb] | [inline-macro to load resources][macro] | [native build][native] | namespaces |
| ------------- |---------|:--------------------------:|:---------------------------:|:--------------------------:|:-----------------------------:|:-------------------------:|:--------------------:|:---------------------------------------:|:----------------------:|:----------:|
| c4k-keycloak | 0.2 | x | x | x | x | x | | | | |
| c4k-taiga | 0.1 | x | x | x | x | x | x | x | x | |
| c4k-nextcloud | 10.2 | x | x | x | x | x | | x | x | |
| c4k-jitsi | 1.6 | x | x | x | x | x | x | x | | |
| c4k-forgejo | 3.0 | x | x | x | x | x | x | x | x | |
| c4k-shynet | 1.0 | | | | | | | | | |
| c4k-website | 2.0 | x | x | x | x | x | x | x | x | |
[edn1]: https://gitlab.com/domaindrivenarchitecture/c4k-website/-/merge_requests/1
[ing1]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/214aa41c28662fbf7a49998e17404e7ac9216430
@ -78,8 +392,9 @@ c4k-common provides the basic functionality for our c4k-modules.
[repo1]: https://repo.prod.meissa.de/meissa/c4k-forgejo/commit/e9ee6136f3347d5fccefa6b5b4a02d30c4dc42e1
[cipyb]: https://gitlab.com/domaindrivenarchitecture/c4k-jitsi/-/merge_requests/1
[macro]: https://repo.prod.meissa.de/meissa/c4k-jitsi/commit/61d05ceedb6dcbc6bb96b96fe6f03598e2878195
[native]: https://repo.prod.meissa.de/meissa/c4k-forgejo/pulls/4/files
## Development & mirrors
## Development & Mirrors
Development happens at: https://repo.prod.meissa.de/meissa/c4k-common
@ -92,6 +407,6 @@ For more details about our repository model see: https://repo.prod.meissa.de/mei
## 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)

@ -23,7 +23,7 @@ def initialize(project):
"release_secondary_build_files": [
"project-cljs.clj",
],
"release_main_branch": "master",
"release_main_branch": "main",
}
build = ReleaseMixin(project, input)

@ -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
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

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

@ -1,10 +1,10 @@
(defproject org.domaindrivenarchitecture/c4k-common-clj "6.1.1"
(defproject org.domaindrivenarchitecture/c4k-common-clj "6.3.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.7"]
: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"]
@ -25,17 +25,13 @@
:uberjar {:aot :all
:main dda.c4k-common.uberjar
:uberjar-name "c4k-common-standalone.jar"
:dependencies [[org.clojure/tools.cli "1.0.219"]
[ch.qos.logback/logback-classic "1.4.14"
: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.11"]]}}
[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"]])

@ -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"))

@ -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,15 +27,23 @@
%)
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?)]

@ -1,85 +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]))
; TODO: use this default consistently
(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?
(defn-spec generate-certificate map?
[config certificate?]
(let [final-config (merge default-config
config)]
(int/generate-certificate final-config)))
(defn-spec generate-ingress map?
[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)))))
(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}
default-config
config)]
[(int/generate-certificate final-config)
(int/generate-ingress final-config)]))
(defn-spec generate-certificate pred/map-or-seq?
[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))))
(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
:ingress-name service-name
:cert-name service-name}
ingress-defaults
simple-ingress-config)]
[(generate-certificate config)
(generate-ingress 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)]))

@ -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)))))

@ -1,8 +0,0 @@
(ns dda.c4k-common.macros
(:require [clojure.java.io :as io]))
(defmacro inline-resources [resource-path]
(let [files (.listFiles (io/file (io/resource resource-path)))
file-contents (map slurp files)
file-names (map #(str resource-path "/" (.getName %)) files)]
(zipmap file-names file-contents)))

@ -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))))

@ -1,128 +1,43 @@
(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 generate seq?
[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 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")])

@ -0,0 +1,92 @@
(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.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. (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 map?
[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)))))

@ -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)]))

@ -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))))

@ -1,99 +1,94 @@
(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]))
(defn postgres-size?
[input]
(contains? #{:2gb :4gb :8gb :16gb} input))
(defn postgres-image?
[input]
(contains? #{"postgres:13" "postgres:14"} 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?)
[dda.c4k-common.namespace :as ns]
[dda.c4k-common.postgres.postgres-internal :as int]))
(def postgres-size? int/postgres-size?)
(def postgres-image? int/postgres-image?)
(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!")))))
(defn-spec generate-config cp/map-or-seq?
(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 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))))
; TODO: why do we need a sequence of configs?
(defn-spec generate-deployment cp/map-or-seq?
(let [final-config (merge default-config
(first config))]
(int/generate-config final-config)))
(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")))))
(defn-spec generate-pvc cp/map-or-seq?
(let [final-config (merge default-config
config)]
(int/generate-persistent-volume final-config)))
(defn-spec generate-pvc map?
[config pg-config?]
(let [final-config (merge default-config
config)]
(int/generate-pvc final-config)))
(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 [{: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)))))
(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")))
(let [final-config (merge default-config
config)]
(int/generate-service final-config)))
(defn-spec generate seq?
[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-config final-config)
(int/generate-pvc final-config)
(int/generate-deployment final-config)
(int/generate-service final-config)]))

@ -0,0 +1,111 @@
(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-config 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 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))))

@ -33,7 +33,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]

@ -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]

@ -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

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

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

@ -1,13 +1,21 @@
(ns dda.c4k-common.macros-test
(:require
(:require
[clojure.test :refer [deftest is are testing run-tests]]
[dda.c4k-common.macros :refer [inline-resources]]))
[dda.c4k-common.macros :as cut :refer [inline-resources]]))
(deftest should-count-inline-resources
(is (= 3 (count (inline-resources "ingress")))))
(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))]
(is (= "1" (get (inline-resources "dda/c4k_common/inline_resources_test") (resource-path "inline_resource_1.yaml"))))
(is (= "2" (get (inline-resources "dda/c4k_common/inline_resources_test") (resource-path "inline_resource_2.yaml"))))
(is (= "3" (get (inline-resources "dda/c4k_common/inline_resources_test") (resource-path "inline_resource_3.yaml"))))))
(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")))))

@ -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]

@ -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"]})))))

@ -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",
@ -121,4 +81,47 @@
:port {:number 80}}}}]}}]}}]
(cut/generate-ingress-and-cert {:fqdns ["test.jit.si"]
:service-name "web"
:service-port 80}))))
: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}))))

@ -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)
(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 invalid-conf auth))))
(deftest should-not-generate-auth
(is (thrown?
#?(:clj Exception :cljs js/Error)
(cut/generate-config 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 conf auth)
[:stringData :prometheus.yaml])
"global:\n scrape_interval:")))

@ -2,16 +2,10 @@
(: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)
(def conf {:cluster-name "clustername"
:cluster-stage "test"
@ -21,45 +15,7 @@
: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:")))

@ -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"}))))

@ -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-config)
(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-config
(is (= {:name "postgres-config",
:namespace "default"
:labels {:app "postgres"}}
(:metadata (cut/generate-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"
:namespace "default"}))))
(is (= {:name "postgres-config",
:namespace "myapp"
:labels {:app "postgres"}}
(:metadata (cut/generate-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"
:namespace "myapp"}))))
(is (= {:postgres-db "postgres"
:postgresql.conf
"max_connections = 100\nwork_mem = 4MB\nshared_buffers = 512MB\n"}
(:data (cut/generate-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"
:namespace "default"}))))
(is (= {:postgres-db "postgres"
:postgresql.conf
"max_connections = 700\nwork_mem = 3MB\nshared_buffers = 2048MB\n"}
(:data (cut/generate-config {: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-config {: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"})))))

@ -3,132 +3,55 @@
#?(: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-persistent-volume)
(st/instrument `cut/generate-pvc)
(st/instrument `cut/generate-secret)
(st/instrument `cut/generate-service)
(st/instrument `cut/generate)
(deftest should-generate-config
(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-config)))))
(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"})))))

@ -4,10 +4,11 @@
[dda.c4k-common.macros :refer-macros [inline-resources]]))
(deftest should-count-inline-resources
(is (= 3 (count (inline-resources "ingress")))))
(is (= 4 (count (inline-resources "ingress")))))
(deftest should-inline-resources
(let [resource-path (fn [name] (str "dda/c4k_common/inline_resources_test/" name))]
(is (= "1" (get (inline-resources "dda/c4k_common/inline_resources_test") (resource-path "inline_resource_1.yaml"))))
(is (= "2" (get (inline-resources "dda/c4k_common/inline_resources_test") (resource-path "inline_resource_2.yaml"))))
(is (= "3" (get (inline-resources "dda/c4k_common/inline_resources_test") (resource-path "inline_resource_3.yaml"))))))
(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"))))))

Loading…
Cancel
Save