diff --git a/api/internal/target/kusttarget.go b/api/internal/target/kusttarget.go index 43d3637dd8..18a38f3c48 100644 --- a/api/internal/target/kusttarget.go +++ b/api/internal/target/kusttarget.go @@ -16,8 +16,10 @@ import ( "sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig" "sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers" "sigs.k8s.io/kustomize/api/internal/plugins/loader" + "sigs.k8s.io/kustomize/api/internal/utils" "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/yaml" @@ -108,7 +110,7 @@ func (kt *KustTarget) MakeCustomizedResMap() (resmap.ResMap, error) { } func (kt *KustTarget) makeCustomizedResMap() (resmap.ResMap, error) { - ra, err := kt.AccumulateTarget() + ra, err := kt.AccumulateTarget(&resource.Origin{}) if err != nil { return nil, err } @@ -151,20 +153,29 @@ func (kt *KustTarget) addHashesToNames( // holding customized resources and the data/rules used // to do so. The name back references and vars are // not yet fixed. -func (kt *KustTarget) AccumulateTarget() ( +// The origin parameter is used through the recursive calls +// to annotate each resource with information about where +// the resource came from, e.g. the file and/or the repository +// it originated from. +// As an entrypoint, one can pass an empty resource.Origin object to +// AccumulateTarget. As AccumulateTarget moves recursively +// through kustomization directories, it updates `origin.path` +// accordingly. When a remote base is found, it updates `origin.repo` +// and `origin.ref` accordingly. +func (kt *KustTarget) AccumulateTarget(origin *resource.Origin) ( ra *accumulator.ResAccumulator, err error) { - return kt.accumulateTarget(accumulator.MakeEmptyAccumulator()) + return kt.accumulateTarget(accumulator.MakeEmptyAccumulator(), origin) } // ra should be empty when this KustTarget is a Kustomization, or the ra of the parent if this KustTarget is a Component // (or empty if the Component does not have a parent). -func (kt *KustTarget) accumulateTarget(ra *accumulator.ResAccumulator) ( +func (kt *KustTarget) accumulateTarget(ra *accumulator.ResAccumulator, origin *resource.Origin) ( resRa *accumulator.ResAccumulator, err error) { - ra, err = kt.accumulateResources(ra, kt.kustomization.Resources) + ra, err = kt.accumulateResources(ra, kt.kustomization.Resources, origin) if err != nil { return nil, errors.Wrap(err, "accumulating resources") } - ra, err = kt.accumulateComponents(ra, kt.kustomization.Components) + ra, err = kt.accumulateComponents(ra, kt.kustomization.Components, origin) if err != nil { return nil, errors.Wrap(err, "accumulating components") } @@ -247,7 +258,7 @@ func (kt *KustTarget) configureExternalGenerators() ([]resmap.Generator, error) } ra.AppendAll(rm) } - ra, err := kt.accumulateResources(ra, generatorPaths) + ra, err := kt.accumulateResources(ra, generatorPaths, &resource.Origin{}) if err != nil { return nil, err } @@ -283,7 +294,7 @@ func (kt *KustTarget) configureExternalTransformers(transformers []string) ([]re } ra.AppendAll(rm) } - ra, err := kt.accumulateResources(ra, transformerPaths) + ra, err := kt.accumulateResources(ra, transformerPaths, &resource.Origin{}) if err != nil { return nil, err @@ -332,16 +343,16 @@ func (kt *KustTarget) removeValidatedByLabel(rm resmap.ResMap) error { // accumulateResources fills the given resourceAccumulator // with resources read from the given list of paths. func (kt *KustTarget) accumulateResources( - ra *accumulator.ResAccumulator, paths []string) (*accumulator.ResAccumulator, error) { + ra *accumulator.ResAccumulator, paths []string, origin *resource.Origin) (*accumulator.ResAccumulator, error) { for _, path := range paths { // try loading resource as file then as base (directory or git repository) - if errF := kt.accumulateFile(ra, path); errF != nil { + if errF := kt.accumulateFile(ra, path, origin); errF != nil { ldr, err := kt.ldr.New(path) if err != nil { return nil, errors.Wrapf( err, "accumulation err='%s'", errF.Error()) } - ra, err = kt.accumulateDirectory(ra, ldr, false) + ra, err = kt.accumulateDirectory(ra, ldr, origin.Append(path), false) if err != nil { return nil, errors.Wrapf( err, "accumulation err='%s'", errF.Error()) @@ -354,7 +365,7 @@ func (kt *KustTarget) accumulateResources( // accumulateResources fills the given resourceAccumulator // with resources read from the given list of paths. func (kt *KustTarget) accumulateComponents( - ra *accumulator.ResAccumulator, paths []string) (*accumulator.ResAccumulator, error) { + ra *accumulator.ResAccumulator, paths []string, origin *resource.Origin) (*accumulator.ResAccumulator, error) { for _, path := range paths { // Components always refer to directories ldr, errL := kt.ldr.New(path) @@ -362,7 +373,8 @@ func (kt *KustTarget) accumulateComponents( return nil, fmt.Errorf("loader.New %q", errL) } var errD error - ra, errD = kt.accumulateDirectory(ra, ldr, true) + origin.Path = filepath.Join(origin.Path, path) + ra, errD = kt.accumulateDirectory(ra, ldr, origin, true) if errD != nil { return nil, fmt.Errorf("accumulateDirectory: %q", errD) } @@ -371,7 +383,7 @@ func (kt *KustTarget) accumulateComponents( } func (kt *KustTarget) accumulateDirectory( - ra *accumulator.ResAccumulator, ldr ifc.Loader, isComponent bool) (*accumulator.ResAccumulator, error) { + ra *accumulator.ResAccumulator, ldr ifc.Loader, origin *resource.Origin, isComponent bool) (*accumulator.ResAccumulator, error) { defer ldr.Cleanup() subKt := NewKustTarget(ldr, kt.validator, kt.rFactory, kt.pLdr) err := subKt.Load() @@ -379,6 +391,7 @@ func (kt *KustTarget) accumulateDirectory( return nil, errors.Wrapf( err, "couldn't make target for path '%s'", ldr.Root()) } + subKt.kustomization.BuildMetadata = kt.kustomization.BuildMetadata var bytes []byte path := ldr.Root() if openApiPath, exists := subKt.Kustomization().OpenAPI["path"]; exists { @@ -402,12 +415,12 @@ func (kt *KustTarget) accumulateDirectory( var subRa *accumulator.ResAccumulator if isComponent { // Components don't create a new accumulator: the kustomization directives are added to the current accumulator - subRa, err = subKt.accumulateTarget(ra) + subRa, err = subKt.accumulateTarget(ra, origin) ra = accumulator.MakeEmptyAccumulator() } else { // Child Kustomizations create a new accumulator which resolves their kustomization directives, which will later // be merged into the current accumulator. - subRa, err = subKt.AccumulateTarget() + subRa, err = subKt.AccumulateTarget(origin) } if err != nil { return nil, errors.Wrapf( @@ -422,11 +435,18 @@ func (kt *KustTarget) accumulateDirectory( } func (kt *KustTarget) accumulateFile( - ra *accumulator.ResAccumulator, path string) error { + ra *accumulator.ResAccumulator, path string, origin *resource.Origin) error { resources, err := kt.rFactory.FromFile(kt.ldr, path) if err != nil { return errors.Wrapf(err, "accumulating resources from '%s'", path) } + if utils.StringSliceContains(kt.kustomization.BuildMetadata, "originAnnotations") { + origin = origin.Append(path) + err = resources.AnnotateAll(utils.OriginAnnotation, origin.String()) + if err != nil { + return errors.Wrapf(err, "cannot add path annotation for '%s'", path) + } + } err = ra.AppendAll(resources) if err != nil { return errors.Wrapf(err, "merging resources from '%s'", path) diff --git a/api/internal/target/vars_test.go b/api/internal/target/vars_test.go index 901dcde907..1fb9124716 100644 --- a/api/internal/target/vars_test.go +++ b/api/internal/target/vars_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "sigs.k8s.io/kustomize/api/resource" kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/resid" @@ -65,7 +66,7 @@ vars: apiVersion: v300 `) ra, err := makeAndLoadKustTarget( - t, th.GetFSys(), "/app").AccumulateTarget() + t, th.GetFSys(), "/app").AccumulateTarget(&resource.Origin{}) if err != nil { t.Fatalf("Err: %v", err) } @@ -120,7 +121,7 @@ resources: `) ra, err := makeAndLoadKustTarget( - t, th.GetFSys(), "/app/overlays/o2").AccumulateTarget() + t, th.GetFSys(), "/app/overlays/o2").AccumulateTarget(&resource.Origin{}) if err != nil { t.Fatalf("Err: %v", err) } @@ -177,7 +178,7 @@ resources: - ../o1 `) _, err := makeAndLoadKustTarget( - t, th.GetFSys(), "/app/overlays/o2").AccumulateTarget() + t, th.GetFSys(), "/app/overlays/o2").AccumulateTarget(&resource.Origin{}) if err == nil { t.Fatalf("expected var collision") } diff --git a/api/internal/utils/annotations.go b/api/internal/utils/annotations.go new file mode 100644 index 0000000000..6c9cecba68 --- /dev/null +++ b/api/internal/utils/annotations.go @@ -0,0 +1,23 @@ +package utils + +import "sigs.k8s.io/kustomize/api/konfig" + +const ( + BuildAnnotationPreviousKinds = konfig.ConfigAnnoDomain + "/previousKinds" + BuildAnnotationPreviousNames = konfig.ConfigAnnoDomain + "/previousNames" + BuildAnnotationPrefixes = konfig.ConfigAnnoDomain + "/prefixes" + BuildAnnotationSuffixes = konfig.ConfigAnnoDomain + "/suffixes" + BuildAnnotationPreviousNamespaces = konfig.ConfigAnnoDomain + "/previousNamespaces" + BuildAnnotationsRefBy = konfig.ConfigAnnoDomain + "/refBy" + BuildAnnotationsGenBehavior = konfig.ConfigAnnoDomain + "/generatorBehavior" + BuildAnnotationsGenAddHashSuffix = konfig.ConfigAnnoDomain + "/needsHashSuffix" + + // the following are only for patches, to specify whether they can change names + // and kinds of their targets + BuildAnnotationAllowNameChange = konfig.ConfigAnnoDomain + "/allowNameChange" + BuildAnnotationAllowKindChange = konfig.ConfigAnnoDomain + "/allowKindChange" + + OriginAnnotation = "config.kubernetes.io/origin" + + Enabled = "enabled" +) diff --git a/api/internal/utils/makeResIds.go b/api/internal/utils/makeResIds.go index 0a1ea01583..17f25168e6 100644 --- a/api/internal/utils/makeResIds.go +++ b/api/internal/utils/makeResIds.go @@ -4,28 +4,10 @@ import ( "fmt" "strings" - "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/kyaml/resid" "sigs.k8s.io/kustomize/kyaml/yaml" ) -const ( - BuildAnnotationPreviousKinds = konfig.ConfigAnnoDomain + "/previousKinds" - BuildAnnotationPreviousNames = konfig.ConfigAnnoDomain + "/previousNames" - BuildAnnotationPreviousNamespaces = konfig.ConfigAnnoDomain + "/previousNamespaces" - BuildAnnotationPrefixes = konfig.ConfigAnnoDomain + "/prefixes" - BuildAnnotationSuffixes = konfig.ConfigAnnoDomain + "/suffixes" - BuildAnnotationsRefBy = konfig.ConfigAnnoDomain + "/refBy" - BuildAnnotationsGenBehavior = konfig.ConfigAnnoDomain + "/generatorBehavior" - BuildAnnotationsGenAddHashSuffix = konfig.ConfigAnnoDomain + "/needsHashSuffix" - - // the following are only for patches, to specify whether they can change names - // and kinds of their targets - BuildAnnotationAllowNameChange = konfig.ConfigAnnoDomain + "/allowNameChange" - BuildAnnotationAllowKindChange = konfig.ConfigAnnoDomain + "/allowKindChange" - Enabled = "enabled" -) - // MakeResIds returns all of an RNode's current and previous Ids func MakeResIds(n *yaml.RNode) ([]resid.ResId, error) { var result []resid.ResId diff --git a/api/krusty/buildmetadata_test.go b/api/krusty/buildmetadata_test.go new file mode 100644 index 0000000000..7644ff3723 --- /dev/null +++ b/api/krusty/buildmetadata_test.go @@ -0,0 +1,265 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package krusty_test + +import ( + "testing" + + _ "sigs.k8s.io/kustomize/api/krusty" + kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" +) + +func TestAnnoOriginLocalFiles(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteF("service.yaml", ` +apiVersion: v1 +kind: Service +metadata: + name: myService +spec: + ports: + - port: 7002 +`) + th.WriteK(".", ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- service.yaml +buildMetadata: [originAnnotations] +`) + options := th.MakeDefaultOptions() + m := th.Run(".", options) + th.AssertActualEqualsExpected(m, ` +apiVersion: v1 +kind: Service +metadata: + annotations: + config.kubernetes.io/origin: | + path: service.yaml + name: myService +spec: + ports: + - port: 7002 +`) +} + +func TestAnnoOriginLocalFilesWithOverlay(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteK("base", ` +namePrefix: b- +resources: +- namespace.yaml +- role.yaml +- service.yaml +- deployment.yaml +`) + th.WriteF("base/service.yaml", ` +apiVersion: v1 +kind: Service +metadata: + name: myService +`) + th.WriteF("base/namespace.yaml", ` +apiVersion: v1 +kind: Namespace +metadata: + name: myNs +`) + th.WriteF("base/role.yaml", ` +apiVersion: v1 +kind: Role +metadata: + name: myRole +`) + th.WriteF("base/deployment.yaml", ` +apiVersion: v1 +kind: Deployment +metadata: + name: myDep +`) + th.WriteK("prod", ` +namePrefix: p- +resources: +- ../base +- service.yaml +- namespace.yaml +buildMetadata: [originAnnotations] +`) + th.WriteF("prod/service.yaml", ` +apiVersion: v1 +kind: Service +metadata: + name: myService2 +`) + th.WriteF("prod/namespace.yaml", ` +apiVersion: v1 +kind: Namespace +metadata: + name: myNs2 +`) + m := th.Run("prod", th.MakeDefaultOptions()) + th.AssertActualEqualsExpected(m, ` +apiVersion: v1 +kind: Namespace +metadata: + annotations: + config.kubernetes.io/origin: | + path: ../base/namespace.yaml + name: myNs +--- +apiVersion: v1 +kind: Role +metadata: + annotations: + config.kubernetes.io/origin: | + path: ../base/role.yaml + name: p-b-myRole +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + config.kubernetes.io/origin: | + path: ../base/service.yaml + name: p-b-myService +--- +apiVersion: v1 +kind: Deployment +metadata: + annotations: + config.kubernetes.io/origin: | + path: ../base/deployment.yaml + name: p-b-myDep +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + config.kubernetes.io/origin: | + path: service.yaml + name: p-myService2 +--- +apiVersion: v1 +kind: Namespace +metadata: + annotations: + config.kubernetes.io/origin: | + path: namespace.yaml + name: myNs2 +`) +} + +// This is a copy of TestGeneratorBasics in configmaps_test.go, +// except that we've enabled the addAnnoOrigin option +// (which doesn't do anything yet). +// TODO: Generated resources should receive the annotation +// config.kubernetes.io/origin: | +// generated-by: path/to/kustomization.yaml +func TestGeneratorWithAnnoOrigin(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteK(".", ` +namePrefix: blah- +configMapGenerator: +- name: bob + literals: + - fruit=apple + - vegetable=broccoli + envs: + - foo.env + env: bar.env + files: + - passphrase=phrase.dat + - forces.txt +- name: json + literals: + - 'v2=[{"path": "var/druid/segment-cache"}]' + - >- + druid_segmentCache_locations=[{"path": + "var/druid/segment-cache", + "maxSize": 32000000000, + "freeSpacePercent": 1.0}] +secretGenerator: +- name: bob + literals: + - fruit=apple + - vegetable=broccoli + envs: + - foo.env + files: + - passphrase=phrase.dat + - forces.txt + env: bar.env +`) + th.WriteF("foo.env", ` +MOUNTAIN=everest +OCEAN=pacific +`) + th.WriteF("bar.env", ` +BIRD=falcon +`) + th.WriteF("phrase.dat", ` +Life is short. +But the years are long. +Not while the evil days come not. +`) + th.WriteF("forces.txt", ` +gravitational +electromagnetic +strong nuclear +weak nuclear +`) + opts := th.MakeDefaultOptions() + m := th.Run(".", opts) + th.AssertActualEqualsExpected( + m, ` +apiVersion: v1 +data: + BIRD: falcon + MOUNTAIN: everest + OCEAN: pacific + forces.txt: |2 + + gravitational + electromagnetic + strong nuclear + weak nuclear + fruit: apple + passphrase: |2 + + Life is short. + But the years are long. + Not while the evil days come not. + vegetable: broccoli +kind: ConfigMap +metadata: + name: blah-bob-g9df72cd5b +--- +apiVersion: v1 +data: + druid_segmentCache_locations: '[{"path": "var/druid/segment-cache", "maxSize": + 32000000000, "freeSpacePercent": 1.0}]' + v2: '[{"path": "var/druid/segment-cache"}]' +kind: ConfigMap +metadata: + name: blah-json-5298bc8g99 +--- +apiVersion: v1 +data: + BIRD: ZmFsY29u + MOUNTAIN: ZXZlcmVzdA== + OCEAN: cGFjaWZpYw== + forces.txt: | + CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn + VjbGVhcgo= + fruit: YXBwbGU= + passphrase: | + CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG + UgZXZpbCBkYXlzIGNvbWUgbm90Lgo= + vegetable: YnJvY2NvbGk= +kind: Secret +metadata: + name: blah-bob-58g62h555c +type: Opaque +`) +} diff --git a/api/krusty/remoteload_test.go b/api/krusty/remoteload_test.go index 1f40d8a8c0..04f87dd0b6 100644 --- a/api/krusty/remoteload_test.go +++ b/api/krusty/remoteload_test.go @@ -4,6 +4,7 @@ package krusty_test import ( + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -39,3 +40,124 @@ spec: name: nginx `, string(yml)) } + +func TestRemoteResource(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/multibases/dev/?ref=v1.0.6 +`))) + m, err := b.Run( + fSys, + tmpDir.String()) + if utils.IsErrTimeout(err) { + // Don't fail on timeouts. + t.SkipNow() + } + assert.NoError(t, err) + yml, err := m.AsYaml() + assert.NoError(t, err) + assert.Equal(t, `apiVersion: v1 +kind: Pod +metadata: + labels: + app: myapp + name: dev-myapp-pod +spec: + containers: + - image: nginx:1.7.9 + name: nginx +`, string(yml)) + assert.NoError(t, fSys.RemoveAll(tmpDir.String())) +} + +func TestRemoteResourceAnnoOrigin(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/multibases/dev/?ref=v1.0.6 +buildMetadata: [originAnnotations] +`))) + m, err := b.Run( + fSys, + tmpDir.String()) + if utils.IsErrTimeout(err) { + // Don't fail on timeouts. + t.SkipNow() + } + assert.NoError(t, err) + yml, err := m.AsYaml() + assert.NoError(t, err) + assert.Equal(t, `apiVersion: v1 +kind: Pod +metadata: + annotations: + config.kubernetes.io/origin: | + path: examples/multibases/base/pod.yaml + repo: https://github.com/kubernetes-sigs/kustomize + ref: v1.0.6 + labels: + app: myapp + name: dev-myapp-pod +spec: + containers: + - image: nginx:1.7.9 + name: nginx +`, string(yml)) + assert.NoError(t, fSys.RemoveAll(tmpDir.String())) +} + +func TestRemoteResourceAsBaseWithAnnoOrigin(t *testing.T) { + fSys := filesys.MakeFsOnDisk() + b := krusty.MakeKustomizer(krusty.MakeDefaultOptions()) + 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)) + assert.NoError(t, fSys.WriteFile(filepath.Join(base, "kustomization.yaml"), []byte(` +resources: +- github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v1.0.6 +`))) + assert.NoError(t, fSys.WriteFile(filepath.Join(prod, "kustomization.yaml"), []byte(` +resources: +- ../base +namePrefix: prefix- +buildMetadata: [originAnnotations] +`))) + + m, err := b.Run( + fSys, + prod) + if utils.IsErrTimeout(err) { + // Don't fail on timeouts. + t.SkipNow() + } + assert.NoError(t, err) + yml, err := m.AsYaml() + assert.NoError(t, err) + assert.Equal(t, `apiVersion: v1 +kind: Pod +metadata: + annotations: + config.kubernetes.io/origin: | + path: examples/multibases/base/pod.yaml + repo: https://github.com/kubernetes-sigs/kustomize + ref: v1.0.6 + labels: + app: myapp + name: prefix-dev-myapp-pod +spec: + containers: + - image: nginx:1.7.9 + name: nginx +`, string(yml)) + assert.NoError(t, fSys.RemoveAll(tmpDir.String())) +} diff --git a/api/resmap/resmap.go b/api/resmap/resmap.go index 704276cf1f..7c0c2ea802 100644 --- a/api/resmap/resmap.go +++ b/api/resmap/resmap.go @@ -136,6 +136,10 @@ type ResMap interface { // self, then its behavior _cannot_ be merge or replace. AbsorbAll(ResMap) error + // AnnotateAll annotates all resources in the ResMap with + // the provided key value pair. + AnnotateAll(key string, value string) error + // AsYaml returns the yaml form of resources. AsYaml() ([]byte, error) diff --git a/api/resmap/reswrangler.go b/api/resmap/reswrangler.go index 0305a55326..5bdc5c585e 100644 --- a/api/resmap/reswrangler.go +++ b/api/resmap/reswrangler.go @@ -9,6 +9,7 @@ import ( "reflect" "github.com/pkg/errors" + "sigs.k8s.io/kustomize/api/filters/annotations" "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/kio" @@ -531,6 +532,19 @@ func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error { } } +// AnnotateAll implements ResMap +func (m *resWrangler) AnnotateAll(key string, value string) error { + return m.ApplyFilter(annotations.Filter{ + Annotations: map[string]string{ + key: value, + }, + FsSlice: []types.FieldSpec{{ + Path: "metadata/annotations", + CreateIfNotPresent: true, + }}, + }) +} + // Select returns a list of resources that // are selected by a Selector func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) { diff --git a/api/resource/origin.go b/api/resource/origin.go new file mode 100644 index 0000000000..ec0b498733 --- /dev/null +++ b/api/resource/origin.go @@ -0,0 +1,60 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package resource + +import ( + "path/filepath" + "strings" + + "sigs.k8s.io/kustomize/api/internal/git" +) + +// 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 string + + // Repo is the remote repository that the resource originated from if it is + // not from a local file + Repo string + + // Ref is the ref of the remote repository that the resource originated from + // if it is not from a local file + Ref string +} + +// Copy returns a copy of origin +func (origin *Origin) Copy() Origin { + return *origin +} + +// Append returns a copy of origin with a path appended to it +func (origin *Origin) Append(path string) *Origin { + originCopy := origin.Copy() + repoSpec, err := git.NewRepoSpecFromUrl(path) + if err == nil { + originCopy.Repo = repoSpec.Host + repoSpec.OrgRepo + absPath := repoSpec.AbsPath() + path = absPath[strings.Index(absPath[1:], "/")+1:][1:] + originCopy.Path = "" + originCopy.Ref = repoSpec.Ref + } + originCopy.Path = filepath.Join(originCopy.Path, path) + return &originCopy +} + +// String returns a string version of origin +func (origin *Origin) String() string { + var anno string + anno = anno + "path: " + origin.Path + "\n" + if origin.Repo != "" { + anno = anno + "repo: " + origin.Repo + "\n" + } + if origin.Ref != "" { + anno = anno + "ref: " + origin.Ref + "\n" + } + return anno +} diff --git a/api/resource/origin_test.go b/api/resource/origin_test.go new file mode 100644 index 0000000000..d880a5ce90 --- /dev/null +++ b/api/resource/origin_test.go @@ -0,0 +1,83 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package resource_test + +import ( + "testing" + + . "sigs.k8s.io/kustomize/api/resource" +) + +func TestOriginAppend(t *testing.T) { + tests := []struct { + in *Origin + path string + expected string + }{ + { + in: &Origin{ + Path: "prod", + }, + path: "service.yaml", + expected: `path: prod/service.yaml +`, + }, + { + in: &Origin{ + Path: "overlay/prod", + }, + path: "github.com/kubernetes-sigs/kustomize/examples/multibases/dev/", + expected: `path: examples/multibases/dev +repo: https://github.com/kubernetes-sigs/kustomize +`, + }, + } + for _, test := range tests { + actual := test.in.Append(test.path).String() + if actual != test.expected { + t.Fatalf("Expected %v, but got %v\n", test.expected, actual) + } + } +} + +func TestOriginString(t *testing.T) { + tests := []struct { + in *Origin + expected string + }{ + { + in: &Origin{ + Path: "prod/service.yaml", + Repo: "github.com/kubernetes-sigs/kustomize/examples/multibases/dev/", + Ref: "v1.0.6", + }, + expected: `path: prod/service.yaml +repo: github.com/kubernetes-sigs/kustomize/examples/multibases/dev/ +ref: v1.0.6 +`, + }, + { + in: &Origin{ + Path: "prod/service.yaml", + Repo: "github.com/kubernetes-sigs/kustomize/examples/multibases/dev/", + }, + expected: `path: prod/service.yaml +repo: github.com/kubernetes-sigs/kustomize/examples/multibases/dev/ +`, + }, + { + in: &Origin{ + Path: "prod/service.yaml", + }, + expected: `path: prod/service.yaml +`, + }, + } + + for _, test := range tests { + if test.in.String() != test.expected { + t.Fatalf("Expected %v, but got %v\n", test.expected, test.in.String()) + } + } +} diff --git a/api/types/kustomization.go b/api/types/kustomization.go index 8f4b45d1c8..3d686ebf31 100644 --- a/api/types/kustomization.go +++ b/api/types/kustomization.go @@ -161,6 +161,9 @@ type Kustomization struct { // Inventory appends an object that contains the record // of all other objects, which can be used in apply, prune and delete Inventory *Inventory `json:"inventory,omitempty" yaml:"inventory,omitempty"` + + // BuildMetadata is a list of strings used to toggle different build options + BuildMetadata []string `json:"buildMetadata,omitempty" yaml:"buildMetadata,omitempty"` } // FixKustomizationPostUnmarshalling fixes things