Update to allow 1:N resource generators if the default fn specifies such

This commit is contained in:
2025-11-22 17:05:37 -06:00
parent 392f852081
commit fd6afdd4ec

View File

@@ -1,12 +1,14 @@
(ns utils.stack-processor (ns utils.stack-processor
(:require (:require
["@pulumi/kubernetes" :as k8s] ["@pulumi/kubernetes" :as k8s]
["@local/crds/gateway" :as gateway-api]
["@local/crds/cert_manager" :as cert-manager]
["@pulumi/pulumi" :as pulumi] ["@pulumi/pulumi" :as pulumi]
["@pulumi/vault" :as vault] ["@pulumi/vault" :as vault]
["@pulumiverse/harbor" :as harbor] ["@pulumiverse/harbor" :as harbor]
[utils.defaults :as default] [utils.defaults :as default]
[utils.vault :as vault-utils] [utils.vault :as vault-utils]
[utils.general :refer [deep-merge new-resource component-factory resource-factory deploy-stack-factory iterate-stack]] [utils.general :refer [deep-merge new-resource create-expander resource-factory deploy-stack-factory iterate-stack]]
["@pulumi/docker" :as docker] ["@pulumi/docker" :as docker]
["@pulumi/docker-build" :as docker-build] ["@pulumi/docker-build" :as docker-build]
[clojure.walk :as walk] [clojure.walk :as walk]
@@ -15,8 +17,9 @@
[configs :refer [cfg]] [configs :refer [cfg]]
[utils.k8s :as k8s-utils] [utils.k8s :as k8s-utils]
[utils.harbor :as harbor-utils] [utils.harbor :as harbor-utils]
[utils.docker :as docker-utils]) [utils.docker :as docker-utils]
(:require-macros [utils.general :refer [build-registry]])) [utils.safe-fns :refer [safe-fns]])
(:require-macros [utils.general :refer [p-> build-registry]]))
#_(def component-specs-defs #_(def component-specs-defs
@@ -26,15 +29,32 @@
#_(def component-specs (build-registry component-specs-defs)) #_(def component-specs (build-registry component-specs-defs))
(defn make-paths [& path-groups]
(mapcat (fn [{:keys [paths backend]}]
(mapv (fn [p]
{:path p
:pathType "Prefix"
:backend {:service backend}})
paths))
path-groups))
;; We should move this to a list of safe-fns that extend the below inherently. That way we don't bloat this file.
(defn make-listeners [domains-or-json]
(let [domains (if (string? domains-or-json)
(js->clj (js/JSON.parse domains-or-json))
domains-or-json)]
(vec
(mapcat
(fn [domain]
(let [clean-name (clojure.string/replace domain #"\." "-")
secret-name (str clean-name "-tls")]
[{:name (str "https-root-" clean-name)
:port 443
:protocol "HTTPS"
:hostname domain
:tls {:mode "Terminate"
:certificateRefs [{:name secret-name}]}}
{:name (str "https-wild-" clean-name)
:port 443
:protocol "HTTPS"
:hostname (str "*." domain)
:tls {:mode "Terminate"
:certificateRefs [{:name secret-name}]}}]))
domains))))
(defn safe-parse-int [s] (defn safe-parse-int [s]
(let [n (js/parseInt s 10)] (let [n (js/parseInt s 10)]
@@ -49,12 +69,6 @@
(safe-parse-int v) (safe-parse-int v)
v)) v))
;; Whitelist functions for resolving templates. Intended to be extended.
(def ^:private safe-fns
{'str str
'b64e (fn [s] (-> (.from js/Buffer s) (.toString "base64")))
'println #(js/console.log %)
'make-paths make-paths})
(defn- is-output? [x] (some? (.-__pulumiOutput x))) (defn- is-output? [x] (some? (.-__pulumiOutput x)))
@@ -128,7 +142,7 @@
:k8s:secret {:constructor (.. k8s -core -v1 -Secret) :k8s:secret {:constructor (.. k8s -core -v1 -Secret)
:provider-key :k8s :provider-key :k8s
:defaults-fn (fn [env] ((get-in default/defaults [:k8s :secret]) (:options env)))} :defaults-fn (fn [env] ((get-in default/defaults [:k8s :secret]) (:options env)))}
:k8s:config-map {:constructor (.. k8s -core -v1 -ConfigMap) :k8s:config-map {:constructor (.. k8s -core -v1 -ConfigMap)
:provider-key :k8s :provider-key :k8s
:defaults-fn (fn [env] ((get-in default/defaults [:k8s :config-map]) (:options env)))} :defaults-fn (fn [env] ((get-in default/defaults [:k8s :config-map]) (:options env)))}
@@ -147,13 +161,46 @@
:k8s:chart {:constructor (.. k8s -helm -v3 -Chart) :k8s:chart {:constructor (.. k8s -helm -v3 -Chart)
:provider-key :k8s :provider-key :k8s
:defaults-fn (fn [env] :defaults-fn (fn [env]
(deep-merge ((get-in default/defaults [:k8s :chart]) (:options env)) (deep-merge ((get-in default/defaults [:k8s :chart]) (:options env))
(update-in (get-in (:options env) [:k8s:chart-opts]) [:values] (update-in (get-in (:options env) [:k8s:chart-opts]) [:values]
#(deep-merge % (or (:yaml-values (:options env)) {})))))} #(deep-merge % (or (:yaml-values (:options env)) {})))))}
:k8s:storage-class {:constructor (.. k8s -storage -v1 -StorageClass) :k8s:storage-class {:constructor (.. k8s -storage -v1 -StorageClass)
:provider-key :k8s
:defaults-fn (fn [env] ((get-in default/defaults [:k8s :storage-class]) (:options env)))}
:k8s:gateway {:constructor (.. gateway-api -v1 -Gateway)
:provider-key :k8s
:defaults-fn (fn [env] ((get-in default/defaults [:k8s :gateway]) (:options env)))}
:k8s:httproute {:constructor (.. gateway-api -v1 -HTTPRoute)
:provider-key :k8s :provider-key :k8s
:defaults-fn (fn [env] ((get-in default/defaults [:k8s :storage-class]) (:options env)))} :defaults-fn (fn [env] ((get-in default/defaults [:k8s :httproute]) (:options env)))}
:k8s:cluster-issuer {:constructor (.. cert-manager -v1 -ClusterIssuer)
:provider-key :k8s
:defaults-fn (fn [env] ((get-in default/defaults [:k8s :cluster-issuer]) (:options env)))}
:k8s:certificates
{:constructor (.. cert-manager -v1 -Certificate)
:provider-key :k8s
:defaults-fn (fn [env]
(let [{:keys [app-namespace is-prod?]} (:options env)]
(p-> env :options :vault:prepare "stringData" .-domains
#(vec
(for [domain (js/JSON.parse %)]
(let [clean-name (clojure.string/replace domain #"\." "-")]
{:_suffix clean-name
:metadata {:namespace app-namespace}
:spec {:dnsNames [domain (str "*." domain)]
:secretName (str clean-name "-tls")
:issuerRef {:name (if is-prod? "letsencrypt-prod" "letsencrypt-staging")
:kind "ClusterIssuer"}}}))))))}
:k8s:certificate
{:constructor (.. cert-manager -v1 -Certificate)
:provider-key :k8s
:defaults-fn (fn [env] ((get-in default/defaults [:k8s :certificate]) (:options env)))}
;; Docker Resources ;; Docker Resources
:docker:image {:constructor (.. docker-build -Image) :docker:image {:constructor (.. docker-build -Image)
@@ -164,7 +211,7 @@
:harbor:project {:constructor (.. harbor -Project) :harbor:project {:constructor (.. harbor -Project)
:provider-key :harbor :provider-key :harbor
:defaults-fn (fn [env] ((get-in default/defaults [:harbor :project]) (:options env)))} :defaults-fn (fn [env] ((get-in default/defaults [:harbor :project]) (:options env)))}
:harbor:robot-account {:constructor (.. harbor -RobotAccount) :harbor:robot-account {:constructor (.. harbor -RobotAccount)
:provider-key :harbor :provider-key :harbor
:defaults-fn (fn [env] ((get-in default/defaults [:harbor :robot-account]) (:options env)))}}) :defaults-fn (fn [env] ((get-in default/defaults [:harbor :robot-account]) (:options env)))}})
@@ -176,31 +223,55 @@
(fn [dispatch-key _config] dispatch-key)) (fn [dispatch-key _config] dispatch-key))
(defmethod deploy-resource :default (defmethod deploy-resource :default
[dispatch-key [dispatch-key full-config]
full-config]
(if-let [spec (get component-specs dispatch-key)] (if-let [spec (get component-specs dispatch-key)]
(let [app-name (:app-name full-config) (let [app-name (:app-name full-config)
dependsOn (:dependsOn full-config) dependsOn (:dependsOn full-config)
provider-key (:provider-key spec) provider-key (:provider-key spec)
provider (get full-config provider-key) provider (get full-config provider-key)
resource-class (:constructor spec)
opts-key (keyword (str (name dispatch-key) "-opts")) opts-key (keyword (str (name dispatch-key) "-opts"))
component-opts (get full-config opts-key) component-opts (get full-config opts-key)
env {:options full-config :secrets (:secrets full-config)} env {:options full-config :secrets (:secrets full-config) :component-opts component-opts}
defaults (when-let [defaults-fn (:defaults-fn spec)] raw-defaults (when-let [df (:defaults-fn spec)] (df env))]
(defaults-fn env))
resource-class (:constructor spec)] (if resource-class
(let [base-creator (fn [final-args suffix]
(if resource-class (let [final-name (if suffix
(let [creator-fn (fn [final-args] (str app-name "-" suffix)
(new-resource resource-class app-name)]
app-name (new-resource resource-class
final-args final-name
provider final-args
dependsOn)) provider
resource (generic-transform creator-fn component-opts defaults (:secrets env) full-config)] dependsOn)))]
{:resource
(p-> raw-defaults
#(let [defaults-list (if (vector? %)
%
[%])
is-multi? (vector? %) resources
(doall
(map-indexed
(fn [idx item]
(let [suffix (cond
(:_suffix item) (:_suffix item)
is-multi? (str idx)
:else nil)
clean-item (dissoc item :_suffix)
item-creator (fn [resolved-args]
(base-creator resolved-args suffix))]
(generic-transform item-creator
component-opts
clean-item
(:secrets env)
full-config)))
defaults-list))]
(if is-multi? resources (first resources))))})
{:resource resource})
(throw (js/Error. (str "No :constructor found for spec: " dispatch-key))))) (throw (js/Error. (str "No :constructor found for spec: " dispatch-key)))))
(throw (js/Error. (str "Unknown resource: " dispatch-key))))) (throw (js/Error. (str "Unknown resource: " dispatch-key)))))
@@ -334,17 +405,17 @@
stack-items)] stack-items)]
(:resources-map result))) (:resources-map result)))
(defn deploy! [{:keys [pulumi-cfg service-registry all-providers]}] (defn deploy! [{:keys [pulumi-cfg resource-configs all-providers]}]
(let [ (let [
deployment-results deployment-results
(into (into
{} {}
(for [config service-registry] (for [config resource-configs]
(let [ (let [
{:keys [stack app-name]} config {:keys [stack app-name]} config
_ (when (nil? config) _ (when (nil? config)
(throw (js/Error. "Service registry contains a nil value!"))) (throw (js/Error. "Resource configs contain a nil value!")))
common-opts (merge common-opts (merge
all-providers all-providers