diff --git a/internal/controller/kustomization_controller.go b/internal/controller/kustomization_controller.go index 5432d672..b803df79 100644 --- a/internal/controller/kustomization_controller.go +++ b/internal/controller/kustomization_controller.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "os" + "regexp" "sort" "strings" "time" @@ -95,6 +96,7 @@ type KustomizationReconciler struct { DefaultServiceAccount string KubeConfigOpts runtimeClient.KubeConfigOptions ConcurrentSSA int + ImplicitSubstitutions bool } // KustomizationReconcilerOptions contains options for the KustomizationReconciler. @@ -104,6 +106,10 @@ type KustomizationReconcilerOptions struct { RateLimiter ratelimiter.RateLimiter } +var ( + refAndRevisionRE = regexp.MustCompile("([^@]+)@sha1:(.+)") +) + func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts KustomizationReconcilerOptions) error { const ( ociRepositoryIndexKey string = ".metadata.ociRepository" @@ -380,7 +386,7 @@ func (r *KustomizationReconciler) reconcile( } // Build the Kustomize overlay and decrypt secrets if needed. - resources, err := r.build(ctx, obj, unstructured.Unstructured{Object: k}, tmpDir, dirPath) + resources, err := r.build(ctx, obj, src.GetArtifact(), unstructured.Unstructured{Object: k}, tmpDir, dirPath) if err != nil { conditions.MarkFalse(obj, meta.ReadyCondition, kustomizev1.BuildFailedReason, err.Error()) return err @@ -573,7 +579,7 @@ func (r *KustomizationReconciler) generate(obj unstructured.Unstructured, } func (r *KustomizationReconciler) build(ctx context.Context, - obj *kustomizev1.Kustomization, u unstructured.Unstructured, + obj *kustomizev1.Kustomization, artifact *sourcev1.Artifact, u unstructured.Unstructured, workDir, dirPath string) ([]byte, error) { dec, cleanup, err := decryptor.NewTempDecryptor(workDir, r.Client, obj) if err != nil { @@ -617,6 +623,22 @@ func (r *KustomizationReconciler) build(ctx context.Context, } } + // add built-in substitutions + if r.ImplicitSubstitutions { + if obj.Spec.PostBuild == nil { + obj.Spec.PostBuild = &kustomizev1.PostBuild{} + } + if obj.Spec.PostBuild.Substitute == nil { + obj.Spec.PostBuild.Substitute = make(map[string]string) + } + obj.Spec.PostBuild.Substitute["FLUX_ARTIFACT_REVISION"] = artifact.Revision + matches := refAndRevisionRE.FindStringSubmatch(artifact.Revision) + if len(matches) == 3 { + obj.Spec.PostBuild.Substitute["FLUX_ARTIFACT_REF"] = matches[1] + obj.Spec.PostBuild.Substitute["FLUX_ARTIFACT_SHA"] = matches[2] + } + } + // run variable substitutions if obj.Spec.PostBuild != nil { outRes, err := generator.SubstituteVariables(ctx, r.Client, u, res, false) diff --git a/main.go b/main.go index 13e8ea91..7eba113f 100644 --- a/main.go +++ b/main.go @@ -75,6 +75,7 @@ func init() { func main() { var ( + implicitSubstitutions bool metricsAddr string eventsAddr string healthAddr string @@ -101,6 +102,8 @@ func main() { flag.IntVar(&concurrent, "concurrent", 4, "The number of concurrent kustomize reconciles.") flag.IntVar(&concurrentSSA, "concurrent-ssa", 4, "The number of concurrent server-side apply operations.") flag.DurationVar(&requeueDependency, "requeue-dependency", 30*time.Second, "The interval at which failing dependencies are reevaluated.") + flag.BoolVar(&implicitSubstitutions, "implicit-substitutions", false, + "Perform substitutions of built-in values such as last-attempted-revision; has side effects of ALWAYS performing substitutions!") flag.BoolVar(&noRemoteBases, "no-remote-bases", false, "Disallow remote bases usage in Kustomize overlays. When this flag is enabled, all resources must refer to local files included in the source artifact.") flag.IntVar(&httpRetry, "http-retry", 9, "The maximum number of retries when failing to fetch artifacts over HTTP.") @@ -223,6 +226,7 @@ func main() { Metrics: metricsH, EventRecorder: eventRecorder, NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs, + ImplicitSubstitutions: implicitSubstitutions, NoRemoteBases: noRemoteBases, FailFast: failFast, ConcurrentSSA: concurrentSSA,