diff --git a/api/internal/localizer/builtinplugins.go b/api/internal/localizer/builtinplugins.go index 97952224b..895e0761a 100644 --- a/api/internal/localizer/builtinplugins.go +++ b/api/internal/localizer/builtinplugins.go @@ -4,35 +4,51 @@ package localizer import ( + "sigs.k8s.io/kustomize/api/filters/filtersutil" + "sigs.k8s.io/kustomize/api/filters/fsslice" + "sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers" + "sigs.k8s.io/kustomize/api/konfig" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/resid" "sigs.k8s.io/kustomize/kyaml/yaml" ) -// localizeBuiltinGenerators localizes built-in generators with file paths. +// localizeBuiltinPlugins localizes built-in plugins with file paths. // Note that this excludes helm, which needs a repo. -type localizeBuiltinGenerators struct { +type localizeBuiltinPlugins struct { + lc *localizer } -var _ kio.Filter = &localizeBuiltinGenerators{} +var _ kio.Filter = &localizeBuiltinPlugins{} -// Filter localizes the built-in generators with file paths. Filter returns an error if -// generators contains a resource that is not a built-in generator, cannot contain a file path, -// needs more than a file path like helm, or is not localizable. -// TODO(annasong): implement -func (lbg *localizeBuiltinGenerators) Filter(generators []*yaml.RNode) ([]*yaml.RNode, error) { - return generators, nil +// Filter localizes the built-in plugins with file paths. +func (lbp *localizeBuiltinPlugins) Filter(plugins []*yaml.RNode) ([]*yaml.RNode, error) { + localizedPlugins, err := kio.FilterAll(fsslice.Filter{ + FsSlice: types.FsSlice{ + types.FieldSpec{ + Gvk: resid.Gvk{Version: konfig.BuiltinPluginApiVersion, Kind: builtinhelpers.PatchTransformer.String()}, + Path: "path", + }, + types.FieldSpec{ + Gvk: resid.Gvk{Version: konfig.BuiltinPluginApiVersion, Kind: builtinhelpers.PatchJson6902Transformer.String()}, + Path: "path", + }, + }, + SetValue: lbp.localizeNode, + }).Filter(plugins) + + // TODO(annasong): localize ReplacementTransformer, PatchStrategicMergeTransformer, ConfigMapGenerator, SecretGenerator + + return localizedPlugins, errors.Wrap(err) } -// localizeBuiltinTransformers localizes built-in transformers with file paths. -type localizeBuiltinTransformers struct { -} - -var _ kio.Filter = &localizeBuiltinTransformers{} - -// Filter localizes the built-in transformers with file paths. Filter returns an error if -// transformers contains a resource that is not a built-in transformer, cannot contain a file path, -// or is not localizable. -// TODO(annasong): implement -func (lbt *localizeBuiltinTransformers) Filter(transformers []*yaml.RNode) ([]*yaml.RNode, error) { - return transformers, nil +// localizeNode sets the scalar node to its value localized as a file path. +func (lbp *localizeBuiltinPlugins) localizeNode(node *yaml.RNode) error { + localizedPath, err := lbp.lc.localizeFile(node.YNode().Value) + if err != nil { + return errors.WrapPrefixf(err, "unable to localize built-in plugin path") + } + return filtersutil.SetScalar(localizedPath)(node) } diff --git a/api/internal/localizer/localizer.go b/api/internal/localizer/localizer.go index d975a48d8..150cefe2f 100644 --- a/api/internal/localizer/localizer.go +++ b/api/internal/localizer/localizer.go @@ -16,7 +16,6 @@ import ( "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/filesys" - "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/yaml" ) @@ -224,7 +223,11 @@ func (lc *localizer) localizeFile(path string) (string, error) { if err != nil { return "", errors.Wrap(err) } + return lc.localizeFileWithContent(path, content) +} +// localizeFileWithContent writes content to the localized file path and returns the localized path. +func (lc *localizer) localizeFileWithContent(path string, content []byte) (string, error) { var locPath string if loader.IsRemoteFile(path) { // TODO(annasong): You need to check if you can add a localize directory here to store @@ -243,10 +246,10 @@ func (lc *localizer) localizeFile(path string) (string, error) { locPath = cleanFilePath(lc.fSys, lc.root, path) } absPath := filepath.Join(lc.dst, locPath) - if err = lc.fSys.MkdirAll(filepath.Dir(absPath)); err != nil { + if err := lc.fSys.MkdirAll(filepath.Dir(absPath)); err != nil { return "", errors.WrapPrefixf(err, "unable to create directories to localize file %q", path) } - if err = lc.fSys.WriteFile(absPath, content); err != nil { + if err := lc.fSys.WriteFile(absPath, content); err != nil { return "", errors.WrapPrefixf(err, "unable to localize file %q", path) } return locPath, nil @@ -297,29 +300,17 @@ func (lc *localizer) localizeDir(path string) (string, error) { // // Note that the localization in this function has not been implemented yet. func (lc *localizer) localizeBuiltinPlugins(kust *types.Kustomization) error { - for fieldName, plugins := range map[string]struct { - entries []string - localizer kio.Filter - }{ - "generators": { - kust.Generators, - &localizeBuiltinGenerators{}, - }, - "transformers": { - kust.Transformers, - &localizeBuiltinTransformers{}, - }, - "validators": { - kust.Validators, - &localizeBuiltinTransformers{}, - }, + for fieldName, entries := range map[string][]string{ + "generators": kust.Generators, + "transformers": kust.Transformers, + "validators": kust.Validators, } { - for i, entry := range plugins.entries { + for i, entry := range entries { rm, isPath, err := lc.loadResource(entry) if err != nil { return errors.WrapPrefixf(err, "unable to load %s entry", fieldName) } - err = rm.ApplyFilter(plugins.localizer) + err = rm.ApplyFilter(&localizeBuiltinPlugins{lc}) if err != nil { return errors.Wrap(err) } @@ -327,14 +318,16 @@ func (lc *localizer) localizeBuiltinPlugins(kust *types.Kustomization) error { if err != nil { return errors.WrapPrefixf(err, "unable to serialize localized %s entry %q", fieldName, entry) } - var newEntry string + var localizedEntry string if isPath { - // TODO(annasong): write localizedPlugin to dst - newEntry = entry + localizedEntry, err = lc.localizeFileWithContent(entry, localizedPlugin) + if err != nil { + return errors.WrapPrefixf(err, "unable to localize %s entry", fieldName) + } } else { - newEntry = string(localizedPlugin) + localizedEntry = string(localizedPlugin) } - plugins.entries[i] = newEntry + entries[i] = localizedEntry } } return nil diff --git a/api/internal/localizer/localizer_test.go b/api/internal/localizer/localizer_test.go index f07d0a540..61bc24885 100644 --- a/api/internal/localizer/localizer_test.go +++ b/api/internal/localizer/localizer_test.go @@ -95,11 +95,9 @@ func reportFSysDiff(t *testing.T, fSysExpected filesys.FileSystem, fSysActual fi err = fSysExpected.Walk("/", func(path string, info fs.FileInfo, err error) error { require.NoError(t, err) - visited[path] = struct{}{} - if _, exists := visited[path]; !exists { - t.Errorf("expected path %q not found", path) - } + _, exists := visited[path] + assert.Truef(t, exists, "expected path %q not found", path) return nil }) require.NoError(t, err) @@ -531,6 +529,96 @@ patches: checkFSys(t, expected, actual) } +func TestLocalizePluginsInlineAndFile(t *testing.T) { + kustAndPlugins := map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +transformers: +- | + apiVersion: builtin + kind: PatchTransformer + metadata: + name: inline + path: patchSM-one.yaml +- patch.yaml +`, + "patch.yaml": `apiVersion: builtin +kind: PatchTransformer +metadata: + name: file +path: patchSM-two.yaml +`, + "patchSM-one.yaml": podConfiguration, + "patchSM-two.yaml": podConfiguration, + } + expected, actual := makeFileSystems(t, "/a", kustAndPlugins) + + err := Run("/a", "", "/dst", actual) + require.NoError(t, err) + + addFiles(t, expected, "/dst", kustAndPlugins) + checkFSys(t, expected, actual) +} + +func TestLocalizeMultiplePluginsInEntry(t *testing.T) { + kustAndPlugins := map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +transformers: +- | + apiVersion: builtin + kind: PatchTransformer + metadata: + name: one + path: patchSM-one.yaml + --- + apiVersion: builtin + kind: PatchTransformer + metadata: + name: two + path: patchSM-two.yaml +`, + "patchSM-one.yaml": podConfiguration, + "patchSM-two.yaml": podConfiguration, + } + expected, actual := makeFileSystems(t, "/a", kustAndPlugins) + + err := Run("/a", "", "/dst", actual) + require.NoError(t, err) + + addFiles(t, expected, "/dst", kustAndPlugins) + checkFSys(t, expected, actual) +} + +func TestLocalizeCleanedPathInPath(t *testing.T) { + const patchf = `apiVersion: builtin +kind: PatchTransformer +metadata: + name: cleaned-path +path: %s +` + kustAndPlugins := map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +transformers: +- patch.yaml +`, + "patch.yaml": fmt.Sprintf(patchf, "../a/patchSM.yaml"), + "patchSM.yaml": podConfiguration, + } + expected, actual := makeFileSystems(t, "/a", kustAndPlugins) + + err := Run("/a", "", "/dst", actual) + require.NoError(t, err) + + addFiles(t, expected, "/dst", map[string]string{ + "kustomization.yaml": kustAndPlugins["kustomization.yaml"], + "patch.yaml": fmt.Sprintf(patchf, "patchSM.yaml"), + "patchSM.yaml": kustAndPlugins["patchSM.yaml"], + }) + checkFSys(t, expected, actual) +} + func TestLocalizeGenerators(t *testing.T) { kustAndPlugins := map[string]string{ "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 @@ -566,47 +654,101 @@ metadata: err := Run("/a", "", "/alpha/dst", actual) require.NoError(t, err) - addFiles(t, expected, "/alpha/dst", map[string]string{ - "kustomization.yaml": kustAndPlugins["kustomization.yaml"], - }) + addFiles(t, expected, "/alpha/dst", kustAndPlugins) checkFSys(t, expected, actual) } -func TestLocalizeTransformers(t *testing.T) { - kustAndPlugins := map[string]string{ +func TestLocalizeTransformersPatch(t *testing.T) { + kustAndPatches := map[string]string{ "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization transformers: - | apiVersion: builtin - jsonOp: '[{"op": "add", "path": "/spec/template/spec/dnsPolicy", "value": "ClusterFirst"}]' - kind: PatchJson6902Transformer + kind: PatchTransformer metadata: - name: patch6902 + name: no-path + patch: '[{"op": "add", "path": "/path", "value": "value"}]' target: - name: deployment - --- - apiVersion: builtin - kind: ReplacementTransformer - metadata: - name: replacement - replacements: - - source: - fieldPath: spec.template.spec.containers.0.image - kind: Deployment - targets: - - fieldPaths: - - spec.template.spec.containers.1.image - select: - kind: Deployment -- plugin.yaml + name: pod +- patch.yaml `, - "plugin.yaml": `apiVersion: builtin -kind: PatchStrategicMergeTransformer + "patch.yaml": `apiVersion: builtin +kind: PatchTransformer metadata: - name: patchSM -paths: -- pod.yaml + name: path +path: patchSM.yaml +`, + "patchSM.yaml": podConfiguration, + } + expected, actual := makeFileSystems(t, "/a", kustAndPatches) + + err := Run("/a", "", "/dst", actual) + require.NoError(t, err) + + addFiles(t, expected, "/dst", kustAndPatches) + checkFSys(t, expected, actual) +} + +func TestLocalizeTransformersPatchJson(t *testing.T) { + kustAndPatches := map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +transformers: +- patch.yaml +`, + "patch.yaml": `apiVersion: builtin +kind: PatchJson6902Transformer +metadata: + name: path +path: nested-patch.yaml +target: + name: pod + namespace: test +--- +apiVersion: builtin +jsonOp: |- + op: replace + path: /path + value: new value +kind: PatchJson6902Transformer +metadata: + name: patch6902 +target: + name: deployment +`, + "nested-patch.yaml": ` [ + {"op": "copy", "from": "/existing/path", "path": "/another/path"}, + ] +`, + } + expected, actual := makeFileSystems(t, "/a", kustAndPatches) + + err := Run("/a", "", "/dst", actual) + require.NoError(t, err) + + addFiles(t, expected, "/dst", kustAndPatches) + checkFSys(t, expected, actual) +} + +func TestLocalizePluginsNoPaths(t *testing.T) { + kustAndPlugins := map[string]string{ + "kustomization.yaml": `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +transformers: +- | + apiVersion: different + kind: MyTransformer + metadata: + name: still-copied + path: /nothing/special +- prefix.yaml +`, + "prefix.yaml": `apiVersion: builtin +kind: PrefixTransformer +metadata: + name: other-built-ins-still-copied +prefix: copy `, } expected, actual := makeFileSystems(t, "/a", kustAndPlugins) @@ -614,9 +756,7 @@ paths: err := Run("/a", "", "/dst", actual) require.NoError(t, err) - addFiles(t, expected, "/dst", map[string]string{ - "kustomization.yaml": kustAndPlugins["kustomization.yaml"], - }) + addFiles(t, expected, "/dst", kustAndPlugins) checkFSys(t, expected, actual) } @@ -632,13 +772,13 @@ validators: name: replacement replacements: - source: - fieldPath: data.[field=value] - group: apps + fieldPath: metadata.name + kind: ConfigMap targets: - fieldPaths: - - spec.* + - metadata.name select: - kind: Pod + kind: ConfigMap - replacement.yaml `, "replacement.yaml": `apiVersion: builtin @@ -662,9 +802,7 @@ replacements: err := Run("/", "", "/dst", actual) require.NoError(t, err) - addFiles(t, expected, "/dst", map[string]string{ - "kustomization.yaml": kustAndPlugin["kustomization.yaml"], - }) + addFiles(t, expected, "/dst", kustAndPlugin) checkFSys(t, expected, actual) }