From 964bb38ba2c4057bd618b77e85e2e154de2adf43 Mon Sep 17 00:00:00 2001 From: Natasha Sarkar Date: Thu, 23 Dec 2021 10:40:29 -0800 Subject: [PATCH] add origin annotation for resources generated by generators (#4341) * add origin annotation for resources generated by builtin and custom generators * decouple origin data from generator data and account for inline generators --- api/internal/plugins/loader/loader.go | 10 +- api/internal/target/kusttarget.go | 48 +- .../target/kusttarget_configplugin.go | 27 +- api/krusty/buildmetadata_test.go | 549 ++++++++++++++++++ api/resmap/resmap.go | 12 + api/resmap/reswrangler.go | 28 + api/resource/origin.go | 41 +- api/resource/resource.go | 23 + 8 files changed, 717 insertions(+), 21 deletions(-) diff --git a/api/internal/plugins/loader/loader.go b/api/internal/plugins/loader/loader.go index 3fff946bb..838e126db 100644 --- a/api/internal/plugins/loader/loader.go +++ b/api/internal/plugins/loader/loader.go @@ -53,14 +53,18 @@ func (l *Loader) SetWorkDir(wd string) { } func (l *Loader) LoadGenerators( - ldr ifc.Loader, v ifc.Validator, rm resmap.ResMap) ([]resmap.Generator, error) { - var result []resmap.Generator + ldr ifc.Loader, v ifc.Validator, rm resmap.ResMap) ( + result []*resmap.GeneratorWithProperties, err error) { for _, res := range rm.Resources() { g, err := l.LoadGenerator(ldr, v, res) if err != nil { return nil, err } - result = append(result, g) + generatorOrigin, err := resource.OriginFromCustomPlugin(res) + if err != nil { + return nil, err + } + result = append(result, &resmap.GeneratorWithProperties{Generator: g, Origin: generatorOrigin}) } return result, nil } diff --git a/api/internal/target/kusttarget.go b/api/internal/target/kusttarget.go index 7cd2ef8c3..30dbbde89 100644 --- a/api/internal/target/kusttarget.go +++ b/api/internal/target/kusttarget.go @@ -29,6 +29,7 @@ import ( // KustTarget encapsulates the entirety of a kustomization build. type KustTarget struct { kustomization *types.Kustomization + kustFileName string ldr ifc.Loader validator ifc.Validator rFactory *resmap.Factory @@ -53,7 +54,7 @@ func NewKustTarget( // Load attempts to load the target's kustomization file. func (kt *KustTarget) Load() error { - content, err := loadKustFile(kt.ldr) + content, kustFileName, err := loadKustFile(kt.ldr) if err != nil { return err } @@ -74,6 +75,7 @@ func (kt *KustTarget) Load() error { strings.Join(errs, "\n"), kt.ldr.Root()) } kt.kustomization = &k + kt.kustFileName = kustFileName return nil } @@ -85,23 +87,25 @@ func (kt *KustTarget) Kustomization() types.Kustomization { return result } -func loadKustFile(ldr ifc.Loader) ([]byte, error) { +func loadKustFile(ldr ifc.Loader) ([]byte, string, error) { var content []byte match := 0 + var kustFileName string for _, kf := range konfig.RecognizedKustomizationFileNames() { c, err := ldr.Load(kf) if err == nil { match += 1 content = c + kustFileName = kf } } switch match { case 0: - return nil, NewErrMissingKustomization(ldr.Root()) + return nil, "", NewErrMissingKustomization(ldr.Root()) case 1: - return content, nil + return content, kustFileName, nil default: - return nil, fmt.Errorf( + return nil, "", fmt.Errorf( "Found multiple kustomization files under: %s\n", ldr.Root()) } } @@ -206,7 +210,7 @@ func (kt *KustTarget) accumulateTarget(ra *accumulator.ResAccumulator, origin *r return nil, errors.Wrapf( err, "merging CRDs %v", crdTc) } - err = kt.runGenerators(ra) + err = kt.runGenerators(ra, origin) if err != nil { return nil, err } @@ -244,23 +248,31 @@ func (kt *KustTarget) IgnoreLocal(ra *accumulator.ResAccumulator) error { } func (kt *KustTarget) runGenerators( - ra *accumulator.ResAccumulator) error { - var generators []resmap.Generator - gs, err := kt.configureBuiltinGenerators() + ra *accumulator.ResAccumulator, origin *resource.Origin) error { + var generators []*resmap.GeneratorWithProperties + gs, err := kt.configureBuiltinGenerators(origin) if err != nil { return err } generators = append(generators, gs...) - gs, err = kt.configureExternalGenerators() + + gs, err = kt.configureExternalGenerators(origin) if err != nil { return errors.Wrap(err, "loading generator plugins") } generators = append(generators, gs...) - for _, g := range generators { + + for i, g := range generators { resMap, err := g.Generate() if err != nil { return err } + if resMap != nil { + err = resMap.AddOriginAnnotation(generators[i].Origin) + if err != nil { + return errors.Wrapf(err, "adding origin annotations for generator %v", g) + } + } err = ra.AbsorbAll(resMap) if err != nil { return errors.Wrapf(err, "merging from generator %v", g) @@ -269,7 +281,8 @@ func (kt *KustTarget) runGenerators( return nil } -func (kt *KustTarget) configureExternalGenerators() ([]resmap.Generator, error) { +func (kt *KustTarget) configureExternalGenerators(origin *resource.Origin) ( + []*resmap.GeneratorWithProperties, error) { ra := accumulator.MakeEmptyAccumulator() var generatorPaths []string for _, p := range kt.kustomization.Generators { @@ -279,10 +292,19 @@ func (kt *KustTarget) configureExternalGenerators() ([]resmap.Generator, error) // not an inline config generatorPaths = append(generatorPaths, p) continue + } else { + // inline config, track the origin + if origin != nil { + resources := rm.Resources() + for _, r := range resources { + r.SetOrigin(origin.Append(kt.kustFileName)) + rm.Replace(r) + } + } } ra.AppendAll(rm) } - ra, err := kt.accumulateResources(ra, generatorPaths, &resource.Origin{}) + ra, err := kt.accumulateResources(ra, generatorPaths, origin) if err != nil { return nil, err } diff --git a/api/internal/target/kusttarget_configplugin.go b/api/internal/target/kusttarget_configplugin.go index ba9b705c0..9da09fcd9 100644 --- a/api/internal/target/kusttarget_configplugin.go +++ b/api/internal/target/kusttarget_configplugin.go @@ -5,11 +5,14 @@ package target import ( "fmt" + "path/filepath" "sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig" "sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers" "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/yaml" ) // Functions dedicated to configuring the builtin @@ -27,8 +30,8 @@ import ( // image tag transforms. In these cases, we'll need // N plugin instances with differing configurations. -func (kt *KustTarget) configureBuiltinGenerators() ( - result []resmap.Generator, err error) { +func (kt *KustTarget) configureBuiltinGenerators(origin *resource.Origin) ( + result []*resmap.GeneratorWithProperties, err error) { for _, bpt := range []builtinhelpers.BuiltinPluginType{ builtinhelpers.ConfigMapGenerator, builtinhelpers.SecretGenerator, @@ -39,7 +42,25 @@ func (kt *KustTarget) configureBuiltinGenerators() ( if err != nil { return nil, err } - result = append(result, r...) + + var generatorOrigin *resource.Origin + if origin != nil { + generatorOrigin = &resource.Origin{ + Repo: origin.Repo, + Ref: origin.Ref, + ConfiguredIn: filepath.Join(origin.Path, kt.kustFileName), + ConfiguredBy: yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{ + APIVersion: "builtin", + Kind: bpt.String(), + }, + }, + } + } + + for i := range r { + result = append(result, &resmap.GeneratorWithProperties{Generator: r[i], Origin: generatorOrigin}) + } } return result, nil } diff --git a/api/krusty/buildmetadata_test.go b/api/krusty/buildmetadata_test.go index 7644ff372..2c6bf4e96 100644 --- a/api/krusty/buildmetadata_test.go +++ b/api/krusty/buildmetadata_test.go @@ -4,10 +4,16 @@ package krusty_test import ( + "os" + "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/api/internal/utils" + "sigs.k8s.io/kustomize/api/krusty" _ "sigs.k8s.io/kustomize/api/krusty" kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" + "sigs.k8s.io/kustomize/kyaml/filesys" ) func TestAnnoOriginLocalFiles(t *testing.T) { @@ -263,3 +269,546 @@ metadata: type: Opaque `) } + +func TestAnnoOriginLocalBuiltinGenerator(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteK(".", ` +resources: +- service.yaml +configMapGenerator: +- name: bob + literals: + - fruit=Indian Gooseberry + - year=2020 + - crisis=true +buildMetadata: [originAnnotations] + +`) + th.WriteF("service.yaml", ` +apiVersion: v1 +kind: Service +metadata: + name: demo +spec: + clusterIP: None +`) + m := th.Run(".", th.MakeDefaultOptions()) + th.AssertActualEqualsExpected( + m, ` +apiVersion: v1 +kind: Service +metadata: + annotations: + config.kubernetes.io/origin: | + path: service.yaml + name: demo +spec: + clusterIP: None +--- +apiVersion: v1 +data: + crisis: "true" + fruit: Indian Gooseberry + year: "2020" +kind: ConfigMap +metadata: + annotations: + config.kubernetes.io/origin: | + configuredIn: kustomization.yaml + configuredBy: + apiVersion: builtin + kind: ConfigMapGenerator + name: bob-79t79mt227 +`) +} + +func TestAnnoOriginConfigMapGeneratorMerge(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteK("base", ` +configMapGenerator: +- name: bob + literals: + - fruit=Indian Gooseberry + - year=2020 + - crisis=true +`) + th.WriteK("overlay", ` +resources: +- ../base +configMapGenerator: +- name: bob + behavior: merge + literals: + - month=12 +buildMetadata: [originAnnotations] +`) + m := th.Run("overlay", th.MakeDefaultOptions()) + th.AssertActualEqualsExpected(m, `apiVersion: v1 +data: + crisis: "true" + fruit: Indian Gooseberry + month: "12" + year: "2020" +kind: ConfigMap +metadata: + annotations: + config.kubernetes.io/origin: | + configuredIn: ../base/kustomization.yaml + configuredBy: + apiVersion: builtin + kind: ConfigMapGenerator + name: bob-bk46gm59c6 +`) +} + +func TestAnnoOriginConfigMapGeneratorReplace(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteK("base", ` +configMapGenerator: +- name: bob + literals: + - fruit=Indian Gooseberry + - year=2020 + - crisis=true +`) + th.WriteK("overlay", ` +resources: +- ../base +configMapGenerator: +- name: bob + behavior: replace + literals: + - month=12 +buildMetadata: [originAnnotations] +`) + m := th.Run("overlay", th.MakeDefaultOptions()) + th.AssertActualEqualsExpected(m, `apiVersion: v1 +data: + month: "12" +kind: ConfigMap +metadata: + annotations: + config.kubernetes.io/origin: | + configuredIn: kustomization.yaml + configuredBy: + apiVersion: builtin + kind: ConfigMapGenerator + name: bob-f8t5fhtbhc +`) +} + +func TestAnnoOriginCustomExecGenerator(t *testing.T) { + fSys := filesys.MakeFsOnDisk() + + th := kusttest_test.MakeHarnessWithFs(t, fSys) + o := th.MakeOptionsPluginsEnabled() + o.PluginConfig.FnpLoadingOptions.EnableExec = true + + tmpDir, err := filesys.NewTmpConfirmedDir() + assert.NoError(t, err) + th.WriteK(tmpDir.String(), ` +resources: +- short_secret.yaml +generators: +- gener.yaml +buildMetadata: [originAnnotations] +`) + + // Create some additional resource just to make sure everything is added + th.WriteF(filepath.Join(tmpDir.String(), "short_secret.yaml"), + ` +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +type: Opaque +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +`) + th.WriteF(filepath.Join(tmpDir.String(), "generateDeployment.sh"), generateDeploymentDotSh) + + assert.NoError(t, os.Chmod(filepath.Join(tmpDir.String(), "generateDeployment.sh"), 0777)) + th.WriteF(filepath.Join(tmpDir.String(), "gener.yaml"), ` +kind: executable +metadata: + name: demo + annotations: + config.kubernetes.io/function: | + exec: + path: ./generateDeployment.sh +spec: +`) + + m := th.Run(tmpDir.String(), o) + assert.NoError(t, err) + yml, err := m.AsYaml() + assert.NoError(t, err) + assert.Equal(t, `apiVersion: v1 +kind: Secret +metadata: + annotations: + config.kubernetes.io/origin: | + path: short_secret.yaml + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +type: Opaque +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + config.kubernetes.io/origin: | + configuredIn: gener.yaml + configuredBy: + kind: executable + name: demo + tshirt-size: small + labels: + app: nginx + name: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx +`, string(yml)) + assert.NoError(t, fSys.RemoveAll(tmpDir.String())) +} + +func TestAnnoOriginCustomInlineExecGenerator(t *testing.T) { + fSys := filesys.MakeFsOnDisk() + + th := kusttest_test.MakeHarnessWithFs(t, fSys) + o := th.MakeOptionsPluginsEnabled() + o.PluginConfig.FnpLoadingOptions.EnableExec = true + + tmpDir, err := filesys.NewTmpConfirmedDir() + assert.NoError(t, err) + th.WriteK(tmpDir.String(), ` +resources: +- short_secret.yaml +generators: +- |- + kind: executable + metadata: + name: demo + annotations: + config.kubernetes.io/function: | + exec: + path: ./generateDeployment.sh + spec: +buildMetadata: [originAnnotations] +`) + + // Create some additional resource just to make sure everything is added + th.WriteF(filepath.Join(tmpDir.String(), "short_secret.yaml"), + ` +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +type: Opaque +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +`) + th.WriteF(filepath.Join(tmpDir.String(), "generateDeployment.sh"), generateDeploymentDotSh) + assert.NoError(t, os.Chmod(filepath.Join(tmpDir.String(), "generateDeployment.sh"), 0777)) + m := th.Run(tmpDir.String(), o) + assert.NoError(t, err) + yml, err := m.AsYaml() + assert.NoError(t, err) + assert.Equal(t, `apiVersion: v1 +kind: Secret +metadata: + annotations: + config.kubernetes.io/origin: | + path: short_secret.yaml + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +type: Opaque +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + config.kubernetes.io/origin: | + configuredIn: kustomization.yaml + configuredBy: + kind: executable + name: demo + tshirt-size: small + labels: + app: nginx + name: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx +`, string(yml)) + assert.NoError(t, fSys.RemoveAll(tmpDir.String())) +} + +func TestAnnoOriginCustomExecGeneratorWithOverlay(t *testing.T) { + fSys := filesys.MakeFsOnDisk() + + th := kusttest_test.MakeHarnessWithFs(t, fSys) + o := th.MakeOptionsPluginsEnabled() + o.PluginConfig.FnpLoadingOptions.EnableExec = true + + tmpDir, err := filesys.NewTmpConfirmedDir() + assert.NoError(t, err) + base := filepath.Join(tmpDir.String(), "base") + prod := filepath.Join(tmpDir.String(), "prod") + assert.NoError(t, fSys.Mkdir(base)) + assert.NoError(t, fSys.Mkdir(prod)) + th.WriteK(base, ` +resources: +- short_secret.yaml +generators: +- gener.yaml +`) + th.WriteK(prod, ` +resources: +- ../base +buildMetadata: [originAnnotations] +`) + th.WriteF(filepath.Join(base, "short_secret.yaml"), + ` +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +type: Opaque +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +`) + th.WriteF(filepath.Join(base, "generateDeployment.sh"), generateDeploymentDotSh) + + assert.NoError(t, os.Chmod(filepath.Join(base, "generateDeployment.sh"), 0777)) + th.WriteF(filepath.Join(base, "gener.yaml"), ` +kind: executable +metadata: + name: demo + annotations: + config.kubernetes.io/function: | + exec: + path: ./generateDeployment.sh +spec: +`) + + m := th.Run(prod, o) + assert.NoError(t, err) + yml, err := m.AsYaml() + assert.NoError(t, err) + assert.Equal(t, `apiVersion: v1 +kind: Secret +metadata: + annotations: + config.kubernetes.io/origin: | + path: ../base/short_secret.yaml + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +type: Opaque +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + config.kubernetes.io/origin: | + configuredIn: ../base/gener.yaml + configuredBy: + kind: executable + name: demo + tshirt-size: small + labels: + app: nginx + name: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx +`, string(yml)) + assert.NoError(t, fSys.RemoveAll(tmpDir.String())) +} + +func TestAnnoOriginCustomInlineExecGeneratorWithOverlay(t *testing.T) { + fSys := filesys.MakeFsOnDisk() + + th := kusttest_test.MakeHarnessWithFs(t, fSys) + o := th.MakeOptionsPluginsEnabled() + o.PluginConfig.FnpLoadingOptions.EnableExec = true + + tmpDir, err := filesys.NewTmpConfirmedDir() + assert.NoError(t, err) + base := filepath.Join(tmpDir.String(), "base") + prod := filepath.Join(tmpDir.String(), "prod") + assert.NoError(t, fSys.Mkdir(base)) + assert.NoError(t, fSys.Mkdir(prod)) + th.WriteK(base, ` +resources: +- short_secret.yaml +generators: +- |- + kind: executable + metadata: + name: demo + annotations: + config.kubernetes.io/function: | + exec: + path: ./generateDeployment.sh + spec: +`) + th.WriteK(prod, ` +resources: +- ../base +buildMetadata: [originAnnotations] +`) + th.WriteF(filepath.Join(base, "short_secret.yaml"), + ` +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +type: Opaque +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +`) + th.WriteF(filepath.Join(base, "generateDeployment.sh"), generateDeploymentDotSh) + assert.NoError(t, os.Chmod(filepath.Join(base, "generateDeployment.sh"), 0777)) + m := th.Run(prod, o) + assert.NoError(t, err) + yml, err := m.AsYaml() + assert.NoError(t, err) + assert.Equal(t, `apiVersion: v1 +kind: Secret +metadata: + annotations: + config.kubernetes.io/origin: | + path: ../base/short_secret.yaml + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +type: Opaque +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + config.kubernetes.io/origin: | + configuredIn: ../base/kustomization.yaml + configuredBy: + kind: executable + name: demo + tshirt-size: small + labels: + app: nginx + name: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx +`, string(yml)) + assert.NoError(t, fSys.RemoveAll(tmpDir.String())) +} + +func TestAnnoOriginRemoteBuiltinGenerator(t *testing.T) { + fSys := filesys.MakeFsOnDisk() + b := krusty.MakeKustomizer(krusty.MakeDefaultOptions()) + tmpDir, err := filesys.NewTmpConfirmedDir() + assert.NoError(t, err) + assert.NoError(t, fSys.WriteFile(filepath.Join(tmpDir.String(), "kustomization.yaml"), []byte(` +resources: +- github.com/kubernetes-sigs/kustomize/examples/ldap/base/?ref=v1.0.6 +buildMetadata: [originAnnotations] +`))) + m, err := b.Run( + fSys, + tmpDir.String()) + if utils.IsErrTimeout(err) { + // Don't fail on timeouts. + t.SkipNow() + } + if !assert.NoError(t, err) { + t.FailNow() + } + yml, err := m.AsYaml() + assert.NoError(t, err) + assert.Contains(t, string(yml), `kind: ConfigMap +metadata: + annotations: + config.kubernetes.io/origin: | + repo: https://github.com/kubernetes-sigs/kustomize + ref: v1.0.6 + configuredIn: examples/ldap/base/kustomization.yaml + configuredBy: + apiVersion: builtin + kind: ConfigMapGenerator + name: ldap-configmap-4d7m6k5b42`) + assert.NoError(t, fSys.RemoveAll(tmpDir.String())) +} diff --git a/api/resmap/resmap.go b/api/resmap/resmap.go index 960441b79..e7bacc33d 100644 --- a/api/resmap/resmap.go +++ b/api/resmap/resmap.go @@ -26,6 +26,13 @@ type Generator interface { Generate() (ResMap, error) } +// A GeneratorWithProperties contains a Generator and stores +// some of its properties +type GeneratorWithProperties struct { + Generator + Origin *resource.Origin +} + // Something that's configurable accepts an // instance of PluginHelpers and a raw config // object (YAML in []byte form). @@ -136,6 +143,11 @@ type ResMap interface { // self, then its behavior _cannot_ be merge or replace. AbsorbAll(ResMap) error + // AddOriginAnnotation will add the provided origin as + // an annotation to all resources in the Resmap, if + // the origin is not nil. + AddOriginAnnotation(origin *resource.Origin) error + // AnnotateAll annotates all resources in the ResMap with // the provided key value pair. AnnotateAll(key string, value string) error diff --git a/api/resmap/reswrangler.go b/api/resmap/reswrangler.go index 291efb5bb..9f634dc37 100644 --- a/api/resmap/reswrangler.go +++ b/api/resmap/reswrangler.go @@ -484,6 +484,25 @@ func (m *resWrangler) AbsorbAll(other ResMap) error { return nil } +// AddOriginAnnotation implements ResMap. +func (m *resWrangler) AddOriginAnnotation(origin *resource.Origin) error { + if origin == nil { + return nil + } + for _, res := range m.rList { + or, err := res.GetOrigin() + if or != nil || err != nil { + // if any resources already have an origin annotation, + // skip it + continue + } + if err := res.SetOrigin(origin); err != nil { + return err + } + } + return nil +} + func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error { id := res.CurId() matches := m.GetMatchingResourcesByAnyId(id.Equals) @@ -510,9 +529,18 @@ func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error { case types.BehaviorReplace: res.CopyMergeMetaDataFieldsFrom(old) case types.BehaviorMerge: + // ensure the origin annotation doesn't get overwritten + orig, err := old.GetOrigin() + if err != nil { + return err + } res.CopyMergeMetaDataFieldsFrom(old) res.MergeDataMapFrom(old) res.MergeBinaryDataMapFrom(old) + if orig != nil { + res.SetOrigin(orig) + } + default: return fmt.Errorf( "id %#v exists; behavior must be merge or replace", id) diff --git a/api/resource/origin.go b/api/resource/origin.go index 7f1bf50cf..3a835ca39 100644 --- a/api/resource/origin.go +++ b/api/resource/origin.go @@ -14,8 +14,9 @@ import ( // Origin retains information about where resources in the output // of `kustomize build` originated from type Origin struct { - // Path is the path to the resource, rooted from the directory upon - // which `kustomize build` was invoked + // Path is the path to the resource. If a local resource, this path is + // rooted from the directory upon which `kustomize build` was invoked. If a + // remote resource, this path is rooted from the root of the remote repo. Path string `json:"path,omitempty" yaml:"path,omitempty"` // Repo is the remote repository that the resource originated from if it is @@ -25,6 +26,16 @@ type Origin struct { // Ref is the ref of the remote repository that the resource originated from // if it is not from a local file Ref string `json:"ref,omitempty" yaml:"ref,omitempty"` + + // The following fields only apply to resources that have been + // generated by fields other than the `resources` field. + + // ConfiguredIn is the file path to the generator config that created the + // resource + ConfiguredIn string `json:"configuredIn,omitempty" yaml:"configuredIn,omitempty"` + + // ConfiguredBy is the ObjectReference of the generator config + ConfiguredBy kyaml.ResourceIdentifier `json:"configuredBy,omitempty" yaml:"configuredBy,omitempty"` } // Copy returns a copy of origin @@ -55,3 +66,29 @@ func (origin *Origin) String() (string, error) { anno, err := kyaml.Marshal(origin) return string(anno), err } + +func OriginFromCustomPlugin(res *Resource) (*Origin, error) { + origin, err := res.GetOrigin() + if err != nil { + return nil, err + } + var result *Origin + if origin != nil { + result = &Origin{ + Repo: origin.Repo, + Ref: origin.Ref, + ConfiguredIn: origin.Path, + ConfiguredBy: kyaml.ResourceIdentifier{ + TypeMeta: kyaml.TypeMeta{ + APIVersion: res.GetApiVersion(), + Kind: res.GetKind(), + }, + NameMeta: kyaml.NameMeta{ + Name: res.GetName(), + Namespace: res.GetNamespace(), + }, + }, + } + } + return result, nil +} diff --git a/api/resource/resource.go b/api/resource/resource.go index 7f8bfbd7d..bd6bb6c81 100644 --- a/api/resource/resource.go +++ b/api/resource/resource.go @@ -67,6 +67,29 @@ func (r *Resource) SetGvk(gvk resid.Gvk) { r.SetApiVersion(gvk.ApiVersion()) } +func (r *Resource) GetOrigin() (*Origin, error) { + annotations := r.GetAnnotations() + originAnnotations, ok := annotations[utils.OriginAnnotation] + if !ok { + return nil, nil + } + var origin Origin + if err := yaml.Unmarshal([]byte(originAnnotations), &origin); err != nil { + return nil, err + } + return &origin, nil +} + +func (r *Resource) SetOrigin(origin *Origin) error { + annotations := r.GetAnnotations() + originStr, err := origin.String() + if err != nil { + return err + } + annotations[utils.OriginAnnotation] = originStr + return r.SetAnnotations(annotations) +} + // ResCtx is an interface describing the contextual added // kept kustomize in the context of each Resource object. // Currently mainly the name prefix and name suffix are added.