Compare commits

..

No commits in common. "master" and "map_function" have entirely different histories.

46 changed files with 925 additions and 1062 deletions

11
.gitignore vendored
View file

@ -7,7 +7,6 @@ target/
.lein-repl-history .lein-repl-history
.lein-failures .lein-failures
pom.* pom.*
reports/*
# cljs # cljs
.shadow-cljs .shadow-cljs
@ -22,11 +21,5 @@ public/js/
*.iml *.iml
.idea/ .idea/
valid-auth.edn myauth.edn
valid-config.edn myconfig.edn
my-auth.edn
.clj-kondo/
.lsp/
build-and-move-frontend.sh

View file

@ -1,11 +1,12 @@
stages: stages:
- first
- build_and_test - build_and_test
- package - package
- security - security
- upload - upload
.cljs-job: &cljs .cljs-job: &cljs
image: "domaindrivenarchitecture/ddadevops-clj-cljs:4.11.3" image: domaindrivenarchitecture/shadow-cljs
cache: cache:
key: ${CI_COMMIT_REF_SLUG} key: ${CI_COMMIT_REF_SLUG}
paths: paths:
@ -13,62 +14,49 @@ stages:
- .shadow-cljs/ - .shadow-cljs/
- .m2 - .m2
before_script: before_script:
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
- echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
- npm install - npm install
.clj-uploadjob: &clj .clj-uploadjob: &clj
image: "domaindrivenarchitecture/ddadevops-clj:4.11.3" image: clojure:lein-2.7.1-alpine
cache: cache:
key: ${CI_COMMIT_REF_SLUG} key: ${CI_COMMIT_REF_SLUG}
paths: paths:
- .m2 - .m2
before_script: 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 - echo "{:auth {:repository-auth {#\"clojars\" {:username \"${CLOJARS_USER}\" :password \"${CLOJARS_TOKEN_DOMAINDRIVENARCHITECTURE}\" }}}}" > ~/.lein/profiles.clj
.tag_only: &tag_only .test-cljs:
rules: <<: *cljs
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' stage: build_and_test
when: never script:
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/' - shadow-cljs compile test
test-clj: test-clj:
<<: *clj <<: *clj
stage: build_and_test stage: build_and_test
script: script:
- pyb test_clj - lein test
test-cljs: .report-frontend:
<<: *cljs
stage: build_and_test
script:
- pyb test_cljs
test-schema:
<<: *clj
stage: build_and_test
script:
- pyb test_schema
artifacts:
paths:
- target/uberjar
report-frontend:
<<: *cljs <<: *cljs
stage: package stage: package
script: script:
- pyb report_frontend - mkdir -p target/frontend-build
- shadow-cljs run shadow.cljs.build-report frontend target/frontend-build/build-report.html
artifacts: artifacts:
paths: paths:
- target/frontend-build/build-report.html - target/frontend-build/build-report.html
package-frontend: .package-frontend:
<<: *cljs <<: *cljs
stage: package stage: package
script: script:
- pyb package_frontend - mkdir -p target/frontend-build
- shadow-cljs release frontend
- cp public/js/main.js target/frontend-build/k8s-keycloak.js
- sha256sum target/frontend-build/k8s-keycloak.js > target/frontend-build/k8s-keycloak.js.sha256
- sha512sum target/frontend-build/k8s-keycloak.js > target/frontend-build/k8s-keycloak.js.sha512
artifacts: artifacts:
paths: paths:
- target/frontend-build - target/frontend-build
@ -77,30 +65,48 @@ package-uberjar:
<<: *clj <<: *clj
stage: package stage: package
script: script:
- pyb package_uberjar - lein uberjar
- sha256sum target/uberjar/k8s-keycloak-standalone.jar > target/uberjar/k8s-keycloak-standalone.jar.sha256
- sha512sum target/uberjar/k8s-keycloak-standalone.jar > target/uberjar/k8s-keycloak-standalone.jar.sha512
artifacts: artifacts:
paths: paths:
- target/uberjar - target/uberjar
package-native: sast:
variables:
SAST_EXCLUDED_ANALYZERS:
bandit, brakeman, flawfinder, gosec, kubesec, phpcs-security-audit,
pmd-apex, security-code-scan, sobelow, spotbugs
stage: security
before_script:
- mkdir -p builds && cp -r target/ builds/
include:
- template: Security/SAST.gitlab-ci.yml
upload-clj-prerelease:
<<: *clj <<: *clj
stage: package stage: upload
rules:
- if: '$CI_COMMIT_BRANCH == "master" && $CI_COMMIT_TAG == null'
script: script:
- pyb package_native - lein deploy clojars
release:
image: registry.gitlab.com/gitlab-org/release-cli:latest
stage: upload
rules:
- if: '$CI_COMMIT_TAG != null'
artifacts: artifacts:
paths: paths:
- target/graalvm - target/uberjar
- target/frontend-build
release-to-clojars:
<<: *clj
<<: *tag_only
stage: upload
script: script:
- pyb upload_clj - apk --no-cache add curl
- |
release-to-forgejo: release-cli create --name "Release $CI_COMMIT_TAG" --tag-name $CI_COMMIT_TAG \
<<: *clj --assets-link "{\"name\":\"k8s-keycloak-standalone.jar\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/-/jobs/${CI_JOB_ID}/artifacts/file/target/uberjar/k8s-keycloak-standalone.jar\"}" \
<<: *tag_only --assets-link "{\"name\":\"k8s-keycloak-standalone.jar.sha256\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/-/jobs/${CI_JOB_ID}/artifacts/file/target/uberjar/k8s-keycloak-standalone.jar.sha256\"}" \
stage: upload --assets-link "{\"name\":\"k8s-keycloak-standalone.jar.sha512\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/-/jobs/${CI_JOB_ID}/artifacts/file/target/uberjar/k8s-keycloak-standalone.jar.sha512\"}" \
script: --assets-link "{\"name\":\"k8s-keycloak.js\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/-/jobs/${CI_JOB_ID}/artifacts/file/target/frontend-build/k8s-keycloak.js\"}" \
- pyb publish_artifacts --assets-link "{\"name\":\"k8s-keycloak.js.sha256\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/-/jobs/${CI_JOB_ID}/artifacts/file/target/frontend-build/k8s-keycloak.js.sha256\"}" \
--assets-link "{\"name\":\"k8s-keycloak.js.sha512\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/-/jobs/${CI_JOB_ID}/artifacts/file/target/frontend-build/k8s-keycloak.js.sha512\"}" \

201
LICENSE
View file

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2021 meissa GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,48 +1,40 @@
# convention 4 kubernetes: c4k-keycloak # k8s-keycloak
[![Clojars Project](https://img.shields.io/clojars/v/org.domaindrivenarchitecture/c4k-keycloak.svg)](https://clojars.org/org.domaindrivenarchitecture/c4k-keycloak) [![pipeline status](https://gitlab.com/domaindrivenarchitecture/c4k-keycloak/badges/master/pipeline.svg)](https://gitlab.com/domaindrivenarchitecture/c4k-keycloak/-/commits/master) [![Clojars Project](https://img.shields.io/clojars/v/dda/k8s-keycloak.svg)](https://clojars.org/dda/k8s-keycloak) [![pipeline status](https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/badges/master/pipeline.svg)](https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/-/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.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) [<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 ## Purpose
c4k-keycloak provides a k8s deployment for keycloak containing: k8s-keycloak ....
* keycloak
* ingress having a letsencrypt managed certificate
* postgres database
The package aims to a low load sceanrio. ## Rational
## Status There are many comparable solutions for creating k8s 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.
This is just a POC, database is stored volatile, there is no backup implemented. Our k8s-* 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
## Try out ## Try out
Click on the image to try out live in your browser: Click on the image to try out live in your browser:
[![Try it out](/doc/tryItOut.png "Try out yourself")](https://domaindrivenarchitecture.org/pages/dda-provision/c4k-keycloak/) [![Try it out](/doc/tryItOut.png "Try out yourself")](https://domaindrivenarchitecture.org/pages/dda-provision/k8s-keycloak/)
Your input will stay in your browser. No server interaction is required. Your input will stay in your browser. No server interaction is required.
You will also be able to try out on cli: You will also be able to try out on cli:
``` ```
target/graalvm/c4k-keycloak src/test/resources/keycloak-test/valid-config.edn src/test/resourceskeycloak-test/valid-auth.edn | kubeval - target/graalvm/k8s-keycloak src/test/resources/valid-config.edn src/test/resources/valid-auth.edn | kubeval -
target/graalvm/c4k-keycloak src/test/resources/keycloak-test/valid-config.edn src/test/resources/keycloak-test/valid-auth.edn | kubectl apply -f - target/graalvm/k8s-keycloak src/test/resources/valid-config.edn src/test/resources/valid-auth.edn | kubectl apply -f -
``` ```
## Development & mirrors
Development happens at: https://repo.prod.meissa.de/meissa/c4k-keycloak
Mirrors are:
* https://gitlab.com/domaindrivenarchitecture/c4k-keycloak (issues and PR, CI)
* https://github.com/DomainDrivenArchitecture/c4k-keycloak
For more details about our repository model see: https://repo.prod.meissa.de/meissa/federate-your-repos
## License ## License
Copyright © 2024 meissa GmbH Copyright © 2021 meissa GmbH
Licensed under the [Apache License, Version 2.0](LICENSE) (the "License") Licensed under the [Apache License, Version 2.0](LICENSE) (the "License")
Pls. find licenses of our subcomponents [here](doc/SUBCOMPONENT_LICENSE) Pls. find licenses of our subcomponents [here](doc/SUBCOMPONENT_LICENSE)

234
build.py
View file

@ -1,234 +0,0 @@
from os import environ
from subprocess import run
from pybuilder.core import init, task
from ddadevops import *
default_task = "dev"
name = 'c4k-keycloak'
MODULE = 'not-used'
PROJECT_ROOT_PATH = '.'
@init
def initialize(project):
project.build_depends_on("ddadevops>=4.7.0")
input = {
"name": name,
"module": MODULE,
"stage": "notused",
"project_root_path": PROJECT_ROOT_PATH,
"build_types": [],
"release_main_branch": "master",
"mixin_types": ["RELEASE"],
"release_primary_build_file": "project.clj",
"release_secondary_build_files": [
"package.json",
],
"release_artifact_server_url": "https://repo.prod.meissa.de",
"release_organisation": "meissa",
"release_repository_name": name,
"release_artifacts": [
"target/graalvm/" + name,
"target/uberjar/" + name + "-standalone.jar",
"target/frontend-build/" + name + ".js",
],
}
build = ReleaseMixin(project, input)
build.initialize_build_dir()
@task
def test_clj():
run("lein test", shell=True, check=True)
@task
def test_cljs():
run("shadow-cljs compile test", shell=True, check=True)
run("node target/node-tests.js", shell=True, check=True)
@task
def test_schema():
run("lein uberjar", shell=True, check=True)
run(
"java -jar target/uberjar/c4k-keycloak-standalone.jar "
+ "src/test/resources/keycloak-test/valid-config.yaml "
+ "src/test/resources/keycloak-test/valid-auth.yaml | "
+ """kubeconform --kubernetes-version 1.23.0 --strict --skip "Certificate,Middleware" -""",
shell=True,
check=True,
)
@task
def test():
test_clj()
test_cljs()
test_schema()
@task
def report_frontend(project):
run("mkdir -p target/frontend-build", shell=True, check=True)
run(
"shadow-cljs run shadow.cljs.build-report frontend target/frontend-build/build-report.html",
shell=True,
check=True,
)
@task
def package_frontend(project):
run("mkdir -p target/frontend-build", shell=True, check=True)
run("shadow-cljs release frontend", shell=True, check=True)
run(
"cp public/js/main.js target/frontend-build/" + project.name + ".js",
shell=True,
check=True,
)
run(
"sha256sum target/frontend-build/c4k-keycloak.js > target/frontend-build/" + project.name + ".js.sha256",
shell=True,
check=True,
)
run(
"sha512sum target/frontend-build/c4k-keycloak.js > target/frontend-build/" + project.name + ".js.sha512",
shell=True,
check=True,
)
@task
def package_uberjar(project):
run("mkdir -p target/uberjar", shell=True, check=True)
run(
"lein uberjar",
shell=True,
check=True,
)
run(
"sha256sum target/uberjar/c4k-keycloak-standalone.jar > target/uberjar/" + project.name + "-standalone.jar.sha256",
shell=True,
check=True,
)
run(
"sha512sum target/uberjar/c4k-keycloak-standalone.jar > target/uberjar/" + project.name + "-standalone.jar.sha512",
shell=True,
check=True,
)
@task
def package_native(project):
run(
"mkdir -p target/graalvm",
shell=True,
check=True,
)
run(
"native-image " +
"--native-image-info " +
"--report-unsupported-elements-at-runtime " +
"--no-server " +
"--no-fallback " +
"--features=clj_easy.graal_build_time.InitClojureClasses " +
f"-jar target/uberjar/{project.name}-standalone.jar " +
"-H:IncludeResources=.*.yaml " +
"-H:Log=registerResource:verbose " +
f"-H:Name=target/graalvm/{project.name}",
shell=True,
check=True,
)
run(
"sha256sum target/graalvm/c4k-keycloak > target/graalvm/" + project.name + ".sha256",
shell=True,
check=True,
)
run(
"sha512sum target/graalvm/c4k-keycloak > target/graalvm/" + project.name + ".sha512",
shell=True,
check=True,
)
@task
def inst(project):
package_uberjar(project)
package_native(project)
run(
"sudo install -m=755 target/uberjar/" + project.name + "-standalone.jar /usr/local/bin/" + project.name + "-standalone.jar",
shell=True,
check=True,
)
run(
"sudo install -m=755 target/graalvm/" + project.name + " /usr/local/bin/" + project.name + "",
shell=True,
check=True,
)
@task
def upload_clj(project):
run("lein deploy", shell=True, check=True)
@task
def lint(project):
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()
@task
def publish_artifacts(project):
build = get_devops_build(project)
build.publish_artifacts()
def release(project):
prepare(project)
tag(project)
def linttest(project, release_type):
build = get_devops_build(project)
build.update_release_type(release_type)
test_clj()
test_cljs()
test_schema()
lint(project)

View file

@ -3,13 +3,13 @@
## clj setup ## clj setup
### install leiningen ### install leiningen
```bash ```
sudo apt install leiningen sudo apt install leiningen
``` ```
or manually using Instructions on https://leiningen.org/#install or manually using Instructions on https://leiningen.org/#install
### install vscode + extensions ### install vscode + extensions
```bash ```
sudo snap install code sudo snap install code
``` ```
or with packages from https://code.visualstudio.com/Download or with packages from https://code.visualstudio.com/Download
@ -18,7 +18,7 @@ install extension "Calva: Clojure & ClojureScript Interactive Programming"
## cljs / js-dev setup ## cljs / js-dev setup
```bash ```
sudo apt install npm sudo apt install npm
sudo npm install -g npx sudo npm install -g npx
@ -32,44 +32,47 @@ npx shadow-cljs compile test
### create frontend script ### create frontend script
```bash ```
npx shadow-cljs release frontend npx shadow-cljs release frontend
``` ```
## graalvm-setup ## graalvm-setup
```bash ```
curl -LO https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz curl -LO https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/graalvm-ce-java11-linux-amd64-21.0.0.2.tar.gz
# unpack # unpack
tar -xzf graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz tar -xzf graalvm-ce-java11-linux-amd64-21.0.0.2.tar.gz
sudo mv graalvm-community-openjdk-21.0.2+13.1 /usr/lib/jvm/ sudo mv graalvm-ce-java11-21.0.0.2 /usr/lib/jvm/
sudo ln -s /usr/lib/jvm/graalvm-community-openjdk-21.0.2+13.1 /usr/lib/jvm/graalvm-21 sudo ln -s /usr/lib/jvm/graalvm-ce-java11-21.0.0.2 /usr/lib/jvm/graalvm
sudo ln -s /usr/lib/jvm/graalvm-21/bin/gu /usr/local/bin sudo ln -s /usr/lib/jvm/graalvm/bin/gu /usr/local/bin
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/graalvm-21/bin/java 2 sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/graalvm/bin/java 2
sudo update-alternatives --config java sudo update-alternatives --config java
sudo ln -s /usr/lib/jvm/graalvm-21/bin/native-image /usr/local/bin
# install native-image in graalvm-ce-java11-linux-amd64-21.0.0.2/bin
sudo gu install native-image
sudo ln -s /usr/lib/jvm/graalvm/bin/native-image /usr/local/bin
# deps # deps
sudo apt-get install build-essential libz-dev zlib1g-dev sudo apt-get install build-essential libz-dev zlib1g-dev
# build # build
cd ~/repo/c4k/c4k-keycloak cd ~/repo/dda/k8s-keycloak
lein uberjar lein uberjar
mkdir -p target/graalvm mkdir -p target/graalvm
lein native lein native
# execute # execute
./target/graalvm/c4k-cloud -h ./target/graalvm/k8s-keycloak -h
./target/graalvm/c4k-cloud src/test/resources/valid-config.edn src/test/resources/valid-auth.edn ./target/graalvm/k8s-keycloak src/test/resources/valid-config.edn src/test/resources/valid-auth.edn
./target/graalvm/c4k-cloud src/test/resources/invalid-config.edn src/test/resources/invalid-auth.edn ./target/graalvm/k8s-keycloak src/test/resources/invalid-config.edn src/test/resources/invalid-auth.edn
``` ```
## c4k-setup ## k8s-setup
### install kubectl ### install kubectl
```bash ```
sudo -i sudo -i
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" \ echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" \
@ -78,18 +81,20 @@ apt update && apt install kubectl
kubectl completion bash >> /etc/bash_completion.d/kubernetes kubectl completion bash >> /etc/bash_completion.d/kubernetes
``` ```
### install kubeconform ### install kubeval
```bash ```
curl -Lo /tmp/kubeconform.tar.gz https://github.com/yannh/kubeconform/releases/download/v0.4.7/kubeconform-linux-amd64.tar.gz wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz
tar -xf /tmp/kubeconform.tar.gz tar xf kubeval-linux-amd64.tar.gz
sudo cp kubeconform /usr/local/bin sudo cp kubeval /usr/local/bin
## remote access to k8s
``` ```
### remote access to c4k ### remote access to k8s
```bash ```
scp -r root@devops.test.meissa-gmbh.de:/home/c4k/.kube ~/ scp -r root@devops.test.meissa-gmbh.de:/home/k8s/.kube ~/
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@devops.test.meissa-gmbh.de -L 8002:localhost:8002 -L 6443:192.168.5.1:6443 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@devops.test.meissa-gmbh.de -L 8002:localhost:8002 -L 6443:192.168.5.1:6443
# add in /etc/hosts "127.0.0.1 kubernetes" # add in /etc/hosts "127.0.0.1 kubernetes"
@ -101,7 +106,7 @@ kubectl get pods
### deploy keycloak ### deploy keycloak
```bash ```
java -jar target/uberjar/c4k-keycloak-standalone.jar valid-config.edn valid-auth.edn | kubeconform --kubernetes-version 1.19.0 --strict --skip Certificate - java -jar target/uberjar/k8s-keycloak-standalone.jar myconfig.edn myauth.edn | kubeval -
java -jar target/uberjar/c4k-keycloak-standalone.jar valid-config.edn my-auth.edn | kubectl apply -f - java -jar target/uberjar/k8s-keycloak-standalone.jar myconfig.edn myauth.edn | kubectl apply -f -
``` ```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

7
image/Dockerfile Normal file
View file

@ -0,0 +1,7 @@
FROM node:lts-slim
RUN npm install -g keycloak
RUN mkdir /app
ENTRYPOINT ["keycloak"]
CMD [ "/app/config.edn" ]

View file

@ -1,29 +1,26 @@
{ {
"name": "c4k-keycloak", "name": "k8s-keycloak",
"description": "Generate c4k yaml for a keycloak deployment.", "description": "Generate k8s yaml for a keycloak deployment.",
"author": "meissa GmbH", "author": "meissa GmbH",
"version": "1.3.2-SNAPSHOT", "version": "0.1.5-SNAPSHOT",
"homepage": "https://gitlab.com/domaindrivenarchitecture/c4k-keycloak#readme", "homepage": "https://gitlab.com/domaindrivenarchitecture/k8s-keycloak#readme",
"repository": "https://www.npmjs.com/package/c4k-keycloak", "repository": "https://www.npmjs.com/package/k8s-keycloak",
"license": "APACHE2", "license": "APACHE2",
"main": "c4k-keycloak.js", "main": "k8s-keycloak.js",
"bin": { "bin": {
"c4k-keycloak": "./c4k-keycloak.js" "k8s-keycloak": "./k8s-keycloak.js"
}, },
"keywords": [ "keywords": [
"cljs", "cljs",
"keycloak", "keycloak",
"k8s", "k8s",
"c4k", "deplyoment",
"deployment", "yaml"
"yaml",
"convention4kubernetes"
], ],
"bugs": { "bugs": {
"url": "https://gitlab.com/domaindrivenarchitecture/c4k-keycloak/issues" "url": "https://gitlab.com/domaindrivenarchitecture/k8s-keycloak/issues"
}, },
"dependencies": { "dependencies": {
"js-base64": "^3.6.1",
"js-yaml": "^4.0.0" "js-yaml": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,34 +1,44 @@
(defproject org.domaindrivenarchitecture/c4k-keycloak "1.3.2-SNAPSHOT" (defproject org.domaindrivenarchitecture/k8s-keycloak "0.1.0-SNAPSHOT"
:description "keycloak c4k-installation package" :description "keycloak k8s-installation package"
:url "https://domaindrivenarchitecture.org" :url "https://domaindrivenarchitecture.org"
:license {:name "Apache License, Version 2.0" :license {:name "Apache License, Version 2.0"
:url "https://www.apache.org/licenses/LICENSE-2.0.html"} :url "https://www.apache.org/licenses/LICENSE-2.0.html"}
:dependencies [[org.clojure/clojure "1.11.4"] :dependencies [[org.clojure/clojure "1.10.3"]
[org.clojure/tools.reader "1.5.0"] [org.clojure/tools.reader "1.3.4"]
[org.domaindrivenarchitecture/c4k-common-clj "8.0.0"]] [aero "1.1.6"]
[orchestra "2021.01.01-1"]
[expound "0.8.9"]
[clj-commons/clj-yaml "0.7.106"]]
:target-path "target/%s/" :target-path "target/%s/"
:source-paths ["src/main/cljc" :source-paths ["src/main/cljc"
"src/main/clj"] "src/main/clj"]
:resource-paths ["src/main/resources"] :resource-paths ["src/main/resources"]
:repositories [["snapshots" :clojars] :repositories [["snapshots" :clojars]
["releases" :clojars]] ["releases" :clojars]]
:deploy-repositories [["snapshots" {:sign-releases false :url "https://clojars.org/repo"}] :deploy-repositories [["snapshots" :clojars]
["releases" {:sign-releases false :url "https://clojars.org/repo"}]] ["releases" :clojars]]
:profiles {:test {:test-paths ["src/test/cljc"] :profiles {:test {:test-paths ["src/test/cljc"]
:resource-paths ["src/test/resources"] :resource-paths ["src/test/resources"]
:dependencies [[dda/data-test "0.1.1"]]} :dependencies [[dda/data-test "0.1.1"]]}
:dev {:plugins [[lein-shell "0.5.0"]]} :dev {:plugins [[lein-shell "0.5.0"]]}
:uberjar {:aot :all :uberjar {:aot :all
:main dda.c4k-keycloak.uberjar :main dda.k8s-keycloak.uberjar
:uberjar-name "c4k-keycloak-standalone.jar" :uberjar-name "k8s-keycloak-standalone.jar"
:dependencies [[org.clojure/tools.cli "1.1.230"] :dependencies [[org.clojure/tools.cli "1.0.206"]
[ch.qos.logback/logback-classic "1.5.7" [ch.qos.logback/logback-classic "1.3.0-alpha4"
:exclusions [com.sun.mail/javax.mail]] :exclusions [com.sun.mail/javax.mail]]
[org.slf4j/jcl-over-slf4j "2.0.16"] [org.slf4j/jcl-over-slf4j "2.0.0-alpha1"]]}}
[com.github.clj-easy/graal-build-time "1.0.5"]]}} :release-tasks [["vcs" "assert-committed"]
:release-tasks [["test"]
["vcs" "assert-committed"]
["change" "version" "leiningen.release/bump-version" "release"] ["change" "version" "leiningen.release/bump-version" "release"]
["vcs" "commit"] ["vcs" "commit"]
["vcs" "tag" "--no-sign"] ["vcs" "tag"]
["change" "version" "leiningen.release/bump-version"]]) ["deploy"]
["change" "version" "leiningen.release/bump-version"]]
:aliases {"native" ["shell"
"native-image"
"--report-unsupported-elements-at-runtime"
"--initialize-at-build-time"
"-jar" "target/uberjar/k8s-keycloak-standalone.jar"
"-H:ResourceConfigurationFiles=graalvm-resource-config.json"
"-H:Log=registerResource"
"-H:Name=target/graalvm/${:name}"]})

View file

@ -3,15 +3,77 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>c4k-keycloak</title> <title>k8s-mastodon-bot</title>
<link href="https://domaindrivenarchitecture.org/css/bootstrap.min.css" rel="stylesheet" type="text/css" /> <link href="https://domaindrivenarchitecture.org/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="https://domaindrivenarchitecture.org/css/fonts/fontawesome/fontawesome.css" rel="stylesheet" <link href="https://domaindrivenarchitecture.org/css/fonts/fontawesome/fontawesome.css" rel="stylesheet" type="text/css" />
type="text/css" />
<link href="https://domaindrivenarchitecture.org/css/custom.css" rel="stylesheet" type="text/css" /> <link href="https://domaindrivenarchitecture.org/css/custom.css" rel="stylesheet" type="text/css" />
</head> </head>
<body> <body>
<div id="c4k-content"></div> <div class="container jumbotron">
<form class="needs-validation" id="form">
<label for="config" class="form-label">Your config.edn:</label>
<textarea name="config" id="config" class="form-control" rows="15">
{:transform [{:source {:source-type :twitter
;; optional, defaults to false
:include-replies? false
;; optional, defaults to false
:include-rts? false
;; Replace Twitter links by Nitter
:nitter-urls? false
;; accounts you wish to mirror
:accounts ["arstechnica" "WIRED"]}
:target {:target-type :mastodon
;; optional flag specifying wether the name of the account
;; will be appended in the post, defaults to false
:append-screen-name? false
;; optional visibility flag: direct, private, unlisted, public
;; defaults to public
:visibility "unlisted"
;; optional boolean to mark content as sensitive. Defaults to true.
:sensitive? true
;; optional boolean defaults to false
;; only sources containing media will be posted when set to true
:media-only? true
;; optional limit for the post length. Defaults to 300.
:max-post-length 300
;; optional signature for posts. Defaults to "not present".
:signature "#newsbot"}
}]
:auth {}}}
</textarea>
<div class="invalid-feedback"><pre id="config-validation"></pre></div>
<br><br>
<label for="auth" class="form-label">Your auth.edn:</label>
<textarea name="auth" id="auth" class="form-control" rows="15">
{:auth {;; add Twitter config to mirror Twitter accounts
:twitter {:consumer_key "XXXX"
:consumer_secret "XXXX"
:access_token_key "XXXX"
:access_token_secret "XXXX"}
:mastodon {:access_token "XXXX"
;; account number you see when you log in and go to your profile
;; e.g: https://mastodon.social/web/accounts/294795
:account-id "XXXX"
:api_url "https://botsin.space/api/v1/"}
:tumblr {:consumer_key "XXXX"
:consumer_secret "XXXX"
:token "XXXX"
:token_secret "XXXX"}}}
</textarea>
<div class="invalid-feedback"><pre id="auth-validation"></pre></div>
<br><br>
<button type="button" id="generate-button" class="btn btn-primary">
Generate k8s yaml
</button></form><br><br>
<div id="k8s-mastodon-bot-output">
<label for="output" class="form-label">Your k8s deployment.yaml:</label>
<textarea name="output" id="output" class="form-control" rows="15">
</textarea>
</div>
</div>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</body> </body>

View file

@ -1,15 +1,16 @@
{:source-paths ["src/main/cljc" {:source-paths ["src/main/cljc"
"src/main/cljs" "src/main/cljs"
"src/main/resources" "src/main/resources"
"src/test/cljc" "src/test/cljc"]
"src/test/cljs" :dependencies [[aero "1.1.6"]
"src/test/resources"] [orchestra "2021.01.01-1"]
:dependencies [[org.domaindrivenarchitecture/c4k-common-cljs "6.3.1"] [expound "0.8.9"]]
[hickory "0.7.1"]] :dev-http {8080 "public"}
:builds {:frontend {:target :browser :builds {:frontend {:target :browser
:modules {:main {:init-fn dda.c4k-keycloak.browser/init}} :modules {:main {:init-fn dda.k8s-keycloak.browser/init}}
:release {} :release {}
:compiler-options {:optimizations :advanced}} :compiler-options {:optimizations :advanced}}
:test {:target :node-test :test {:target :node-test
:output-to "target/node-tests.js" :output-to "target/node-tests.js"
:autorun true
:repl-pprint true}}} :repl-pprint true}}}

View file

@ -1,17 +0,0 @@
(ns dda.c4k-keycloak.uberjar
(:gen-class)
(:require
[dda.c4k-common.uberjar :as uberjar]
[dda.c4k-keycloak.core :as core]))
(set! *warn-on-reflection* true)
(defn -main [& cmd-args]
(uberjar/main-cm
"c4k-keycloak"
core/config?
core/auth?
core/config-defaults
core/config-objects
core/auth-objects
cmd-args))

View file

@ -0,0 +1,56 @@
(ns dda.k8s-keycloak.uberjar
(:gen-class)
(:require
[clojure.spec.alpha :as s]
[clojure.string :as cs]
[clojure.tools.reader.edn :as edn]
[expound.alpha :as expound]
[dda.k8s-keycloak.core :as core]))
(def usage
"usage:
k8s-keycloak {your configuraton file} {your authorization file}")
(s/def ::options (s/* #{"-h"}))
(s/def ::filename (s/and string?
#(not (cs/starts-with? % "-"))))
(s/def ::cmd-args (s/cat :options ::options
:args (s/?
(s/cat :config ::filename
:auth ::filename))))
(defn expound-config
[config]
(expound/expound ::core/config config))
(defn invalid-args-msg
[spec args]
(s/explain spec args)
(println (str "Bad commandline arguments\n" usage)))
(defn -main [& cmd-args]
(let [parsed-args-cmd (s/conform ::cmd-args cmd-args)]
(if (= ::s/invalid parsed-args-cmd)
(invalid-args-msg ::cmd-args cmd-args)
(let [{:keys [options args]} parsed-args-cmd
{:keys [config auth]} args]
(cond
(some #(= "-h" %) options)
(println usage)
:default
(let [config-str (slurp config)
auth-str (slurp auth)
config-edn (edn/read-string config-str)
auth-edn (edn/read-string auth-str)
config-valid? (s/valid? core/config? config-edn)
auth-valid? (s/valid? core/auth? auth-edn)]
(if (and config-valid? auth-valid?)
(println (core/generate config-edn auth-edn))
(do
(when (not config-valid?)
(println
(expound/expound-str core/config? config-edn {:print-specs? false})))
(when (not auth-valid?)
(println
(expound/expound-str core/auth? auth-edn {:print-specs? false})))))))))))

View file

@ -0,0 +1,20 @@
(ns dda.k8s-keycloak.yaml
(:require
[clojure.java.io :as io]
[clj-yaml.core :as yaml]
[clojure.walk]))
(defn cast-lazy-seq-to-vec
[lazy-seq]
(clojure.walk/postwalk #(if (instance? clojure.lang.LazySeq %)
(into [] %)
%) lazy-seq))
(defn load-resource [resource-name]
(slurp (io/resource resource-name)))
(defn from-string [input]
(cast-lazy-seq-to-vec (yaml/parse-string input)))
(defn to-string [edn]
(yaml/generate-string edn :dumper-options {:flow-style :block}))

View file

@ -1,57 +0,0 @@
(ns dda.c4k-keycloak.core
(:require
[clojure.spec.alpha :as s]
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.c4k-common.common :as cm]
[dda.c4k-common.predicate :as cp]
[dda.c4k-common.monitoring :as mon]
[dda.c4k-common.yaml :as yaml]
[dda.c4k-common.postgres :as postgres]
[dda.c4k-keycloak.keycloak :as kc]
[dda.c4k-common.namespace :as ns]))
(def default-storage-class :local-path)
(def config-defaults {:issuer "staging",
:namespace "keycloak"
:postgres-image "postgres:14"
:postgres-size :2gb
:db-name "keycloak"
:pv-storage-size-gb 30
:pvc-storage-class-name default-storage-class})
(def config? (s/keys :req-un [::kc/fqdn]
:opt-un [::kc/issuer
::mon/mon-cfg
::kc/namespace]))
(def auth? (s/keys :req-un [::kc/keycloak-admin-user ::kc/keycloak-admin-password
::postgres/postgres-db-user ::postgres/postgres-db-password]
:opt-un [::mon/mon-auth]))
(defn-spec config-objects cp/map-or-seq?
[config config?]
(map yaml/to-string
(filter
#(not (nil? %))
(cm/concat-vec
(ns/generate config)
(postgres/generate-config config)
[(kc/generate-service config)
(kc/generate-deployment config)]
(kc/generate-ingress config)
(when (contains? config :mon-cfg)
(mon/generate-config))))))
(defn-spec auth-objects cp/map-or-seq?
[config config?
auth auth?]
(map yaml/to-string
(filter
#(not (nil? %))
(cm/concat-vec
(postgres/generate-auth config auth)
[(kc/generate-secret config auth)]
(when (and (contains? auth :mon-auth) (contains? config :mon-cfg))
(mon/generate-auth (:mon-cfg config) (:mon-auth auth)))))))

View file

@ -1,64 +0,0 @@
(ns dda.c4k-keycloak.keycloak
(:require
[clojure.spec.alpha :as s]
#?(:cljs [dda.c4k-common.macros :refer-macros [inline-resources]])
#?(: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.base64 :as b64]
[dda.c4k-common.ingress :as ing]
[dda.c4k-common.predicate :as cp]))
(s/def ::fqdn cp/fqdn-string?)
(s/def ::namespace string?)
(s/def ::issuer cp/letsencrypt-issuer?)
(s/def ::keycloak-admin-user cp/bash-env-string?)
(s/def ::keycloak-admin-password cp/bash-env-string?)
(def config? (s/keys :req-un [::fqdn]
:opt-un [::issuer
::namespace]))
(def auth? (s/keys :req-un [::keycloak-admin-user
::keycloak-admin-password]))
#?(:cljs
(defmethod yaml/load-resource :keycloak [resource-name]
(get (inline-resources "keycloak") resource-name)))
(defn-spec generate-ingress cp/map-or-seq?
[config config?]
(ing/generate-ingress-and-cert
(merge
{:service-name "keycloak"
:service-port 80
:fqdns [(:fqdn config)]}
config)))
(defn-spec generate-secret cp/map-or-seq?
[config config?
auth auth?]
(let [{:keys [namespace]} config
{:keys [keycloak-admin-user keycloak-admin-password]} auth]
(->
(yaml/load-as-edn "keycloak/secret.yaml")
(cm/replace-all-matching "NAMESPACE" namespace)
(cm/replace-key-value :keycloak-user (b64/encode keycloak-admin-user))
(cm/replace-key-value :keycloak-password (b64/encode keycloak-admin-password)))))
(defn-spec generate-service cp/map-or-seq?
[config config?]
(let [{:keys [namespace]} config]
(->
(yaml/load-as-edn "keycloak/service.yaml")
(cm/replace-all-matching "NAMESPACE" namespace))))
(defn-spec generate-deployment cp/map-or-seq?
[config config?]
(let [{:keys [fqdn namespace]} config]
(->
(yaml/load-as-edn "keycloak/deployment.yaml")
(cm/replace-all-matching "NAMESPACE" namespace)
(cm/replace-all-matching "FQDN" fqdn))))

View file

@ -0,0 +1,104 @@
(ns dda.k8s-keycloak.core
(:require
[clojure.string :as cs]
[clojure.spec.alpha :as s]
#?(:clj [orchestra.core :refer [defn-spec]]
:cljs [orchestra.core :refer-macros [defn-spec]])
[dda.k8s-keycloak.yaml :as yaml]
[clojure.walk]))
(defn bash-env-string?
[input]
(and (string? input)
(not (re-matches #".*['\"\$]+.*" input))))
(defn fqdn-string?
[input]
(and (string? input)
(not (nil? (re-matches #"(?=^.{4,253}\.?$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63}\.?$)" input)))))
(s/def ::user-name bash-env-string?)
(s/def ::user-password string?)
(s/def ::fqdn fqdn-string?)
(s/def ::issuer #{:prod :staging})
(def config? (s/keys :req-un [::fqdn]
:opt-un [::issuer]))
(def auth? (s/keys :req-un [::user-name ::user-password]))
(defn replace-all-matching-values-by-new-value
[coll value-to-match value-to-replace]
(clojure.walk/postwalk #(if (and (= (type value-to-match) (type %))
(= value-to-match %))
value-to-replace
%) coll))
(defn generate-config [my-config my-auth]
(->
(yaml/from-string (yaml/load-resource "config.yaml"))
(assoc-in [:data :config.edn] (str my-config))
(assoc-in [:data :credentials.edn] (str my-auth))))
(defn generate-postgres-config []
(yaml/from-string (yaml/load-resource "postgres/postgres-config.yaml")))
(defn generate-deployment [my-auth]
(let [{:keys [user-name user-password]} my-auth]
(->
(yaml/from-string (yaml/load-resource "deployment.yaml"))
(assoc-in [:spec :template :spec :containers 0 :env 0 :value] user-name)
(assoc-in [:spec :template :spec :containers 0 :env 1 :value] user-password))))
(defn generate-postgres-deployment [my-auth]
(let [{:keys [postgres-user postgres-password postgres-db]} my-auth]
(->
(yaml/from-string (yaml/load-resource "postgres/postgres-deployment.yaml"))
(assoc-in [:spec :template :spec :containers 0 :env 0 :value] postgres-user)
(assoc-in [:spec :template :spec :containers 0 :env 1 :value] postgres-db)
(assoc-in [:spec :template :spec :containers 0 :env 2 :value] postgres-password))))
(defn generate-certificate [config]
(let [{:keys [fqdn issuer]
:or {issuer :staging}} config
letsencrypt-issuer (str "letsencrypt-" (name issuer) "-issuer")]
(->
(yaml/from-string (yaml/load-resource "certificate.yaml"))
(assoc-in [:spec :commonName] fqdn)
(assoc-in [:spec :dnsNames] [fqdn])
(assoc-in [:spec :issuerRef :name] letsencrypt-issuer))))
(defn generate-ingress [config]
(let [{:keys [fqdn issuer]
:or {issuer :staging}} config
letsencrypt-issuer (str "letsencrypt-" (name issuer) "-issuer")]
(->
(yaml/from-string (yaml/load-resource "ingress.yaml"))
(assoc-in [:metadata :annotations :cert-manager.io/cluster-issuer] letsencrypt-issuer)
(replace-all-matching-values-by-new-value "fqdn" fqdn))))
(defn generate-service []
(yaml/from-string (yaml/load-resource "service.yaml")))
(defn generate-postgres-service []
(yaml/from-string (yaml/load-resource "postgres/postgres-service.yaml")))
(defn-spec generate any?
[my-config config?
my-auth auth?]
(cs/join "\n"
[(yaml/to-string (generate-postgres-config))
"---"
(yaml/to-string (generate-postgres-service))
"---"
(yaml/to-string (generate-postgres-deployment my-auth))
"---"
(yaml/to-string (generate-config my-config my-auth))
"---"
(yaml/to-string (generate-certificate my-config))
"---"
(yaml/to-string (generate-ingress my-config))
"---"
(yaml/to-string (generate-service))
"---"
(yaml/to-string (generate-deployment my-auth))]))

View file

@ -1,88 +0,0 @@
(ns dda.c4k-keycloak.browser
(:require
[clojure.tools.reader.edn :as edn]
[dda.c4k-common.monitoring :as mon]
[dda.c4k-common.common :as cm]
[dda.c4k-common.browser :as br]
[dda.c4k-keycloak.core :as core]
[dda.c4k-keycloak.keycloak :as kc]))
(defn generate-content []
(cm/concat-vec
[(assoc
(br/generate-needs-validation) :content
(cm/concat-vec
(br/generate-group
"domain"
(cm/concat-vec
(br/generate-input-field "fqdn" "Your fqdn:" "keycloak.prod.meissa.de")
(br/generate-input-field "issuer" "(Optional) Your issuer prod/staging:" "")
(br/generate-input-field "mon-cluster-name" "(Optional) monitoring cluster name:" "keycloak")
(br/generate-input-field "mon-cluster-stage" "(Optional) monitoring cluster stage:" "test")
(br/generate-input-field "mon-cloud-url" "(Optional) grafana cloud url:" "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom/push")))
(br/generate-group
"credentials"
(br/generate-text-area "auth" "Your auth.edn:"
"{:keycloak-admin-user \"keycloak\"
:keycloak-admin-password \"adminpassword\"
:postgres-db-user \"keycloakuser\"
:postgres-db-password \"testdbpassword\"
:mon-auth {:grafana-cloud-user \"your-user-id\"
:grafana-cloud-password \"your-cloud-password\"}}"
"5"))
[(br/generate-br)]
(br/generate-button "generate-button" "Generate c4k yaml")))]
(br/generate-output "c4k-keycloak-output" "Your c4k deployment.yaml:" "25")))
(defn generate-content-div
[]
{:type :element
:tag :div
:content
(generate-content)})
(defn config-from-document []
(let [issuer (br/get-content-from-element "issuer" :optional true)
mon-cluster-name (br/get-content-from-element "mon-cluster-name" :optional true)
mon-cluster-stage (br/get-content-from-element "mon-cluster-stage" :optional true :deserializer keyword)
mon-cloud-url (br/get-content-from-element "mon-cloud-url" :optional true)]
(merge
{:fqdn (br/get-content-from-element "fqdn")}
(when (some? issuer)
{:issuer issuer})
(when (some? mon-cluster-name)
{:mon-cfg {:cluster-name mon-cluster-name
:cluster-stage (keyword mon-cluster-stage)
:grafana-cloud-url mon-cloud-url}}))))
(defn validate-all! []
(br/validate! "fqdn" ::kc/fqdn)
(br/validate! "issuer" ::kc/issuer :optional true)
(br/validate! "mon-cluster-name" ::mon/cluster-name :optional true)
(br/validate! "mon-cluster-stage" ::mon/cluster-stage :optional true :deserializer keyword)
(br/validate! "mon-cloud-url" ::mon/grafana-cloud-url :optional true)
(br/validate! "auth" core/auth? :deserializer edn/read-string)
(br/set-validated!))
(defn add-validate-listener [name]
(-> (br/get-element-by-id name)
(.addEventListener "blur" #(do (validate-all!)))))
(defn init []
(br/append-hickory (generate-content-div))
(-> js/document
(.getElementById "generate-button")
(.addEventListener "click"
#(do (validate-all!)
(-> (cm/generate-common
(config-from-document)
(br/get-content-from-element "auth" :deserializer edn/read-string)
{}
core/k8s-objects)
(br/set-output!)))))
(add-validate-listener "fqdn")
(add-validate-listener "issuer")
(add-validate-listener "mon-cluster-name")
(add-validate-listener "mon-cluster-stage")
(add-validate-listener "mon-cloud-url")
(add-validate-listener "auth"))

View file

@ -0,0 +1,99 @@
(ns dda.k8s-keycloak.browser
(:require
[clojure.spec.alpha :as s]
[clojure.tools.reader.edn :as edn]
[expound.alpha :as expound]
[dda.k8s-keycloak.core :as core]))
(defn print-debug [sth]
(print "debug " sth)
sth)
(defn config []
(-> js/document
(.getElementById "config")))
(defn auth []
(-> js/document
(.getElementById "auth")))
(defn form []
(-> js/document
(.getElementById "form")))
(defn config-from-document []
(-> (config)
(.-value)))
(defn auth-from-document []
(-> (auth)
(.-value)))
(defn set-output!
[input]
(-> js/document
(.getElementById "output")
(.-value)
(set! input)))
(defn set-config-validation-result!
[validation-result]
(-> js/document
(.getElementById "config-validation")
(.-innerHTML)
(set! validation-result))
(-> (config)
(.setCustomValidity validation-result))
validation-result)
(defn validate-config! []
(let [config-str (config-from-document)
config-map (edn/read-string config-str)]
(if (s/valid? core/config? config-map)
(set-config-validation-result! "")
(set-config-validation-result!
(expound/expound-str core/config? config-map {:print-specs? false})))))
(defn set-validated! []
(-> (form)
(.-classList)
(.add "was-validated")))
(defn set-auth-validation-result!
[validation-result]
(-> js/document
(.getElementById "auth-validation")
(.-innerHTML)
(set! validation-result))
(-> (auth)
(.setCustomValidity validation-result))
validation-result)
(defn validate-auth! []
(let [auth-str (auth-from-document)
auth-map (edn/read-string auth-str)]
(print-debug (s/valid? core/auth? auth-map))
(if (s/valid? core/auth? auth-map)
(set-auth-validation-result! "")
(set-auth-validation-result!
(expound/expound-str core/auth? auth-map {:print-specs? false})))))
(defn init []
(-> js/document
(.getElementById "generate-button")
(.addEventListener "click"
#(do (validate-config!)
(validate-auth!)
(set-validated!)
(-> (core/generate (config-from-document) (auth-from-document))
(set-output!)))))
(-> (config)
(.addEventListener "blur"
#(do (validate-config!)
(validate-auth!)
(set-validated!))))
(-> (auth)
(.addEventListener "blur"
#(do (validate-config!)
(validate-auth!)
(set-validated!)))))

View file

@ -0,0 +1,24 @@
(ns dda.k8s-keycloak.yaml
(:require
["js-yaml" :as yaml]
[shadow.resource :as rc]
))
(def config (rc/inline "config.yaml"))
(def cron (rc/inline "cron.yaml"))
(def deployment (rc/inline "deployment.yaml"))
(defn load-resource [resource-name]
(case resource-name
"config.yaml" config
"cron.yaml" cron
"deployment.yaml" deployment))
(defn from-string [input]
(js->clj (yaml/load input)
:keywordize-keys true))
(defn to-string [edn]
(yaml/dump (clj->js edn)))

View file

@ -0,0 +1,13 @@
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: keycloak-cert
namespace: default
spec:
secretName: keycloak-secret
commonName: fqdn
dnsNames:
- fqdn
issuerRef:
name: letsencrypt-staging-issuer
kind: ClusterIssuer

11
src/main/resources/config.yaml Executable file
View file

@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: keycloak
labels:
app.kubernetes.io/name: k8s-keycloak
data:
config.edn: |
some-config-value
credentials.edn: |
some-credentials-value

View file

@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
namespace: default
labels:
app: keycloak
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:13.0.0
env:
- name: KEYCLOAK_USER
value: "admin"
- name: KEYCLOAK_PASSWORD
value: "admin"
- name: PROXY_ADDRESS_FORWARDING
value: "true"
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /auth/realms/master
port: 8080

View file

@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-cloud
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging-issuer
nginx.ingress.kubernetes.io/proxy-body-size: "256m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
namespace: default
spec:
tls:
- hosts:
- fqdn
secretName: keycloak-secret
rules:
- host: fqdn
http:
paths:
- backend:
serviceName: keycloak
servicePort: 8080

View file

@ -1,80 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
namespace: NAMESPACE
labels:
app: keycloak
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:20.0.3
imagePullPolicy: IfNotPresent
args:
- start
volumeMounts:
- name: keycloak-cert
mountPath: /etc/certs
readOnly: true
env:
- name: KC_HTTPS_CERTIFICATE_FILE
value: /etc/certs/tls.crt
- name: KC_HTTPS_CERTIFICATE_KEY_FILE
value: /etc/certs/tls.key
- name: KC_HOSTNAME
value: FQDN
- name: KC_PROXY
value: edge
- name: DB_VENDOR
value: POSTGRES
- name: DB_ADDR
value: postgresql-service
- name: DB_SCHEMA
value: public
- name: DB_DATABASE
valueFrom:
configMapKeyRef:
name: postgres-config
key: postgres-db
- name: DB_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-user
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-password
- name: KEYCLOAK_ADMIN
valueFrom:
secretKeyRef:
name: keycloak-secret
key: keycloak-user
- name: KEYCLOAK_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-secret
key: keycloak-password
ports:
- name: http
containerPort: 8080
volumes:
- name: keycloak-cert
secret:
secretName: keycloak
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key

View file

@ -1,9 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: keycloak-secret
namespace: NAMESPACE
type: Opaque
data:
keycloak-user: admin
keycloak-password: admin

View file

@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
labels:
app: postgres
data:
postgresql.conf: |
max_connections = 1000
shared_buffers = 512MB

View file

@ -0,0 +1,38 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresql
spec:
selector:
matchLabels:
app: postgresql
strategy:
type: Recreate
template:
metadata:
labels:
app: postgresql
spec:
containers:
- image: postgres
name: postgresql
env:
- name: POSTGRES_USER
value: "psql-user"
- name: POSTGRES_DB
value: "psql-db"
- name: POSTGRES_PASSWORD
value: "psql-pw"
ports:
- containerPort: 5432
name: postgresql
cmd:
volumeMounts:
- name: postgres-config-volume
mountPath: /etc/postgresql/postgresql.conf
subPath: postgresql.conf
readOnly: true
volumes:
- name: postgres-config-volume
configMap:
name: postgres-config

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: Service
metadata:
name: postgresql-service
spec:
selector:
app: postgresql
ports:
- port: 5432

View file

@ -3,12 +3,12 @@ kind: Service
metadata: metadata:
name: keycloak name: keycloak
labels: labels:
service: keycloak app: keycloak
namespace: NAMESPACE
spec: spec:
ports: ports:
- name: "http" - name: http
port: 80 port: 8080
targetPort: 8080 targetPort: 8080
selector: selector:
app: keycloak app: keycloak
type: LoadBalancer

View file

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

View file

@ -1,78 +0,0 @@
(ns dda.c4k-keycloak.keycloak-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-keycloak.keycloak :as cut]))
(st/instrument)
(deftest should-generate-secret
(is (= {:apiVersion "v1"
:kind "Secret"
:metadata {:name "keycloak-secret", :namespace "keycloak"}
:type "Opaque"
:data
{:keycloak-user "dXNlcg=="
:keycloak-password "cGFzc3dvcmQ="}}
(cut/generate-secret {:namespace "keycloak" :fqdn "test.de"} {:keycloak-admin-user "user" :keycloak-admin-password "password"}))))
(deftest should-generate-deployment
(is (= {:apiVersion "apps/v1",
:kind "Deployment",
:metadata
{:name "keycloak", :namespace "keycloak", :labels {:app "keycloak"}},
:spec
{:replicas 1,
:selector {:matchLabels {:app "keycloak"}},
:template
{:metadata {:labels {:app "keycloak"}},
:spec
{:containers
[{:name "keycloak",
:image "quay.io/keycloak/keycloak:20.0.3",
:imagePullPolicy "IfNotPresent",
:args ["start"],
:volumeMounts
[{:name "keycloak-cert",
:mountPath "/etc/certs",
:readOnly true}],
:env
[{:name "KC_HTTPS_CERTIFICATE_FILE",
:value "/etc/certs/tls.crt"}
{:name "KC_HTTPS_CERTIFICATE_KEY_FILE",
:value "/etc/certs/tls.key"}
{:name "KC_HOSTNAME", :value "test.de"}
{:name "KC_PROXY", :value "edge"}
{:name "DB_VENDOR", :value "POSTGRES"}
{:name "DB_ADDR", :value "postgresql-service"}
{:name "DB_SCHEMA", :value "public"}
{:name "DB_DATABASE",
:valueFrom
{:configMapKeyRef
{:name "postgres-config", :key "postgres-db"}}}
{:name "DB_USER",
:valueFrom
{:secretKeyRef
{:name "postgres-secret", :key "postgres-user"}}}
{:name "DB_PASSWORD",
:valueFrom
{:secretKeyRef
{:name "postgres-secret", :key "postgres-password"}}}
{:name "KEYCLOAK_ADMIN",
:valueFrom
{:secretKeyRef
{:name "keycloak-secret", :key "keycloak-user"}}}
{:name "KEYCLOAK_ADMIN_PASSWORD",
:valueFrom
{:secretKeyRef
{:name "keycloak-secret", :key "keycloak-password"}}}],
:ports [{:name "http", :containerPort 8080}]}],
:volumes
[{:name "keycloak-cert",
:secret
{:secretName "keycloak",
:items
[{:key "tls.crt", :path "tls.crt"}
{:key "tls.key", :path "tls.key"}]}}]}}}}
(cut/generate-deployment {:fqdn "test.de" :namespace "keycloak"}))))

View file

@ -0,0 +1,112 @@
(ns dda.k8s-keycloak.core-test
(:require
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[dda.k8s-keycloak.core :as cut]
[dda.k8s-keycloak.yaml :as yaml]))
(deftest should-generate-yaml
(is (= {:apiVersion "v1", :kind "ConfigMap"
:metadata {:name "keycloak",
:labels {:app.kubernetes.io/name "k8s-keycloak"}},
:data {:config.edn "some-config-value\n",
:credentials.edn "some-credentials-value\n"}}
(cut/generate-config "some-config-value\n" "some-credentials-value\n"))))
(deftest should-generate-certificate
(is (= {:apiVersion "cert-manager.io/v1alpha2"
:kind "Certificate"
:metadata {:name "keycloak-cert", :namespace "default"}
:spec
{:secretName "keycloak-secret"
:commonName "test.de"
:dnsNames ["test.de"]
:issuerRef {:name "letsencrypt-prod-issuer", :kind "ClusterIssuer"}}}
(cut/generate-certificate {:fqdn "test.de" :issuer :prod} ))))
(deftest should-generate-ingress-yaml-with-default-issuer
(is (= {:apiVersion "networking.k8s.io/v1beta1"
:kind "Ingress"
:metadata
{:name "ingress-cloud"
:annotations
{:cert-manager.io/cluster-issuer "letsencrypt-staging-issuer"
:nginx.ingress.kubernetes.io/proxy-body-size "256m"
:nginx.ingress.kubernetes.io/ssl-redirect "true"
:nginx.ingress.kubernetes.io/rewrite-target "/"
:nginx.ingress.kubernetes.io/proxy-connect-timeout "300"
:nginx.ingress.kubernetes.io/proxy-send-timeout "300"
:nginx.ingress.kubernetes.io/proxy-read-timeout "300"}
:namespace "default"}
:spec
{:tls [{:hosts ["test.de"] :secretName "keycloak-secret"}]
:rules [{:host "test.de", :http {:paths [{:backend {:serviceName "keycloak", :servicePort 8080}}]}}]}}
(cut/generate-ingress {:fqdn "test.de"}))))
(deftest should-generate-ingress-yaml-with-prod-issuer
(is (= {:apiVersion "networking.k8s.io/v1beta1"
:kind "Ingress"
:metadata
{:name "ingress-cloud"
:annotations
{:cert-manager.io/cluster-issuer "letsencrypt-prod-issuer"
:nginx.ingress.kubernetes.io/proxy-body-size "256m"
:nginx.ingress.kubernetes.io/ssl-redirect "true"
:nginx.ingress.kubernetes.io/rewrite-target "/"
:nginx.ingress.kubernetes.io/proxy-connect-timeout "300"
:nginx.ingress.kubernetes.io/proxy-send-timeout "300"
:nginx.ingress.kubernetes.io/proxy-read-timeout "300"}
:namespace "default"}
:spec
{:tls [{:hosts ["test.de"], :secretName "keycloak-secret"}]
:rules '({:host "test.de", :http {:paths [{:backend {:serviceName "keycloak", :servicePort 8080}}]}})}}
(cut/generate-ingress {:fqdn "test.de"
:issuer :prod}))))
(deftest should-generate-deployment
(is (= {:apiVersion "apps/v1"
:kind "Deployment"
:metadata {:name "keycloak", :namespace "default", :labels {:app "keycloak"}}
:spec
{:replicas 1
:selector {:matchLabels {:app "keycloak"}}
:template
{:metadata {:labels {:app "keycloak"}}
:spec
{:containers
[{:name "keycloak"
:image "quay.io/keycloak/keycloak:13.0.0"
:env
[{:name "KEYCLOAK_USER", :value "testuser"}
{:name "KEYCLOAK_PASSWORD", :value "test1234"}
{:name "PROXY_ADDRESS_FORWARDING", :value "true"}]
:ports [{:name "http", :containerPort 8080}]
:readinessProbe {:httpGet {:path "/auth/realms/master", :port 8080}}}]}}}}
(cut/generate-deployment {:user-name "testuser" :user-password "test1234"}))))
(deftest should-generate-postgres-deployment
(is (= {:apiVersion "apps/v1"
:kind "Deployment"
:metadata {:name "postgresql"}
:spec
{:selector {:matchLabels {:app "postgresql"}}
:strategy {:type "Recreate"}
:template
{:metadata {:labels {:app "postgresql"}}
:spec
{:containers
[{:image "postgres"
:name "postgresql"
:env
[{:name "POSTGRES_USER", :value "psqluser"}
{:name "POSTGRES_DB", :value "keycloak"}
{:name "POSTGRES_PASSWORD", :value "test1234"}]
:ports [{:containerPort 5432, :name "postgresql"}]
:cmd nil
:volumeMounts
[{:name "postgres-config-volume"
:mountPath "/etc/postgresql/postgresql.conf"
:subPath "postgresql.conf"
:readOnly true}]}]
:volumes [{:name "postgres-config-volume", :configMap {:name "postgres-config"}}]}}}}
(cut/generate-postgres-deployment {:postgres-user "psqluser" :postgres-db "keycloak" :postgres-password "test1234"}))))

View file

@ -0,0 +1,22 @@
(ns dda.k8s-keycloak.yaml-test
(:require
#?(:clj [clojure.test :refer [deftest is are testing run-tests]]
:cljs [cljs.test :refer-macros [deftest is are testing run-tests]])
[dda.k8s-keycloak.yaml :as cut]))
(deftest should-parse-yaml-string
(is (= {:hallo "welt"}
(cut/from-string "hallo: welt"))))
(deftest should-generate-yaml-string
(is (= "hallo: welt
"
(cut/to-string {:hallo "welt"}))))
(deftest should-convert-config-yml-to-map
(is (= {:apiVersion "v1", :kind "ConfigMap"
:metadata {:name "keycloak",
:labels {:app.kubernetes.io/name "k8s-keycloak"}},
:data {:config.edn "some-config-value\n",
:credentials.edn "some-credentials-value\n"}}
(cut/from-string (cut/load-resource "config.yaml")))))

View file

@ -0,0 +1,31 @@
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-cloud
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging-issuer
nginx.ingress.kubernetes.io/proxy-body-size: "256m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
namespace: default
spec:
tls:
- hosts:
- fqdn
secretName: keycloak-secret
rules:
- host: fqdn
http:
paths:
- backend:
serviceName: keycloak
servicePort: 8080
- host: fqdn
http:
paths:
- backend:
serviceName: another_keycloak
servicePort: 8081

View file

@ -0,0 +1 @@
{:xx {}}

View file

@ -0,0 +1,27 @@
{:transform [{:source {:source-type :twitter
;; optional, defaults to false
:include-replies? false
;; optional, defaults to false
:include-rts? false
;; Replace Twitter links by Nitter
:nitter-urls? 42
;; accounts you wish to mirror
:accounts ["arstechnica" "WIRED"]}
:target {:target-type :mastodon
;; optional flag specifying wether the name of the account
;; will be appended in the post, defaults to false
:append-screen-name? false
;; optional visibility flag: direct, private, unlisted, public
;; defaults to public
:visibility "unlisted"
;; optional boolean to mark content as sensitive. Defaults to true.
:sensitive? true
;; optional boolean defaults to false
;; only sources containing media will be posted when set to true
:media-only? true
;; optional limit for the post length. Defaults to 300.
:max-post-length 300
;; optional signature for posts. Defaults to "not present".
:signature "#newsbot"}
}]
:auth {}}

View file

@ -1,7 +0,0 @@
keycloak-admin-user: "testuser"
keycloak-admin-password: "testpassword"
postgres-db-user: "keycloakuser"
postgres-db-password: "testdbpassword"
mon-auth:
grafana-clod-user: "user"
grafana-cloud-password: "password"

View file

@ -1,6 +0,0 @@
fqdn: "keycloak.test.meissa-gmbh.de"
issuer: "staging"
mon-cfg:
grafana-clod-url: "url-for-your-prom-remote-write-endpoint"
cluster-nam: "keycloak"
cluster-stage: "none"

View file

@ -1,7 +0,0 @@
keycloak-admin-user: "testuser"
keycloak-admin-password: "testpassword"
postgres-db-user: "keycloakuser"
postgres-db-password: "testdbpassword"
mon-auth:
grafana-cloud-user: "user"
grafana-cloud-password: "password"

View file

@ -1,6 +0,0 @@
fqdn: "keycloak.test.meissa-gmbh.de"
issuer: "staging"
mon-cfg:
grafana-cloud-url: "url-for-your-prom-remote-write-endpoint"
cluster-name: "keycloak"
cluster-stage: "test"

View file

@ -0,0 +1 @@
{:auth {}}

View file

@ -0,0 +1,27 @@
{:transform [{:source {:source-type :twitter
;; optional, defaults to false
:include-replies? false
;; optional, defaults to false
:include-rts? false
;; Replace Twitter links by Nitter
:nitter-urls? false
;; accounts you wish to mirror
:accounts ["arstechnica" "WIRED"]}
:target {:target-type :mastodon
;; optional flag specifying wether the name of the account
;; will be appended in the post, defaults to false
:append-screen-name? false
;; optional visibility flag: direct, private, unlisted, public
;; defaults to public
:visibility "unlisted"
;; optional boolean to mark content as sensitive. Defaults to true.
:sensitive? true
;; optional boolean defaults to false
;; only sources containing media will be posted when set to true
:media-only? true
;; optional limit for the post length. Defaults to 300.
:max-post-length 300
;; optional signature for posts. Defaults to "not present".
:signature "#newsbot"}
}]
:auth {}}

2
valid-auth.edn Normal file
View file

@ -0,0 +1,2 @@
{:user-name "testuser"
:user-password "test1234"}

1
valid-config.edn Normal file
View file

@ -0,0 +1 @@
{:fqdn "keycloak.test.meissa-gmbh.de"}