From 2ea3b31cd58623b78ec018a508323c9cef957a48 Mon Sep 17 00:00:00 2001 From: GigiaJ Date: Wed, 8 Oct 2025 23:59:12 -0500 Subject: [PATCH] Finish revising the resource creations to allow dynamic prop assignments to enable adjusting even the default behavior if needed --- iac/src/main/utils/k8s.cljs | 185 ++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 82 deletions(-) diff --git a/iac/src/main/utils/k8s.cljs b/iac/src/main/utils/k8s.cljs index 2f7c771..2c822b0 100644 --- a/iac/src/main/utils/k8s.cljs +++ b/iac/src/main/utils/k8s.cljs @@ -11,6 +11,11 @@ (defn assoc-ins [m path-vals] (reduce (fn [acc [path val]] (assoc-in acc path val)) m path-vals)) +(defn deep-merge [a b] + (merge-with (fn [x y] + (if (map? y) (deep-merge x y) y)) + a b)) + (defn make-transformer "Given f that takes {:app-name .. :secrets ..}, where :secrets is a plain map (already unwrapped inside .apply), return a Helm transformer." @@ -24,73 +29,81 @@ after (clj->js (assoc-ins base-values updates))] after))))) -(defn create-secret [provider app-namespace app-name secrets-data] - (new (.. k8s -core -v1 -Secret) (str app-name "-secrets") - (clj->js {:metadata {:name (str app-name "-secrets") - :namespace app-namespace} - :stringData secrets-data}) - (clj->js {:provider provider}))) - -(defn create-image [app-name] - (let [context-path (.. path (join "." (-> cfg :resource-path))) - dockerfile-path (.. path (join context-path (str app-name ".dockerfile")))] - (new (.. docker -Image) app-name - (clj->js {:build {:context context-path - :dockerfile dockerfile-path} - :imageName (str (-> cfg :docker-repo) "/" app-name ":latest")})))) - -(defn create-ns [provider app-namespace] - (new (.. k8s -core -v1 -Namespace) app-namespace - (clj->js {:metadata {:name app-namespace}}) - (clj->js {:provider provider}))) - -(defn create-chart [provider app-namespace app-name chart-repo helm-fn dependencies transformations ] - (new (.. k8s -helm -v3 -Chart) app-name - (clj->js {:chart app-name - :fetchOpts {:repo chart-repo} - :namespace app-namespace - :values helm-fn - :transformations (if transformations [transformations] [])}) - (clj->js {:provider provider +(defn new-resource [resource-type resource-name final-args provider dependencies] + (new resource-type resource-name + (clj->js final-args) + (clj->js {:provider provider :enableServerSideApply false - :dependsOn dependencies}))) + :dependsOn dependencies}))) -(defn create-deployment [provider app-namespace app-name app-labels image image-port dependencies] - (new (.. k8s -apps -v1 -Deployment) app-name - (clj->js {:metadata {:namespace app-namespace - :name app-name} - :spec {:selector {:matchLabels app-labels} - :replicas 1 - :template {:metadata {:labels app-labels} - :spec {:containers - [{:name app-name - :image image - :ports [{:containerPort image-port}]}]}}}}) - (clj->js {:provider provider - :dependsOn dependencies}))) -(defn create-service [provider app-namespace app-name app-labels image-port dependencies] - (new (.. k8s -core -v1 -Service) app-name - (clj->js {:metadata {:namespace app-namespace - :name app-name} - :spec {:selector app-labels - :ports [{:port 80 :targetPort image-port}]}}) - (clj->js {:provider provider :dependsOn dependencies}))) +(defn create-secret [provider app-namespace app-name dependencies secret-options] + (let [base-args {:metadata {:name (str app-name "-secrets") + :namespace app-namespace}} + final-args (deep-merge base-args secret-options)] + (new-resource (.. k8s -core -v1 -Secret) app-name final-args provider dependencies))) -(defn create-ingress [provider app-namespace app-name full-snippet host image-port dependencies] - (new (.. k8s -networking -v1 -Ingress) app-name - (clj->js - {:metadata {:name app-name - :namespace app-namespace - :annotations {"caddy.ingress.kubernetes.io/snippet" full-snippet}} - :spec - {:ingressClassName "caddy" - :rules [{:host host - :http {:paths [{:path "/" - :pathType "Prefix" - :backend {:service {:name app-name - :port {:number image-port}}}}]}}]}}) - (clj->js {:provider provider :dependsOn dependencies}))) +(defn create-image [app-name image-options] + (let [context-path (.. path (join "." (-> cfg :resource-path))) + dockerfile-path (.. path (join context-path (str app-name ".dockerfile"))) + base-args {:build {:context context-path + :dockerfile dockerfile-path} + :imageName (str (-> cfg :docker-repo) "/" app-name ":latest")} + final-args (deep-merge base-args image-options)] + (new (.. docker -Image) app-name (clj->js final-args)))) + +(defn create-namespace [provider app-namespace dependencies ns-options] + (let [base-args {:metadata {:name app-namespace}} + final-args (deep-merge base-args ns-options)] + (new-resource (.. k8s -core -v1 -Namespace) app-namespace final-args provider dependencies))) + +(defn create-deployment [provider app-namespace app-name app-labels image-name image-port dependencies deployment-options] + (let [base-args {:metadata {:namespace app-namespace + :name app-name} + :spec {:selector {:matchLabels app-labels} + :replicas 1 + :template {:metadata {:labels app-labels} + :spec {:containers + [{:name app-name + :image image-name + :ports [{:containerPort image-port}]}]}}}} + final-args (deep-merge base-args deployment-options)] + (new-resource (.. k8s -apps -v1 -Deployment) app-name final-args provider dependencies))) + +(defn create-service [provider app-namespace app-name app-labels image-port dependencies service-options] + (let [base-args {:metadata {:namespace app-namespace + :name app-name} + :spec {:selector app-labels + :ports [{:port 80 :targetPort image-port}]}} + final-args (deep-merge base-args service-options)] + (new-resource (.. k8s -core -v1 -Service) app-name final-args provider dependencies))) + +(defn create-chart [provider app-namespace app-name dependencies chart-options] + (let [base-args {:chart app-name + :namespace app-namespace} + final-args (deep-merge base-args chart-options)] + (new-resource (.. k8s -helm -v3 -Chart) app-name final-args provider dependencies))) + +(defn create-ingress [provider app-namespace app-name host image-port dependencies ingress-options] + (let [base-args {:metadata {:name app-name + :namespace app-namespace + :annotations {"caddy.ingress.kubernetes.io/snippet" + (str "tls {\n dns cloudflare {env.CLOUDFLARE_API_TOKEN}\n}\n" (:caddy-snippet ingress-options))}} + :spec + {:ingressClassName "caddy" + :rules [{:host host + :http {:paths [{:path "/" + :pathType "Prefix" + :backend {:service {:name app-name + :port {:number image-port}}}}]}}]} + } + final-args (deep-merge base-args ingress-options)] + (new-resource (.. k8s -networking -v1 -Ingress) app-name final-args provider dependencies))) + +(defn create-storage-class [provider app-name dependencies storage-options] + (let [base-args {:metadata {:name app-name}} + final-args (deep-merge base-args storage-options)] + (new-resource (.. k8s -storage -v1 -StorageClass) app-name final-args provider dependencies))) (defn deploy-stack "Deploys a versatile stack of K8s resources, including optional Helm charts." @@ -98,12 +111,11 @@ (let [[component-kws [options]] (split-with keyword? args) requested-components (set component-kws) - {:keys [provider vault-provider pulumi-cfg app-namespace app-name image image-port caddy-snippet vault-load-yaml chart-repo transformations exec-fn helm-values-fn] - :or {vault-load-yaml false image-port 80 caddy-snippet "" helm-values-fn #(:base-values %) transformations nil}} options + {:keys [provider vault-provider pulumi-cfg app-namespace app-name image image-port vault-load-yaml exec-fn + storage-class-opts secret-opts ns-opts image-opts ingress-opts service-opts deployment-opts chart-opts] + :or {vault-load-yaml false image-port 80}} options app-labels {:app app-name} - full-snippet (str "tls {\n dns cloudflare {env.CLOUDFLARE_API_TOKEN}\n}\n" caddy-snippet) - prepared-vault-data (when (requested-components :vault-secrets) (vault-utils/prepare {:provider provider :vault-provider vault-provider @@ -113,38 +125,47 @@ {:keys [secrets yaml-values bind-secrets]} (or prepared-vault-data {:secrets nil :yaml-values nil :bind-secrets nil}) - - helm-fn (helm-values-fn {:base-values yaml-values - :secrets (if (some? prepared-vault-data) secrets nil) - :app-name app-name}) - host (when secrets (.apply secrets #(aget % "host"))) ns (when (requested-components :namespace) - (create-ns provider app-namespace)) + (create-namespace provider app-namespace nil ns-opts)) docker-image (when (requested-components :docker-image) - (create-image app-name)) + (create-image app-name image-opts)) - image (if (some? docker-image) (str (-> cfg :docker-repo) "/" app-name ":latest") image) + secret (when (requested-components :secret) + (create-secret provider app-namespace app-name nil secret-opts)) + storage-class (when (requested-components :storage-class) + (create-storage-class provider app-name nil storage-class-opts)) + + image (if (some? image) image (str (-> cfg :docker-repo) "/" app-name ":latest")) chart (when (requested-components :chart) - (create-chart provider app-namespace app-name chart-repo helm-fn (vec (filter some? [ns docker-image bind-secrets])) transformations)) + (create-chart provider app-namespace app-name (vec (filter some? [ns docker-image bind-secrets])) + (let [helm-values-fn (get chart-opts :helm-values-fn (fn [ctx] (:base-values ctx))) + context {:base-values yaml-values + :secrets (if (some? prepared-vault-data) secrets nil) + :app-name app-name} + calculated-values (helm-values-fn context) + transformations-fn (if (get chart-opts :transformations) [(get chart-opts :transformations)] [])] + (-> chart-opts + (assoc :values calculated-values) + (assoc :transformations transformations-fn))))) deployment (when (requested-components :deployment) - (create-deployment provider app-namespace app-name app-labels image image-port [docker-image])) + (create-deployment provider app-namespace app-name app-labels image image-port [docker-image] deployment-opts)) service (when (requested-components :service) - (create-service provider app-namespace app-name app-labels image-port [deployment])) + (create-service provider app-namespace app-name app-labels image-port [deployment] service-opts)) app-dependency (or service chart bind-secrets) - ingress (when (requested-components :ingress) (create-ingress provider app-namespace app-name full-snippet host image-port [app-dependency])) - + ingress (when (requested-components :ingress) (create-ingress provider app-namespace app-name host image-port [app-dependency] ingress-opts)) + execute (when (requested-components :execute) - (exec-fn (assoc options :dependencies (vec (filter some? [chart ns deployment service ingress docker-image])))))] + (exec-fn (assoc options :dependencies (vec (filter some? [chart ns secret storage-class deployment service ingress docker-image])))))] - {:namespace ns, :vault-secrets prepared-vault-data, :chart chart, :deployment deployment, :service service, :ingress ingress :execute execute})) \ No newline at end of file + {:namespace ns, :vault-secrets prepared-vault-data, :secret secret, :docker-image docker-image :storage-class storage-class, :chart chart, :deployment deployment, :service service, :ingress ingress :execute execute})) \ No newline at end of file