(ns pulumicljs.execution.providers (:require ["@pulumi/pulumi" :as pulumi] ["@pulumi/vault" :as vault] ["@pulumiverse/harbor" :as harbor] ["@pulumi/kubernetes" :as k8s] [clojure.string :as str] [clojure.walk :as walk] [pulumicljs.execution.general :refer [resolve-template]] [pulumicljs.providers.k8s :as k8s-utils] [pulumicljs.providers.harbor :as harbor-utils] [pulumicljs.providers.docker :as docker-utils] [pulumicljs.providers.vault :as vault-utils] [pulumicljs.execution.stack-processor :refer [deploy! component-specs]])) (defn resolve-provider-template [constructor name config] {:constructor constructor :name name :config config}) (def provider-templates (into {} (map (fn [[k v]] [k (apply resolve-provider-template (vals v))]) {:vault vault-utils/provider-template :harbor harbor-utils/provider-template :k8s k8s-utils/provider-template}))) (defn get-stack-refs [stack-ref-array] (into {} (map (fn [stack-name] [(keyword stack-name) (new pulumi/StackReference stack-name)]) stack-ref-array))) (defn extract-expanded-keywords [stack] (let [expand-chain (fn [chain] (when (and (sequential? chain) (keyword? (first chain))) (let [ns (or (namespace (first chain)) (name (first chain)))] (map #(keyword ns (name %)) (rest chain)))))] (mapcat (fn [item] (cond (and (sequential? item) (keyword? (first item))) (expand-chain item) (keyword? item) [item] :else nil)) stack))) (defn get-all-providers [resource-configs] (->> resource-configs (mapcat (comp extract-expanded-keywords :stack)) (map (fn [component-key] (if-let [ns (namespace component-key)] (keyword ns) (let [k-name (name component-key) parts (str/split k-name #":")] (when (> (count parts) 1) (keyword (first parts))))))) (remove nil?) (into #{}) vec)) (def provider-rules {:k8s k8s-utils/pre-deploy-rule}) (defn provider-apply [stack-resources-definition pulumi-cfg] (let [providers-needed (get-all-providers (:resource-configs stack-resources-definition)) provider-outputs-config (:provider-external-inputs stack-resources-definition) stack-refs (get-stack-refs (:stack-references stack-resources-definition)) needed-output-configs (select-keys provider-outputs-config providers-needed) ;; At some point we should add the ability for Providers to be passed Pulumi configs or our config map? ;; Cloudflare and others may require or request a token. outputs-to-fetch (reduce-kv (fn [acc _provider-key data] (let [stack-key (:stack data) stack-ref (get stack-refs stack-key) outputs (:outputs data)] (reduce (fn [m output-name] (assoc m (keyword output-name) (.getOutput stack-ref output-name))) acc outputs))) {} needed-output-configs) all-provider-inputs (pulumi/all (clj->js outputs-to-fetch))] (.apply all-provider-inputs (fn [values] (js/Promise. (fn [resolve _reject] (let [resolved-outputs (js->clj values :keywordize-keys true) instantiated-providers (reduce (fn [acc provider-key] (if-let [template (get provider-templates provider-key)] (let [constructor (:constructor template) provider-name (:name template) resolved-config (resolve-template (:config template) {} resolved-outputs)] (assoc acc provider-key (new constructor provider-name (clj->js resolved-config)))) acc)) {} providers-needed) pre-deploy-results (reduce-kv (fn [acc provider-key provider-instance] (if-let [rule-fn (get provider-rules provider-key)] (let [rule-results (rule-fn {:resource-configs (:resource-configs stack-resources-definition) :provider provider-instance})] (assoc acc provider-key rule-results)) acc)) {} instantiated-providers)] (resolve (deploy! {:pulumi-cfg pulumi-cfg :resource-configs (:resource-configs stack-resources-definition) :all-providers instantiated-providers :pre-deploy-deps pre-deploy-results}))))))))) (defn execute [stack-resources-definition exports] (-> (let [pulumi-cfg (pulumi/Config.)] (provider-apply stack-resources-definition pulumi-cfg)) (exports) (clj->js)))