diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca00915 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# pybuilder +.pybuilder/ +__pycache__/ + +# lein +target/ +.lein-repl-history +.lein-failures +pom.* + +# cljs +.shadow-cljs +.nrepl-* +package-lock.json +node_modules/ +public/js/ + +# ide +.calva + +*.iml +.idea/ + +valid-auth.edn +valid-config.edn +my-auth.edn diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..5a60c57 --- /dev/null +++ b/project.clj @@ -0,0 +1,28 @@ +(defproject org.domaindrivenarchitecture/c4k-common "0.1.0-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.10.3"] + [org.clojure/tools.reader "1.3.4"] + [aero "1.1.6"] + [orchestra "2021.01.01-1"] + [expound "0.8.9"] + [clj-commons/clj-yaml "0.7.106"]] + :source-paths ["src/main/cljc" + "src/main/clj"] + :resource-paths ["src/main/resources"] + :repositories [["snapshots" :clojars] + ["releases" :clojars]] + :deploy-repositories [["snapshots" :clojars] + ["releases" :clojars]] + :profiles {:test {:test-paths ["src/test/cljc"] + :resource-paths ["src/test/resources"] + :dependencies [[dda/data-test "0.1.1"]]} + :dev {:plugins [[lein-shell "0.5.0"]]}} + :release-tasks [["vcs" "assert-committed"] + ["change" "version" "leiningen.release/bump-version" "release"] + ["vcs" "commit"] + ["vcs" "tag"] + ["deploy"] + ["change" "version" "leiningen.release/bump-version"]]) diff --git a/src/main/clj/dda/c4k_keycloak/base64.clj b/src/main/clj/dda/c4k_keycloak/base64.clj new file mode 100644 index 0000000..4f9bb50 --- /dev/null +++ b/src/main/clj/dda/c4k_keycloak/base64.clj @@ -0,0 +1,14 @@ +(ns dda.c4k-keycloak.base64 + (:import (java.util Base64))) + +(defn encode + [string] + (.encodeToString + (Base64/getEncoder) + (.getBytes string "UTF-8"))) + +(defn decode + [string] + (String. + (.decode (Base64/getDecoder) string) + "UTF-8")) \ No newline at end of file diff --git a/src/main/clj/dda/c4k_keycloak/yaml.clj b/src/main/clj/dda/c4k_keycloak/yaml.clj new file mode 100644 index 0000000..c05db95 --- /dev/null +++ b/src/main/clj/dda/c4k_keycloak/yaml.clj @@ -0,0 +1,20 @@ +(ns dda.c4k-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})) \ No newline at end of file diff --git a/src/main/cljc/dda/c4k_keycloak/common.cljc b/src/main/cljc/dda/c4k_keycloak/common.cljc new file mode 100644 index 0000000..7beee60 --- /dev/null +++ b/src/main/cljc/dda/c4k_keycloak/common.cljc @@ -0,0 +1,41 @@ +(ns dda.c4k-keycloak.common + (:require + [clojure.walk])) + +(defn bash-env-string? + [input] + (and (string? input) + (not (re-matches #".*['\"\$]+.*" input)))) + +(defn 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 letsencrypt-issuer? + [input] + (contains? #{:prod :staging} input)) + +(defn replace-named-value + [coll name value] + (clojure.walk/postwalk #(if (and (map? %) + (= name (:name %))) + {:name name :value value} + %) + coll)) + +(defn replace-key-value + [coll key value] + (clojure.walk/postwalk #(if (and (map? %) + (contains? % key)) + (assoc % key value) + %) + coll)) + +(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)) diff --git a/src/main/cljs/dda/c4k_keycloak/base64.cljs b/src/main/cljs/dda/c4k_keycloak/base64.cljs new file mode 100644 index 0000000..8c1caa0 --- /dev/null +++ b/src/main/cljs/dda/c4k_keycloak/base64.cljs @@ -0,0 +1,11 @@ +(ns dda.c4k-keycloak.base64 + (:require + ["js-base64" :as b64])) + +(defn encode + [string] + (.encode b64/Base64 string)) + +(defn decode + [string] + (.decode b64/Base64 string)) diff --git a/src/main/cljs/dda/c4k_keycloak/browser.cljs b/src/main/cljs/dda/c4k_keycloak/browser.cljs new file mode 100644 index 0000000..bd0296c --- /dev/null +++ b/src/main/cljs/dda/c4k_keycloak/browser.cljs @@ -0,0 +1,70 @@ +(ns dda.c4k-keycloak.browser + (:require + [clojure.string :as st] + [clojure.spec.alpha :as s] + [clojure.tools.reader.edn :as edn] + [expound.alpha :as expound] + [orchestra.core :refer-macros [defn-spec]] + [dda.c4k-keycloak.core :as core] + [dda.c4k-keycloak.keycloak :as kc])) + +(defn-spec print-debug string? + [sth string?] + (print "debug " sth) + sth) + +(defn-spec get-element-by-id any? + [name string?] + (-> js/document + (.getElementById name))) + +(s/def ::deserializer fn?) +(s/def ::optional boolean?) +(defn-spec get-content-from-element any? + [name string? + & {:keys [deserializer optional] + :or {deserializer nil optional false}} (s/keys :opt-un [::deserializer ::optional])] + (let [content (-> (get-element-by-id name) + (.-value))] + (cond + (and optional (some? deserializer)) + (when-not (st/blank? content) + (apply deserializer [content])) + (and (false? optional) (some? deserializer)) + (apply deserializer [content]) + :else + content))) + +(defn-spec set-validation-result! any? + [name string? + validation-result any?] + (-> (get-element-by-id (str name "-validation")) + (.-innerHTML) + (set! validation-result)) + (-> (get-element-by-id name) + (.setCustomValidity validation-result)) + validation-result) + +(defn-spec validate! any? + [name string? + spec any? + & {:keys [deserializer optional] + :or {deserializer nil optional false}} (s/keys :opt-un [::deserializer ::optional])] + (let [content (get-content-from-element name :optional optional :deserializer deserializer)] + (if (or (and optional (st/blank? content)) + (s/valid? spec content)) + (set-validation-result! name "") + (set-validation-result! name + (expound/expound-str spec content {:print-specs? false}))))) + +(defn set-output! + [input] + (-> js/document + (.getElementById "output") + (.-value) + (set! input))) + +(defn set-validated! [] + (-> (get-element-by-id "form") + (.-classList) + (.add "was-validated"))) \ No newline at end of file diff --git a/src/main/cljs/dda/c4k_keycloak/yaml.cljs b/src/main/cljs/dda/c4k_keycloak/yaml.cljs new file mode 100644 index 0000000..34ab7a0 --- /dev/null +++ b/src/main/cljs/dda/c4k_keycloak/yaml.cljs @@ -0,0 +1,16 @@ +(ns dda.c4k-keycloak.yaml + (:require + ["js-yaml" :as yaml] + [shadow.resource :as rc])) + +(defn load-resource [resource-name] + (case resource-name + "ingress_test.yaml" (rc/inline "ingress_test.yaml") + (throw (js/Error. "Undefined Resource!")))) + +(defn from-string [input] + (js->clj (yaml/load input) + :keywordize-keys true)) + +(defn to-string [edn] + (yaml/dump (clj->js edn))) \ No newline at end of file diff --git a/src/test/cljc/dda/c4k_keycloak/yaml_test.cljc b/src/test/cljc/dda/c4k_keycloak/yaml_test.cljc new file mode 100644 index 0000000..cb31273 --- /dev/null +++ b/src/test/cljc/dda/c4k_keycloak/yaml_test.cljc @@ -0,0 +1,45 @@ +(ns dda.c4k-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.c4k-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 "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}}]}}]}} + (cut/from-string (cut/load-resource "ingress_test.yaml"))))) diff --git a/src/test/resources/ingress_test.yaml b/src/test/resources/ingress_test.yaml new file mode 100644 index 0000000..ed5d4b0 --- /dev/null +++ b/src/test/resources/ingress_test.yaml @@ -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 \ No newline at end of file