diff --git a/api/v1/kustomization_types.go b/api/v1/kustomization_types.go
index 59a86c8a..6bd217c3 100644
--- a/api/v1/kustomization_types.go
+++ b/api/v1/kustomization_types.go
@@ -177,6 +177,13 @@ type KustomizationSpec struct {
// +optional
Wait bool `json:"wait,omitempty"`
+ // BuildMetadata specifies which kustomize build metadata should be added
+ // to the built resources. The allowed values are 'originAnnotations' to
+ // annotate resources with their source origin, and 'transformerAnnotations'
+ // to annotate resources with the transformers that produced them.
+ // +optional
+ BuildMetadata []BuildMetadataOption `json:"buildMetadata,omitempty"`
+
// Components specifies relative paths to kustomize Components.
// +optional
Components []string `json:"components,omitempty"`
@@ -194,6 +201,19 @@ type KustomizationSpec struct {
HealthCheckExprs []kustomize.CustomHealthCheck `json:"healthCheckExprs,omitempty"`
}
+// BuildMetadataOption defines the supported buildMetadata options.
+// +kubebuilder:validation:Enum=originAnnotations;transformerAnnotations
+type BuildMetadataOption string
+
+const (
+ // BuildMetadataOriginAnnotations enables config.kubernetes.io/origin annotations
+ // that track which file and path each resource was loaded from.
+ BuildMetadataOriginAnnotations BuildMetadataOption = "originAnnotations"
+ // BuildMetadataTransformerAnnotations enables internal.config.kubernetes.io annotations
+ // that record which kustomize transformers modified each resource.
+ BuildMetadataTransformerAnnotations BuildMetadataOption = "transformerAnnotations"
+)
+
// CommonMetadata defines the common labels and annotations.
type CommonMetadata struct {
// Annotations to be added to the object's metadata.
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index e8122756..231b5b52 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -222,6 +222,11 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) {
*out = new(metav1.Duration)
**out = **in
}
+ if in.BuildMetadata != nil {
+ in, out := &in.BuildMetadata, &out.BuildMetadata
+ *out = make([]BuildMetadataOption, len(*in))
+ copy(*out, *in)
+ }
if in.Components != nil {
in, out := &in.Components, &out.Components
*out = make([]string, len(*in))
diff --git a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml
index ceda2cb4..ccda40e1 100644
--- a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml
+++ b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml
@@ -53,6 +53,20 @@ spec:
KustomizationSpec defines the configuration to calculate the desired state
from a Source using Kustomize.
properties:
+ buildMetadata:
+ description: |-
+ BuildMetadata specifies which kustomize build metadata should be added
+ to the built resources. The allowed values are 'originAnnotations' to
+ annotate resources with their source origin, and 'transformerAnnotations'
+ to annotate resources with the transformers that produced them.
+ items:
+ description: BuildMetadataOption defines the supported buildMetadata
+ options.
+ enum:
+ - originAnnotations
+ - transformerAnnotations
+ type: string
+ type: array
commonMetadata:
description: |-
CommonMetadata specifies the common labels and annotations that are
diff --git a/docs/api/v1/kustomize.md b/docs/api/v1/kustomize.md
index 582dc5f4..11624c6c 100644
--- a/docs/api/v1/kustomize.md
+++ b/docs/api/v1/kustomize.md
@@ -385,6 +385,23 @@ resources. When enabled, the HealthChecks are ignored. Defaults to false.
+buildMetadata
+
+
+[]BuildMetadataOption
+
+
+ |
+
+(Optional)
+ BuildMetadata specifies which kustomize build metadata should be added
+to the built resources. The allowed values are ‘originAnnotations’ to
+annotate resources with their source origin, and ‘transformerAnnotations’
+to annotate resources with the transformers that produced them.
+ |
+
+
+
components
[]string
@@ -444,6 +461,13 @@ KustomizationStatus
+
+
+(Appears on:
+KustomizationSpec)
+
+BuildMetadataOption defines the supported buildMetadata options.
@@ -1018,6 +1042,23 @@ resources. When enabled, the HealthChecks are ignored. Defaults to false.
|
+buildMetadata
+
+
+[]BuildMetadataOption
+
+
+ |
+
+(Optional)
+ BuildMetadata specifies which kustomize build metadata should be added
+to the built resources. The allowed values are ‘originAnnotations’ to
+annotate resources with their source origin, and ‘transformerAnnotations’
+to annotate resources with the transformers that produced them.
+ |
+
+
+
components
[]string
diff --git a/docs/spec/v1/kustomizations.md b/docs/spec/v1/kustomizations.md
index 420f8000..ff9a7eaa 100644
--- a/docs/spec/v1/kustomizations.md
+++ b/docs/spec/v1/kustomizations.md
@@ -649,6 +649,41 @@ spec:
digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
```
+### Build metadata
+
+`.spec.buildMetadata` is an optional list used to specify which
+[Kustomize `buildMetadata`](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/buildmetadata/)
+options should be added to the built resources. The allowed values are:
+
+- `originAnnotations`: Adds `config.kubernetes.io/origin` annotations that
+ track which file and path each resource was loaded from.
+- `transformerAnnotations`: Adds `internal.config.kubernetes.io` annotations
+ that record which kustomize transformers modified each resource.
+
+```yaml
+apiVersion: kustomize.toolkit.fluxcd.io/v1
+kind: Kustomization
+metadata:
+ name: podinfo
+ namespace: flux-system
+spec:
+ # ...omitted for brevity
+ buildMetadata:
+ - originAnnotations
+```
+
+When `originAnnotations` is enabled, each resource gets an annotation like:
+
+```yaml
+metadata:
+ annotations:
+ config.kubernetes.io/origin: |
+ path: apps/deployment.yaml
+```
+
+This is useful for debugging, auditing, and tooling that needs to trace
+resources back to their source files.
+
### Components
`.spec.components` is an optional list used to specify
diff --git a/go.mod b/go.mod
index 04a5a4cb..ad1218f4 100644
--- a/go.mod
+++ b/go.mod
@@ -27,7 +27,7 @@ require (
github.com/fluxcd/pkg/auth v0.40.0
github.com/fluxcd/pkg/cache v0.13.0
github.com/fluxcd/pkg/http/fetch v0.22.0
- github.com/fluxcd/pkg/kustomize v1.28.0
+ github.com/fluxcd/pkg/kustomize v1.29.0
github.com/fluxcd/pkg/runtime v0.103.0
github.com/fluxcd/pkg/ssa v0.70.0
github.com/fluxcd/pkg/tar v0.17.0
diff --git a/go.sum b/go.sum
index 8363d183..bfef6a9f 100644
--- a/go.sum
+++ b/go.sum
@@ -219,8 +219,8 @@ github.com/fluxcd/pkg/envsubst v1.5.0 h1:S07mo+MkGhptdHA4pRze5HPKlc8tHxKswNdcMZi
github.com/fluxcd/pkg/envsubst v1.5.0/go.mod h1:c3a8DYI855sZUubHFYQbjfjop6Wu4/zg1cLyf7SnCes=
github.com/fluxcd/pkg/http/fetch v0.22.0 h1:FT8CfstPE/e7+KRxNrx8ZJ1Uj5rkR5wXOtvQJurNQ0U=
github.com/fluxcd/pkg/http/fetch v0.22.0/go.mod h1:X+8wF3peP79TyyDSgCJiavz+fAcYaf7CRXSeu7ccsPA=
-github.com/fluxcd/pkg/kustomize v1.28.0 h1:0RuFVczJRabbt8frHZ/ql8aqte6BOOKk274O09l6/hE=
-github.com/fluxcd/pkg/kustomize v1.28.0/go.mod h1:cW08mnngSP8MJYb6mDmMvxH8YjNATdiML0udb37dk+M=
+github.com/fluxcd/pkg/kustomize v1.29.0 h1:B/5hr9wX6INwaQAZ6BGKVNvZm++A6qjgorUfoaBAwPw=
+github.com/fluxcd/pkg/kustomize v1.29.0/go.mod h1:cW08mnngSP8MJYb6mDmMvxH8YjNATdiML0udb37dk+M=
github.com/fluxcd/pkg/runtime v0.103.0 h1:J5y5GPhWdkyqIUBlaI1FP2N02TtZmsjbWhhZubuTSFk=
github.com/fluxcd/pkg/runtime v0.103.0/go.mod h1:mbo2f3azo3yVQgm7XZGxQB6/2zvzQ5Wgtd8TjRRwwAw=
github.com/fluxcd/pkg/sourceignore v0.17.0 h1:Z72nruRMhC15zIEpWoDrAcJcJ1El6QDnP/aRDfE4WOA=
diff --git a/internal/controller/kustomization_transformer_test.go b/internal/controller/kustomization_transformer_test.go
index ac1f7adf..954885d1 100644
--- a/internal/controller/kustomization_transformer_test.go
+++ b/internal/controller/kustomization_transformer_test.go
@@ -604,3 +604,104 @@ func checkSecret(list *corev1.SecretList, name string) bool {
return false
}
+
+func TestKustomizationReconciler_BuildMetadata(t *testing.T) {
+ g := NewWithT(t)
+ id := "bm-" + randStringRunes(5)
+ revision := "v1.0.0"
+ resultK := &kustomizev1.Kustomization{}
+
+ err := createNamespace(id)
+ g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
+
+ err = createKubeConfigSecret(id)
+ g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
+
+ manifests := func(name string) []testserver.File {
+ return []testserver.File{
+ {
+ Name: "config.yaml",
+ Body: fmt.Sprintf(`---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: %[1]s
+data:
+ key: val
+`, name),
+ },
+ }
+ }
+
+ artifact, err := testServer.ArtifactFromFiles(manifests(id))
+ g.Expect(err).NotTo(HaveOccurred())
+
+ repositoryName := types.NamespacedName{
+ Name: fmt.Sprintf("bm-%s", randStringRunes(5)),
+ Namespace: id,
+ }
+
+ err = applyGitRepository(repositoryName, artifact, revision)
+ g.Expect(err).NotTo(HaveOccurred())
+
+ kustomizationKey := types.NamespacedName{
+ Name: fmt.Sprintf("bm-%s", randStringRunes(5)),
+ Namespace: id,
+ }
+ kustomization := &kustomizev1.Kustomization{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: kustomizationKey.Name,
+ Namespace: kustomizationKey.Namespace,
+ },
+ Spec: kustomizev1.KustomizationSpec{
+ Interval: metav1.Duration{Duration: 2 * time.Minute},
+ Path: "./",
+ KubeConfig: &meta.KubeConfigReference{
+ SecretRef: &meta.SecretKeyReference{
+ Name: "kubeconfig",
+ },
+ },
+ SourceRef: kustomizev1.CrossNamespaceSourceReference{
+ Name: repositoryName.Name,
+ Namespace: repositoryName.Namespace,
+ Kind: sourcev1.GitRepositoryKind,
+ },
+ Prune: true,
+ BuildMetadata: []kustomizev1.BuildMetadataOption{
+ kustomizev1.BuildMetadataOriginAnnotations,
+ },
+ TargetNamespace: id,
+ },
+ }
+
+ g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
+
+ t.Run("sets origin annotations", func(t *testing.T) {
+ g := NewWithT(t)
+ g.Eventually(func() bool {
+ _ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
+ return isReconcileSuccess(resultK)
+ }, timeout, time.Second).Should(BeTrue())
+ kstatusCheck.CheckErr(ctx, resultK)
+
+ var cm corev1.ConfigMap
+ g.Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: id, Namespace: id}, &cm)).To(Succeed())
+ g.Expect(cm.GetAnnotations()).To(HaveKey("config.kubernetes.io/origin"))
+ })
+
+ t.Run("removes origin annotations", func(t *testing.T) {
+ g := NewWithT(t)
+ resultK.Spec.BuildMetadata = nil
+ g.Expect(k8sClient.Update(context.Background(), resultK)).To(Succeed())
+
+ g.Eventually(func() bool {
+ _ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
+ return isReconcileSuccess(resultK)
+ }, timeout, time.Second).Should(BeTrue())
+ kstatusCheck.CheckErr(ctx, resultK)
+
+ var cm corev1.ConfigMap
+ g.Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: id, Namespace: id}, &cm)).To(Succeed())
+ g.Expect(cm.GetAnnotations()).ToNot(HaveKey("config.kubernetes.io/origin"))
+ })
+}
|