From 9bd4f782880aaab6cff1254d53a9b4ca441b49ea Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Fri, 12 Jun 2020 19:21:00 +0000 Subject: [PATCH 01/22] PoC to use kpt functions as kustomize plugins Closes [1] In addition removes accidentally committed binary [2] [1] https://github.com/GoogleContainerTools/kpt/issues/646 [2] https://github.com/kubernetes-sigs/kustomize/commit/1644fdd076aab42ab04ab6ef56f6970549ddc31f#diff-78873bc1f515e5cb644e68f0bcbaba23 --- api/internal/plugins/fnplugin/fnplugin.go | 287 +++++++++++++++++++++ api/internal/plugins/loader/loader.go | 15 +- api/types/pluginconfig.go | 3 + api/types/pluginrestrictions.go | 13 + kustomize/internal/commands/build/build.go | 21 ++ 5 files changed, 336 insertions(+), 3 deletions(-) create mode 100644 api/internal/plugins/fnplugin/fnplugin.go diff --git a/api/internal/plugins/fnplugin/fnplugin.go b/api/internal/plugins/fnplugin/fnplugin.go new file mode 100644 index 000000000..89063d7d0 --- /dev/null +++ b/api/internal/plugins/fnplugin/fnplugin.go @@ -0,0 +1,287 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package fnplugin + +import ( + "bytes" + "fmt" + "log" + "strconv" + + "github.com/pkg/errors" + + "sigs.k8s.io/kustomize/api/resid" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/yaml" + + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" + "sigs.k8s.io/kustomize/kyaml/kio" + + "sigs.k8s.io/kustomize/kyaml/runfn" + "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" +) + +const ( + idAnnotation = "kustomize.config.k8s.io/id" + HashAnnotation = "kustomize.config.k8s.io/needs-hash" + BehaviorAnnotation = "kustomize.config.k8s.io/behavior" +) + +type FnPlugin struct { + // Function runner + RunFns runfn.RunFns + + // Plugin configuration data. + cfg []byte + + // PluginHelpers + h *resmap.PluginHelpers +} + +func bytesToRNode(yml []byte) (*kyaml.RNode, error) { + rnode, err := kyaml.Parse(string(yml)) + if err != nil { + return nil, err + } + return rnode, nil +} + +func resourceToRNode(res *resource.Resource) (*kyaml.RNode, error) { + yml, err := res.AsYAML() + if err != nil { + return nil, err + } + + return bytesToRNode(yml) +} + +func GetFunctionSpec(res *resource.Resource) (*runtimeutil.FunctionSpec, error) { + rnode, err := resourceToRNode(res) + if err != nil { + return nil, err + } + + fSpec := runtimeutil.GetFunctionSpec(rnode) + if fSpec == nil { + return nil, fmt.Errorf("resource %v doesn't contain function spec", res.GetGvk()) + } + + return fSpec, nil +} + +func toStorageMounts(mounts []string) []runtimeutil.StorageMount { + var sms []runtimeutil.StorageMount + for _, mount := range mounts { + sms = append(sms, runtimeutil.StringToStorageMount(mount)) + } + return sms +} + +func NewFnPlugin(o *types.FnPluginLoadingOptions) *FnPlugin { + log.Printf("options: %v\n", o) + return &FnPlugin{ + RunFns: runfn.RunFns{ + Functions: []*kyaml.RNode{}, + Network: o.Network, + NetworkName: o.NetworkName, + EnableStarlark: o.EnableStar, + EnableExec: o.EnableExec, + StorageMounts: toStorageMounts(o.Mounts), + }, + } +} + +func (p *FnPlugin) Cfg() []byte { + return p.cfg +} + +func (p *FnPlugin) Config(h *resmap.PluginHelpers, config []byte) error { + p.h = h + p.cfg = config + + rnode, err := bytesToRNode(config) + if err != nil { + return err + } + + p.RunFns.Functions = append(p.RunFns.Functions, rnode) + + return nil +} + +func (p *FnPlugin) Generate() (resmap.ResMap, error) { + output, err := p.invokePlugin(nil) + if err != nil { + return nil, err + } + rm, err := p.h.ResmapFactory().NewResMapFromBytes(output) + if err != nil { + return nil, err + } + return p.UpdateResourceOptions(rm) +} + +func (p *FnPlugin) Transform(rm resmap.ResMap) error { + // add ResIds as annotations to all objects so that we can add them back + inputRM, err := p.getResMapWithIdAnnotation(rm) + if err != nil { + return err + } + + // encode the ResMap so it can be fed to the plugin + resources, err := inputRM.AsYaml() + if err != nil { + return err + } + + // invoke the plugin with resources as the input + output, err := p.invokePlugin(resources) + if err != nil { + return fmt.Errorf("%v %s", err, string(output)) + } + + // update the original ResMap based on the output + return p.updateResMapValues(output, rm) +} + +// invokePlugin uses Function runner to run function as plugin +func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) { + // Transform to ResourceList + var inOut bytes.Buffer + inIn := bytes.NewReader(input) + + err := kio.Pipeline{ + Inputs: []kio.Reader{&kio.ByteReader{Reader: inIn}}, + Outputs: []kio.Writer{kio.ByteWriter{ + Writer: &inOut, + WrappingKind: kio.ResourceListKind, + WrappingAPIVersion: kio.ResourceListAPIVersion,}}, + }.Execute() + if err != nil { + return nil, errors.Wrap( + err, "couldn't transform to ResourceList") + } + + //log.Printf("converted to:\n%s\n", inOut.String()) + + // Execute Fn (it's configured - see Config()) + var runFnsOut bytes.Buffer + p.RunFns.Input = bytes.NewReader(inOut.Bytes()) + p.RunFns.Output = &runFnsOut + + err = p.RunFns.Execute() + if err != nil { + return nil, errors.Wrap( + err, "couln't execute function") + } + + //log.Printf("fn returned:\n%s\n", runFnsOut.String()) + + // Convert back to a single multi-yaml doc + var outOut bytes.Buffer + outIn := bytes.NewReader(runFnsOut.Bytes()) + + err = kio.Pipeline{ + Inputs: []kio.Reader{&kio.ByteReader{Reader: outIn}}, + Outputs: []kio.Writer{kio.ByteWriter{Writer: &outOut}}, + }.Execute() + if err != nil { + return nil, errors.Wrap( + err, "couldn't transform from ResourceList") + } + + //log.Printf("converted back to:\n%s\n", outOut.String()) + + return outOut.Bytes(), nil +} + +// Returns a new copy of the given ResMap with the ResIds annotated in each Resource +func (p *FnPlugin) getResMapWithIdAnnotation(rm resmap.ResMap) (resmap.ResMap, error) { + inputRM := rm.DeepCopy() + for _, r := range inputRM.Resources() { + idString, err := yaml.Marshal(r.CurId()) + if err != nil { + return nil, err + } + annotations := r.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations[idAnnotation] = string(idString) + r.SetAnnotations(annotations) + } + return inputRM, nil +} + +// updateResMapValues updates the Resource value in the given ResMap +// with the emitted Resource values in output. +func (p *FnPlugin) updateResMapValues(output []byte, rm resmap.ResMap) error { + outputRM, err := p.h.ResmapFactory().NewResMapFromBytes(output) + if err != nil { + return err + } + for _, r := range outputRM.Resources() { + // for each emitted Resource, find the matching Resource in the original ResMap + // using its id + annotations := r.GetAnnotations() + idString, ok := annotations[idAnnotation] + if !ok { + return fmt.Errorf("the transformer should not remove annotation %s", + idAnnotation) + } + id := resid.ResId{} + err := yaml.Unmarshal([]byte(idString), &id) + if err != nil { + return err + } + res, err := rm.GetByCurrentId(id) + if err != nil { + return fmt.Errorf("unable to find unique match to %s", id.String()) + } + // remove the annotation set by Kustomize to track the resource + delete(annotations, idAnnotation) + if len(annotations) == 0 { + annotations = nil + } + r.SetAnnotations(annotations) + + // update the ResMap resource value with the transformed object + res.Kunstructured = r.Kunstructured + } + return nil +} + +// updateResourceOptions updates the generator options for each resource in the +// given ResMap based on plugin provided annotations. +func (p *FnPlugin) UpdateResourceOptions(rm resmap.ResMap) (resmap.ResMap, error) { + for _, r := range rm.Resources() { + // Disable name hashing by default and require plugin to explicitly + // request it for each resource. + annotations := r.GetAnnotations() + behavior := annotations[BehaviorAnnotation] + var needsHash bool + if val, ok := annotations[HashAnnotation]; ok { + b, err := strconv.ParseBool(val) + if err != nil { + return nil, fmt.Errorf( + "the annotation %q contains an invalid value (%q)", + HashAnnotation, val) + } + needsHash = b + } + delete(annotations, HashAnnotation) + delete(annotations, BehaviorAnnotation) + if len(annotations) == 0 { + annotations = nil + } + r.SetAnnotations(annotations) + r.SetOptions(types.NewGenArgs( + &types.GeneratorArgs{ + Behavior: behavior, + Options: &types.GeneratorOptions{DisableNameSuffixHash: !needsHash}})) + } + return rm, nil +} diff --git a/api/internal/plugins/loader/loader.go b/api/internal/plugins/loader/loader.go index 09dc3a167..25cb12ebf 100644 --- a/api/internal/plugins/loader/loader.go +++ b/api/internal/plugins/loader/loader.go @@ -16,6 +16,7 @@ import ( "sigs.k8s.io/kustomize/api/ifc" "sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers" "sigs.k8s.io/kustomize/api/internal/plugins/execplugin" + "sigs.k8s.io/kustomize/api/internal/plugins/fnplugin" "sigs.k8s.io/kustomize/api/internal/plugins/utils" "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/resid" @@ -116,7 +117,7 @@ func (l *Loader) loadAndConfigurePlugin( if isBuiltinPlugin(res) { switch l.pc.BpLoadingOptions { case types.BploLoadFromFileSys: - c, err = l.loadPlugin(res.OrgId()) + c, err = l.loadPlugin(res) case types.BploUseStaticallyLinked: // Instead of looking for and loading a .so file, // instantiate the plugin from a generated factory @@ -131,7 +132,7 @@ func (l *Loader) loadAndConfigurePlugin( } else { switch l.pc.PluginRestrictions { case types.PluginRestrictionsNone: - c, err = l.loadPlugin(res.OrgId()) + c, err = l.loadPlugin(res) case types.PluginRestrictionsBuiltinsOnly: err = types.NewErrOnlyBuiltinPluginsAllowed(res.OrgId().Kind) default: @@ -166,7 +167,15 @@ func (l *Loader) makeBuiltinPlugin(r resid.Gvk) (resmap.Configurable, error) { return nil, errors.Errorf("unable to load builtin %s", r) } -func (l *Loader) loadPlugin(resId resid.ResId) (resmap.Configurable, error) { +func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error) { + _, err := fnplugin.GetFunctionSpec(res) + if err == nil { + return fnplugin.NewFnPlugin(&l.pc.FnpLoadingOptions), nil + } + return l.loadExecOrGoPlugin(res.OrgId()) +} + +func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, error) { // First try to load the plugin as an executable. p := execplugin.NewExecPlugin(l.absolutePluginPath(resId)) err := p.ErrIfNotExecutable() diff --git a/api/types/pluginconfig.go b/api/types/pluginconfig.go index 88c0ade77..9b48c6771 100644 --- a/api/types/pluginconfig.go +++ b/api/types/pluginconfig.go @@ -29,4 +29,7 @@ type PluginConfig struct { // BpLoadingOptions distinguishes builtin plugin behaviors. BpLoadingOptions BuiltinPluginLoadingOptions + + // FnpLoadingOpeions sets the way function-based plugin behaviors. + FnpLoadingOptions FnPluginLoadingOptions } diff --git a/api/types/pluginrestrictions.go b/api/types/pluginrestrictions.go index a9953c00f..81478a8da 100644 --- a/api/types/pluginrestrictions.go +++ b/api/types/pluginrestrictions.go @@ -41,3 +41,16 @@ const ( // to generate static code. BploLoadFromFileSys ) + +// FnPluginLoadingOptions set way functions-based pluing are restricted +type FnPluginLoadingOptions struct { + // Allow to run executables + EnableExec bool + // Allow to run starlark + EnableStar bool + // Allow container access to network + Network bool + NetworkName string + // list of mounts + Mounts []string +} diff --git a/kustomize/internal/commands/build/build.go b/kustomize/internal/commands/build/build.go index 8cadb15ea..76219b8ef 100644 --- a/kustomize/internal/commands/build/build.go +++ b/kustomize/internal/commands/build/build.go @@ -25,6 +25,7 @@ type Options struct { kustomizationPath string outputPath string outOrder reorderOutput + fnOptions types.FnPluginLoadingOptions } // NewOptions creates a Options object @@ -74,10 +75,27 @@ func NewCmdBuild(out io.Writer) *cobra.Command { &o.outputPath, "output", "o", "", "If specified, write the build output to this path.") + cmd.Flags().BoolVar( + &o.fnOptions.EnableExec, "enable-exec", false /*do not change!*/, + "enable support for exec functions -- note: exec functions run arbitrary code -- do not use for untrusted configs!!! (Alpha)") + cmd.Flags().BoolVar( + &o.fnOptions.EnableStar, "enable-star", false, + "enable support for starlark functions. (Alpha)") + cmd.Flags().BoolVar( + &o.fnOptions.Network, "network", false, + "enable network access for functions that declare it") + cmd.Flags().StringVar( + &o.fnOptions.NetworkName, "network-name", "bridge", + "the docker network to run the container in") + cmd.Flags().StringArrayVar( + &o.fnOptions.Mounts, "mount", []string{}, + "a list of storage options read from the filesystem") + addFlagLoadRestrictor(cmd.Flags()) addFlagEnablePlugins(cmd.Flags()) addFlagReorderOutput(cmd.Flags()) addFlagEnableManagedbyLabel(cmd.Flags()) + return cmd } @@ -111,6 +129,9 @@ func (o *Options) makeOptions() *krusty.Options { if err != nil { log.Fatal(err) } + + c.FnpLoadingOptions = o.fnOptions + opts.PluginConfig = c } else { opts.PluginConfig = konfig.DisabledPluginConfig() From b78464c8b1136d72c4004f8d0964013c70099272 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Sun, 14 Jun 2020 03:28:19 +0000 Subject: [PATCH 02/22] Made generators work in addition to transformers Made go fmt to make linter happy --- api/internal/plugins/fnplugin/fnplugin.go | 80 +++++++++++++--------- api/internal/plugins/loader/loader.go | 2 +- api/types/pluginconfig.go | 2 +- api/types/pluginrestrictions.go | 10 +-- kustomize/internal/commands/build/build.go | 2 +- 5 files changed, 54 insertions(+), 42 deletions(-) diff --git a/api/internal/plugins/fnplugin/fnplugin.go b/api/internal/plugins/fnplugin/fnplugin.go index 89063d7d0..1b163e1f9 100644 --- a/api/internal/plugins/fnplugin/fnplugin.go +++ b/api/internal/plugins/fnplugin/fnplugin.go @@ -6,7 +6,7 @@ package fnplugin import ( "bytes" "fmt" - "log" + //"log" "strconv" "github.com/pkg/errors" @@ -17,22 +17,22 @@ import ( "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/yaml" - kyaml "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/kio" + kyaml "sigs.k8s.io/kustomize/kyaml/yaml" - "sigs.k8s.io/kustomize/kyaml/runfn" "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" + "sigs.k8s.io/kustomize/kyaml/runfn" ) const ( - idAnnotation = "kustomize.config.k8s.io/id" - HashAnnotation = "kustomize.config.k8s.io/needs-hash" - BehaviorAnnotation = "kustomize.config.k8s.io/behavior" + idAnnotation = "kustomize.config.k8s.io/id" + HashAnnotation = "kustomize.config.k8s.io/needs-hash" + BehaviorAnnotation = "kustomize.config.k8s.io/behavior" ) type FnPlugin struct { // Function runner - RunFns runfn.RunFns + runFns runfn.RunFns // Plugin configuration data. cfg []byte @@ -42,11 +42,11 @@ type FnPlugin struct { } func bytesToRNode(yml []byte) (*kyaml.RNode, error) { - rnode, err := kyaml.Parse(string(yml)) - if err != nil { - return nil, err - } - return rnode, nil + rnode, err := kyaml.Parse(string(yml)) + if err != nil { + return nil, err + } + return rnode, nil } func resourceToRNode(res *resource.Resource) (*kyaml.RNode, error) { @@ -81,10 +81,10 @@ func toStorageMounts(mounts []string) []runtimeutil.StorageMount { } func NewFnPlugin(o *types.FnPluginLoadingOptions) *FnPlugin { - log.Printf("options: %v\n", o) + //log.Printf("options: %v\n", o) return &FnPlugin{ - RunFns: runfn.RunFns{ - Functions: []*kyaml.RNode{}, + runFns: runfn.RunFns{ + Functions: []*kyaml.RNode{}, Network: o.Network, NetworkName: o.NetworkName, EnableStarlark: o.EnableStar, @@ -101,14 +101,6 @@ func (p *FnPlugin) Cfg() []byte { func (p *FnPlugin) Config(h *resmap.PluginHelpers, config []byte) error { p.h = h p.cfg = config - - rnode, err := bytesToRNode(config) - if err != nil { - return err - } - - p.RunFns.Functions = append(p.RunFns.Functions, rnode) - return nil } @@ -149,30 +141,50 @@ func (p *FnPlugin) Transform(rm resmap.ResMap) error { // invokePlugin uses Function runner to run function as plugin func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) { + // get config rnode + rnode, err := bytesToRNode(p.cfg) + if err != nil { + return nil, err + } + err = rnode.PipeE(kyaml.SetAnnotation("config.kubernetes.io/local-config", "true")) + if err != nil { + return nil, err + } + + // we need to add config as input for generators. Some of them don't work with FunctionConfig + // and in addition kio.Pipeline won't create anything if there are no objects + // see https://github.com/kubernetes-sigs/kustomize/blob/master/kyaml/kio/kio.go#L93 + if input == nil { + yaml, err := rnode.String() + if err != nil { + return nil, err + } + input = []byte(yaml) + } + // Transform to ResourceList var inOut bytes.Buffer inIn := bytes.NewReader(input) - - err := kio.Pipeline{ - Inputs: []kio.Reader{&kio.ByteReader{Reader: inIn}}, + err = kio.Pipeline{ + Inputs: []kio.Reader{&kio.ByteReader{Reader: inIn}}, Outputs: []kio.Writer{kio.ByteWriter{ Writer: &inOut, WrappingKind: kio.ResourceListKind, - WrappingAPIVersion: kio.ResourceListAPIVersion,}}, + WrappingAPIVersion: kio.ResourceListAPIVersion}}, }.Execute() if err != nil { return nil, errors.Wrap( - err, "couldn't transform to ResourceList") + err, "couldn't transform to ResourceList") } - //log.Printf("converted to:\n%s\n", inOut.String()) - // Execute Fn (it's configured - see Config()) + // Configure and Execute Fn var runFnsOut bytes.Buffer - p.RunFns.Input = bytes.NewReader(inOut.Bytes()) - p.RunFns.Output = &runFnsOut + p.runFns.Input = bytes.NewReader(inOut.Bytes()) + p.runFns.Functions = append(p.runFns.Functions, rnode) + p.runFns.Output = &runFnsOut - err = p.RunFns.Execute() + err = p.runFns.Execute() if err != nil { return nil, errors.Wrap( err, "couln't execute function") @@ -186,7 +198,7 @@ func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) { err = kio.Pipeline{ Inputs: []kio.Reader{&kio.ByteReader{Reader: outIn}}, - Outputs: []kio.Writer{kio.ByteWriter{Writer: &outOut}}, + Outputs: []kio.Writer{kio.ByteWriter{Writer: &outOut}}, }.Execute() if err != nil { return nil, errors.Wrap( diff --git a/api/internal/plugins/loader/loader.go b/api/internal/plugins/loader/loader.go index 25cb12ebf..393f665f9 100644 --- a/api/internal/plugins/loader/loader.go +++ b/api/internal/plugins/loader/loader.go @@ -16,7 +16,7 @@ import ( "sigs.k8s.io/kustomize/api/ifc" "sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers" "sigs.k8s.io/kustomize/api/internal/plugins/execplugin" - "sigs.k8s.io/kustomize/api/internal/plugins/fnplugin" + "sigs.k8s.io/kustomize/api/internal/plugins/fnplugin" "sigs.k8s.io/kustomize/api/internal/plugins/utils" "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/resid" diff --git a/api/types/pluginconfig.go b/api/types/pluginconfig.go index 9b48c6771..2756f9826 100644 --- a/api/types/pluginconfig.go +++ b/api/types/pluginconfig.go @@ -30,6 +30,6 @@ type PluginConfig struct { // BpLoadingOptions distinguishes builtin plugin behaviors. BpLoadingOptions BuiltinPluginLoadingOptions - // FnpLoadingOpeions sets the way function-based plugin behaviors. + // FnpLoadingOptions sets the way function-based plugin behaviors. FnpLoadingOptions FnPluginLoadingOptions } diff --git a/api/types/pluginrestrictions.go b/api/types/pluginrestrictions.go index 81478a8da..f5588baa4 100644 --- a/api/types/pluginrestrictions.go +++ b/api/types/pluginrestrictions.go @@ -45,12 +45,12 @@ const ( // FnPluginLoadingOptions set way functions-based pluing are restricted type FnPluginLoadingOptions struct { // Allow to run executables - EnableExec bool + EnableExec bool // Allow to run starlark - EnableStar bool + EnableStar bool // Allow container access to network - Network bool - NetworkName string + Network bool + NetworkName string // list of mounts - Mounts []string + Mounts []string } diff --git a/kustomize/internal/commands/build/build.go b/kustomize/internal/commands/build/build.go index 76219b8ef..b83b48b32 100644 --- a/kustomize/internal/commands/build/build.go +++ b/kustomize/internal/commands/build/build.go @@ -76,7 +76,7 @@ func NewCmdBuild(out io.Writer) *cobra.Command { "output", "o", "", "If specified, write the build output to this path.") cmd.Flags().BoolVar( - &o.fnOptions.EnableExec, "enable-exec", false /*do not change!*/, + &o.fnOptions.EnableExec, "enable-exec", false, /*do not change!*/ "enable support for exec functions -- note: exec functions run arbitrary code -- do not use for untrusted configs!!! (Alpha)") cmd.Flags().BoolVar( &o.fnOptions.EnableStar, "enable-star", false, From b3951942e37a11e2cf0bcdfa77d141ffe838b07c Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Sun, 14 Jun 2020 03:37:56 +0000 Subject: [PATCH 03/22] removed commented import to make linter happy --- api/internal/plugins/fnplugin/fnplugin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/internal/plugins/fnplugin/fnplugin.go b/api/internal/plugins/fnplugin/fnplugin.go index 1b163e1f9..98c8b1eb0 100644 --- a/api/internal/plugins/fnplugin/fnplugin.go +++ b/api/internal/plugins/fnplugin/fnplugin.go @@ -6,7 +6,6 @@ package fnplugin import ( "bytes" "fmt" - //"log" "strconv" "github.com/pkg/errors" From 448c060084763431da2dff4468ef8470660661f6 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 04:13:59 +0000 Subject: [PATCH 04/22] Added tests for fn-based generators and transformers --- api/krusty/fnplugin_test.go | 319 ++++++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 api/krusty/fnplugin_test.go diff --git a/api/krusty/fnplugin_test.go b/api/krusty/fnplugin_test.go new file mode 100644 index 000000000..cfc6aed6d --- /dev/null +++ b/api/krusty/fnplugin_test.go @@ -0,0 +1,319 @@ +package krusty_test + +import ( + "testing" + + kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" +) + +func TestFnGenerator(t *testing.T) { + th := kusttest_test.MakeEnhancedHarness(t) + defer th.Reset() + + th.WriteK("/app", ` +resources: +- short_secret.yaml +generators: +- gener.yaml +`) + // Create generator config + th.WriteF("/app/gener.yaml", ` +apiVersion: examples.config.kubernetes.io/v1beta1 +kind: CockroachDB +metadata: + name: demo + annotations: + config.kubernetes.io/function: | + container: + image: gcr.io/kustomize-functions/example-cockroachdb:v0.1.0 +spec: + replicas: 3 +`) + // Create some additional resource just to make sure everything is added + th.WriteF("/app/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 +`) + m := th.Run("/app", th.MakeOptionsPluginsEnabled()) + th.AssertActualEqualsExpected(m, ` +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: "true" + name: node1-bmc-secret +stringData: + userData: | + bootcmd: + - mkdir /mnt/vda +type: Opaque +--- +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + annotations: + config.kubernetes.io/path: config/demo-budget_poddisruptionbudget.yaml + labels: + app: cockroachdb + name: demo + name: demo-budget +spec: + minAvailable: 67% + selector: + matchLabels: + app: cockroachdb + name: demo +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + config.kubernetes.io/path: config/demo-public_service.yaml + labels: + app: cockroachdb + name: demo + name: demo-public +spec: + ports: + - name: grpc + port: 26257 + targetPort: 26257 + - name: http + port: 8080 + targetPort: 8080 + selector: + app: cockroachdb + name: demo +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + config.kubernetes.io/path: config/demo_service.yaml + prometheus.io/path: _status/vars + prometheus.io/port: "8080" + prometheus.io/scrape: "true" + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + labels: + app: cockroachdb + name: demo + name: demo +spec: + clusterIP: None + ports: + - name: grpc + port: 26257 + targetPort: 26257 + - name: http + port: 8080 + targetPort: 8080 + selector: + app: cockroachdb + name: demo +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + annotations: + config.kubernetes.io/path: config/demo_statefulset.yaml + labels: + app: cockroachdb + name: demo + name: demo +spec: + replicas: 3 + selector: + matchLabels: + app: cockroachdb + name: demo + serviceName: demo + template: + metadata: + labels: + app: cockroachdb + name: demo + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - cockroachdb + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - command: + - /bin/bash + - -ecx + - | + # The use of qualified ` + "`hostname -f`" + ` is crucial: + # Other nodes aren't able to look up the unqualified hostname. + CRARGS=("start" "--logtostderr" "--insecure" "--host" "$(hostname -f)" "--http-host" "0.0.0.0") + # We only want to initialize a new cluster (by omitting the join flag) + # if we're sure that we're the first node (i.e. index 0) and that + # there aren't any other nodes running as part of the cluster that + # this is supposed to be a part of (which indicates that a cluster + # already exists and we should make sure not to create a new one). + # It's fine to run without --join on a restart if there aren't any + # other nodes. + if [ ! "$(hostname)" == "cockroachdb-0" ] || [ -e "/cockroach/cockroach-data/cluster_exists_marker" ] + then + # We don't join cockroachdb in order to avoid a node attempting + # to join itself, which currently doesn't work + # (https://github.com/cockroachdb/cockroach/issues/9625). + CRARGS+=("--join" "cockroachdb-public") + fi + exec /cockroach/cockroach ${CRARGS[*]} + image: cockroachdb/cockroach:v1.1.0 + imagePullPolicy: IfNotPresent + name: demo + ports: + - containerPort: 26257 + name: grpc + - containerPort: 8080 + name: http + volumeMounts: + - mountPath: /cockroach/cockroach-data + name: datadir + initContainers: + - args: + - -on-start=/on-start.sh + - -service=cockroachdb + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: cockroachdb/cockroach-k8s-init:0.1 + imagePullPolicy: IfNotPresent + name: bootstrap + volumeMounts: + - mountPath: /cockroach/cockroach-data + name: datadir + terminationGracePeriodSeconds: 60 + volumes: + - name: datadir + persistentVolumeClaim: + claimName: datadir + volumeClaimTemplates: + - metadata: + name: datadir + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +`) +} + +func TestFnTransformer(t *testing.T) { + th := kusttest_test.MakeEnhancedHarness(t) + defer th.Reset() + + th.WriteK("/app", ` +resources: +- data.yaml +transformers: +- transf1.yaml +- transf2.yaml +`) + + th.WriteF("/app/data.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx + annotations: + tshirt-size: small # this injects the resource reservations +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx +`) + // This transformer should add resource reservations based on annotation in data.yaml + // See https://github.com/kubernetes-sigs/kustomize/tree/master/functions/examples/injection-tshirt-sizes + th.WriteF("/app/transf1.yaml", ` +apiVersion: examples.config.kubernetes.io/v1beta1 +kind: Validator +metadata: + name: valid + annotations: + config.kubernetes.io/function: |- + container: + image: gcr.io/kustomize-functions/example-tshirt:v0.2.0 +`) + // This transformer will check resources without and won't do any changes + // See https://github.com/kubernetes-sigs/kustomize/tree/master/functions/examples/validator-kubeval + th.WriteF("/app/transf2.yaml", ` +apiVersion: examples.config.kubernetes.io/v1beta1 +kind: Kubeval +metadata: + name: validate + annotations: + config.kubernetes.io/function: | + container: + image: gcr.io/kustomize-functions/example-validator-kubeval:v0.1.0 +spec: + strict: true + ignoreMissingSchemas: true + + # TODO: Update this to use network/volumes features. + # Relevant issues: + # - https://github.com/kubernetes-sigs/kustomize/issues/1901 + # - https://github.com/kubernetes-sigs/kustomize/issues/1902 + kubernetesVersion: "1.16.0" + schemaLocation: "file:///schemas" +`) + m := th.Run("/app", th.MakeOptionsPluginsEnabled()) + th.AssertActualEqualsExpected(m, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + config.kubernetes.io/path: deployment_nginx.yaml + tshirt-size: small + labels: + app: nginx + name: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx + resources: + requests: + cpu: 200m + memory: 50M +`) +} From 1aca8b8b9e96b46d4ddb21a28da39ac41b4c3f88 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 04:27:27 +0000 Subject: [PATCH 05/22] Corrected literal to make lint happy --- api/krusty/fnplugin_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/krusty/fnplugin_test.go b/api/krusty/fnplugin_test.go index cfc6aed6d..5a321d52d 100644 --- a/api/krusty/fnplugin_test.go +++ b/api/krusty/fnplugin_test.go @@ -159,7 +159,7 @@ spec: - /bin/bash - -ecx - | - # The use of qualified ` + "`hostname -f`" + ` is crucial: + # The use of qualified `+"`hostname -f`"+` is crucial: # Other nodes aren't able to look up the unqualified hostname. CRARGS=("start" "--logtostderr" "--insecure" "--host" "$(hostname -f)" "--http-host" "0.0.0.0") # We only want to initialize a new cluster (by omitting the join flag) From 6e91e0667d926364101a4c5c19f8e064dd2985e4 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 05:09:47 +0000 Subject: [PATCH 06/22] Disabled tests because we don't have docker installed --- api/internal/plugins/fnplugin/fnplugin.go | 2 +- api/krusty/fnplugin_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/internal/plugins/fnplugin/fnplugin.go b/api/internal/plugins/fnplugin/fnplugin.go index 98c8b1eb0..0b2853407 100644 --- a/api/internal/plugins/fnplugin/fnplugin.go +++ b/api/internal/plugins/fnplugin/fnplugin.go @@ -186,7 +186,7 @@ func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) { err = p.runFns.Execute() if err != nil { return nil, errors.Wrap( - err, "couln't execute function") + err, "couldn't execute function") } //log.Printf("fn returned:\n%s\n", runFnsOut.String()) diff --git a/api/krusty/fnplugin_test.go b/api/krusty/fnplugin_test.go index 5a321d52d..399f0d47f 100644 --- a/api/krusty/fnplugin_test.go +++ b/api/krusty/fnplugin_test.go @@ -6,7 +6,8 @@ import ( kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" ) -func TestFnGenerator(t *testing.T) { +// TODO: enable when get docker installed before these tests start +func disabled_TestFnGenerator(t *testing.T) { th := kusttest_test.MakeEnhancedHarness(t) defer th.Reset() @@ -220,7 +221,7 @@ spec: `) } -func TestFnTransformer(t *testing.T) { +func disabled_TestFnTransformer(t *testing.T) { th := kusttest_test.MakeEnhancedHarness(t) defer th.Reset() From afc14afe45c10de57fe6ffa26b24285564c17cbe Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 06:22:22 +0000 Subject: [PATCH 07/22] Trying to install rootless docker to run fn tests --- Makefile | 12 +++++++++--- api/krusty/fnplugin_test.go | 5 ++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 69878c008..30a4ec259 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,9 @@ # Makefile for kustomize CLI and API. MYGOBIN := $(shell go env GOPATH)/bin +MYDOCKERBIN := $(HOME)/bin SHELL := /bin/bash -export PATH := $(MYGOBIN):$(PATH) +export PATH := $(MYGOBIN):$(MYDOCKERBIN):$(PATH) .PHONY: all all: verify-kustomize @@ -194,9 +195,14 @@ lint-kustomize: install-tools $(builtinplugins) build-kustomize-api: $(builtinplugins) cd api; go build ./... +# Using the approach from https://docs.docker.com/engine/security/rootless/#install +# pinning docker 19.03.11 +$(MYDOCKERBIN)/docker: + curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | sh + .PHONY: test-unit-kustomize-api -test-unit-kustomize-api: build-kustomize-api - cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" +test-unit-kustomize-api: build-kustomize-api $(MYDOCKERBIN)/docker + cd api; DOCKER_HOST=unix://$(XDG_RUNTIME_DIR)/docker.sock go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" .PHONY: test-unit-kustomize-plugins test-unit-kustomize-plugins: diff --git a/api/krusty/fnplugin_test.go b/api/krusty/fnplugin_test.go index 399f0d47f..5a321d52d 100644 --- a/api/krusty/fnplugin_test.go +++ b/api/krusty/fnplugin_test.go @@ -6,8 +6,7 @@ import ( kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" ) -// TODO: enable when get docker installed before these tests start -func disabled_TestFnGenerator(t *testing.T) { +func TestFnGenerator(t *testing.T) { th := kusttest_test.MakeEnhancedHarness(t) defer th.Reset() @@ -221,7 +220,7 @@ spec: `) } -func disabled_TestFnTransformer(t *testing.T) { +func TestFnTransformer(t *testing.T) { th := kusttest_test.MakeEnhancedHarness(t) defer th.Reset() From 2c39ff0fa078504fc84be8801546583ef9709d2e Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 06:38:21 +0000 Subject: [PATCH 08/22] Changed to std docker to run fn tests --- Makefile | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 30a4ec259..77046573b 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,8 @@ # Makefile for kustomize CLI and API. MYGOBIN := $(shell go env GOPATH)/bin -MYDOCKERBIN := $(HOME)/bin SHELL := /bin/bash -export PATH := $(MYGOBIN):$(MYDOCKERBIN):$(PATH) +export PATH := $(MYGOBIN):$(PATH) .PHONY: all all: verify-kustomize @@ -195,14 +194,13 @@ lint-kustomize: install-tools $(builtinplugins) build-kustomize-api: $(builtinplugins) cd api; go build ./... -# Using the approach from https://docs.docker.com/engine/security/rootless/#install -# pinning docker 19.03.11 -$(MYDOCKERBIN)/docker: - curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | sh +.PHONY: install-docker +install-docker: + curl -fsSL https://get.docker.com| sh .PHONY: test-unit-kustomize-api -test-unit-kustomize-api: build-kustomize-api $(MYDOCKERBIN)/docker - cd api; DOCKER_HOST=unix://$(XDG_RUNTIME_DIR)/docker.sock go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" +test-unit-kustomize-api: build-kustomize-api install-docker + cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" .PHONY: test-unit-kustomize-plugins test-unit-kustomize-plugins: From 27cf3981cab96502d62a58d0926954ec08cb4187 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 06:49:18 +0000 Subject: [PATCH 09/22] Added post-install check for docker --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 77046573b..9121ea8e4 100644 --- a/Makefile +++ b/Makefile @@ -197,6 +197,8 @@ build-kustomize-api: $(builtinplugins) .PHONY: install-docker install-docker: curl -fsSL https://get.docker.com| sh + docker --version + docker run hello-world .PHONY: test-unit-kustomize-api test-unit-kustomize-api: build-kustomize-api install-docker From abf862fff173f77b826995e6015d898618525126 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 07:02:58 +0000 Subject: [PATCH 10/22] Added more post-install check for docker --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 9121ea8e4..68a31764b 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,8 @@ build-kustomize-api: $(builtinplugins) install-docker: curl -fsSL https://get.docker.com| sh docker --version + service docker status + service docker restart docker run hello-world .PHONY: test-unit-kustomize-api From e20e126d6552eb93625cec2cb5ad1a8a67717963 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 07:21:08 +0000 Subject: [PATCH 11/22] Switched back to rootless docker --- Makefile | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 68a31764b..67943cd54 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,9 @@ # Makefile for kustomize CLI and API. MYGOBIN := $(shell go env GOPATH)/bin +MYDOCKERBIN := $(HOME)/bin SHELL := /bin/bash -export PATH := $(MYGOBIN):$(PATH) +export PATH := $(MYGOBIN):$(MYDOCKERBIN):$(PATH) .PHONY: all all: verify-kustomize @@ -194,17 +195,14 @@ lint-kustomize: install-tools $(builtinplugins) build-kustomize-api: $(builtinplugins) cd api; go build ./... -.PHONY: install-docker -install-docker: - curl -fsSL https://get.docker.com| sh - docker --version - service docker status - service docker restart - docker run hello-world +# Using the approach from https://docs.docker.com/engine/security/rootless/#install +# pinning docker 19.03.11 +$(MYDOCKERBIN)/docker: + curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | FORCE_ROOTLESS_INSTALL=1 sh .PHONY: test-unit-kustomize-api -test-unit-kustomize-api: build-kustomize-api install-docker - cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" +test-unit-kustomize-api: build-kustomize-api $(MYDOCKERBIN)/docker + cd api; DOCKER_HOST=unix://$(XDG_RUNTIME_DIR)/docker.sock go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" .PHONY: test-unit-kustomize-plugins test-unit-kustomize-plugins: From 0dd191c0f65a709766bd9949bae7b7ba86e2fc1a Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 07:26:18 +0000 Subject: [PATCH 12/22] Added flag to skip iptables --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 67943cd54..5d081ee21 100644 --- a/Makefile +++ b/Makefile @@ -198,7 +198,7 @@ build-kustomize-api: $(builtinplugins) # Using the approach from https://docs.docker.com/engine/security/rootless/#install # pinning docker 19.03.11 $(MYDOCKERBIN)/docker: - curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | FORCE_ROOTLESS_INSTALL=1 sh + curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | SKIP_IPTABLES=1 FORCE_ROOTLESS_INSTALL=1 sh .PHONY: test-unit-kustomize-api test-unit-kustomize-api: build-kustomize-api $(MYDOCKERBIN)/docker From af1280ea43c13a58dad5edf38c1cbba7b8adf8c6 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 07:32:58 +0000 Subject: [PATCH 13/22] Install uidmap before docker --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 5d081ee21..49cb4b3fc 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,7 @@ build-kustomize-api: $(builtinplugins) # Using the approach from https://docs.docker.com/engine/security/rootless/#install # pinning docker 19.03.11 $(MYDOCKERBIN)/docker: + apt-get install -y uidmap curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | SKIP_IPTABLES=1 FORCE_ROOTLESS_INSTALL=1 sh .PHONY: test-unit-kustomize-api From de7fa4bf3a2ed8b5f274d56674b48fc4252c20e9 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 07:44:31 +0000 Subject: [PATCH 14/22] Check what is os release --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 49cb4b3fc..ca76a7cf9 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,7 @@ build-kustomize-api: $(builtinplugins) # Using the approach from https://docs.docker.com/engine/security/rootless/#install # pinning docker 19.03.11 $(MYDOCKERBIN)/docker: + cat /etc/os-release apt-get install -y uidmap curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | SKIP_IPTABLES=1 FORCE_ROOTLESS_INSTALL=1 sh From 57ca8fa3219c0fdaa287e8291d40252a59014b9a Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 08:00:31 +0000 Subject: [PATCH 15/22] Do update of repos --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index ca76a7cf9..a9a0c427a 100644 --- a/Makefile +++ b/Makefile @@ -199,6 +199,7 @@ build-kustomize-api: $(builtinplugins) # pinning docker 19.03.11 $(MYDOCKERBIN)/docker: cat /etc/os-release + apt -y update && apt -y upgrade apt-get install -y uidmap curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | SKIP_IPTABLES=1 FORCE_ROOTLESS_INSTALL=1 sh From 4646bca23033cb77e9a9d054687829aeeb593ab0 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 08:10:32 +0000 Subject: [PATCH 16/22] Added info about user to subuid --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a9a0c427a..f2adb6b4c 100644 --- a/Makefile +++ b/Makefile @@ -201,6 +201,7 @@ $(MYDOCKERBIN)/docker: cat /etc/os-release apt -y update && apt -y upgrade apt-get install -y uidmap + echo "root:100000:65536" >> /etc/subuid curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | SKIP_IPTABLES=1 FORCE_ROOTLESS_INSTALL=1 sh .PHONY: test-unit-kustomize-api From 80b3f4e00aea2f6981458885d564f4cb7b2dd1a1 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 08:15:14 +0000 Subject: [PATCH 17/22] Added info about user to subgid --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index f2adb6b4c..2525dd619 100644 --- a/Makefile +++ b/Makefile @@ -202,6 +202,7 @@ $(MYDOCKERBIN)/docker: apt -y update && apt -y upgrade apt-get install -y uidmap echo "root:100000:65536" >> /etc/subuid + echo "root:100000:65536" >> /etc/subgid curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | SKIP_IPTABLES=1 FORCE_ROOTLESS_INSTALL=1 sh .PHONY: test-unit-kustomize-api From dfc5c32af5d206bcde2722429c0df662389d809c Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 08:33:19 +0000 Subject: [PATCH 18/22] Added manual start of rootless docker --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2525dd619..da241a5a2 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,7 @@ $(MYDOCKERBIN)/docker: .PHONY: test-unit-kustomize-api test-unit-kustomize-api: build-kustomize-api $(MYDOCKERBIN)/docker - cd api; DOCKER_HOST=unix://$(XDG_RUNTIME_DIR)/docker.sock go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" + export XDG_RUNTIME_DIR=/tmp/docker-0; export DOCKER_HOST=unix:///tmp/docker-0/docker.sock; /root/bin/dockerd-rootless.sh --experimental --iptables=false --storage-driver vfs &; cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" .PHONY: test-unit-kustomize-plugins test-unit-kustomize-plugins: From f053ca6a5fa4e293e27c34062a4e7507912049d5 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 14:59:21 +0000 Subject: [PATCH 19/22] little fix --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index da241a5a2..4f439c51d 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,7 @@ $(MYDOCKERBIN)/docker: .PHONY: test-unit-kustomize-api test-unit-kustomize-api: build-kustomize-api $(MYDOCKERBIN)/docker - export XDG_RUNTIME_DIR=/tmp/docker-0; export DOCKER_HOST=unix:///tmp/docker-0/docker.sock; /root/bin/dockerd-rootless.sh --experimental --iptables=false --storage-driver vfs &; cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" + export XDG_RUNTIME_DIR=/tmp/docker-0; export DOCKER_HOST=unix:///tmp/docker-0/docker.sock; /root/bin/dockerd-rootless.sh --experimental --iptables=false --storage-driver vfs & cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" .PHONY: test-unit-kustomize-plugins test-unit-kustomize-plugins: From d732a6faaba453010bddf6194b54bd0fb5ece0e0 Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Mon, 15 Jun 2020 16:26:51 +0000 Subject: [PATCH 20/22] Gave up to install docker in pod. Just skipping tests that require docker if there is no binary --- Makefile | 17 +++-------------- api/krusty/fnplugin_test.go | 11 +++++++++++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 4f439c51d..69878c008 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,8 @@ # Makefile for kustomize CLI and API. MYGOBIN := $(shell go env GOPATH)/bin -MYDOCKERBIN := $(HOME)/bin SHELL := /bin/bash -export PATH := $(MYGOBIN):$(MYDOCKERBIN):$(PATH) +export PATH := $(MYGOBIN):$(PATH) .PHONY: all all: verify-kustomize @@ -195,19 +194,9 @@ lint-kustomize: install-tools $(builtinplugins) build-kustomize-api: $(builtinplugins) cd api; go build ./... -# Using the approach from https://docs.docker.com/engine/security/rootless/#install -# pinning docker 19.03.11 -$(MYDOCKERBIN)/docker: - cat /etc/os-release - apt -y update && apt -y upgrade - apt-get install -y uidmap - echo "root:100000:65536" >> /etc/subuid - echo "root:100000:65536" >> /etc/subgid - curl -fsSL https://raw.githubusercontent.com/docker/docker-install/3d1b8a8/rootless-install.sh | SKIP_IPTABLES=1 FORCE_ROOTLESS_INSTALL=1 sh - .PHONY: test-unit-kustomize-api -test-unit-kustomize-api: build-kustomize-api $(MYDOCKERBIN)/docker - export XDG_RUNTIME_DIR=/tmp/docker-0; export DOCKER_HOST=unix:///tmp/docker-0/docker.sock; /root/bin/dockerd-rootless.sh --experimental --iptables=false --storage-driver vfs & cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" +test-unit-kustomize-api: build-kustomize-api + cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222" .PHONY: test-unit-kustomize-plugins test-unit-kustomize-plugins: diff --git a/api/krusty/fnplugin_test.go b/api/krusty/fnplugin_test.go index 5a321d52d..8cbdcc22d 100644 --- a/api/krusty/fnplugin_test.go +++ b/api/krusty/fnplugin_test.go @@ -1,12 +1,21 @@ package krusty_test import ( + "os/exec" "testing" kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" ) +func skipIfNoDocker(t *testing.T) { + if _, err := exec.LookPath("docker"); err != nil { + t.Skip("skipping because docker binary wasn't found in PATH") + } +} + func TestFnGenerator(t *testing.T) { + skipIfNoDocker(t) + th := kusttest_test.MakeEnhancedHarness(t) defer th.Reset() @@ -221,6 +230,8 @@ spec: } func TestFnTransformer(t *testing.T) { + skipIfNoDocker(t) + th := kusttest_test.MakeEnhancedHarness(t) defer th.Reset() From 178f4e21f07bee967385014d81a3380f4f29540c Mon Sep 17 00:00:00 2001 From: Alexey Odinokov Date: Tue, 16 Jun 2020 04:25:25 +0000 Subject: [PATCH 21/22] Added test for exec-based function generator --- api/krusty/fnplugin_test.go | 80 +++++++++++++++++++++++++- api/krusty/fnplugin_test/fnexectest.sh | 26 +++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100755 api/krusty/fnplugin_test/fnexectest.sh diff --git a/api/krusty/fnplugin_test.go b/api/krusty/fnplugin_test.go index 8cbdcc22d..1aaa2ac90 100644 --- a/api/krusty/fnplugin_test.go +++ b/api/krusty/fnplugin_test.go @@ -7,13 +7,89 @@ import ( kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" ) +func TestFnExecGenerator(t *testing.T) { + th := kusttest_test.MakeEnhancedHarness(t) + defer th.Reset() + + th.WriteK("/app", ` +resources: +- short_secret.yaml +generators: +- gener.yaml +`) + + // Create some additional resource just to make sure everything is added + th.WriteF("/app/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("/app/gener.yaml", ` +kind: executable +metadata: + name: demo + annotations: + config.kubernetes.io/function: | + exec: + path: ./fnplugin_test/fnexectest.sh +spec: +`) + o := th.MakeOptionsPluginsEnabled() + o.PluginConfig.FnpLoadingOptions.EnableExec = true + m := th.Run("/app", o) + th.AssertActualEqualsExpected(m, ` +apiVersion: v1 +kind: Secret +metadata: + 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/path: deployment_nginx.yaml + tshirt-size: small + labels: + app: nginx + name: nginx +spec: + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - image: nginx + name: nginx +`) +} + func skipIfNoDocker(t *testing.T) { if _, err := exec.LookPath("docker"); err != nil { t.Skip("skipping because docker binary wasn't found in PATH") } } -func TestFnGenerator(t *testing.T) { +func TestFnContainerGenerator(t *testing.T) { skipIfNoDocker(t) th := kusttest_test.MakeEnhancedHarness(t) @@ -229,7 +305,7 @@ spec: `) } -func TestFnTransformer(t *testing.T) { +func TestFnContainerTransformer(t *testing.T) { skipIfNoDocker(t) th := kusttest_test.MakeEnhancedHarness(t) diff --git a/api/krusty/fnplugin_test/fnexectest.sh b/api/krusty/fnplugin_test/fnexectest.sh new file mode 100755 index 000000000..ed98a837c --- /dev/null +++ b/api/krusty/fnplugin_test/fnexectest.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# not sure if we want to generate bash scripts, since we always want to run +# only trusted executables +cat < Date: Wed, 17 Jun 2020 05:26:30 +0000 Subject: [PATCH 22/22] Refactoring to get rid of duplicated code --- api/internal/plugins/execplugin/execplugin.go | 100 +------------ .../plugins/execplugin/execplugin_test.go | 106 -------------- .../{fnplugin => execplugin}/fnplugin.go | 135 ++++-------------- api/internal/plugins/execplugin/utils.go | 108 ++++++++++++++ api/internal/plugins/execplugin/utils_test.go | 111 ++++++++++++++ api/internal/plugins/loader/loader.go | 5 +- 6 files changed, 252 insertions(+), 313 deletions(-) rename api/internal/plugins/{fnplugin => execplugin}/fnplugin.go (56%) create mode 100644 api/internal/plugins/execplugin/utils.go create mode 100644 api/internal/plugins/execplugin/utils_test.go diff --git a/api/internal/plugins/execplugin/execplugin.go b/api/internal/plugins/execplugin/execplugin.go index 5e63457fd..d4c7d0069 100644 --- a/api/internal/plugins/execplugin/execplugin.go +++ b/api/internal/plugins/execplugin/execplugin.go @@ -9,22 +9,16 @@ import ( "io/ioutil" "os" "os/exec" - "strconv" "strings" "github.com/google/shlex" "github.com/pkg/errors" - "sigs.k8s.io/kustomize/api/resid" "sigs.k8s.io/kustomize/api/resmap" - "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/yaml" ) const ( - idAnnotation = "kustomize.config.k8s.io/id" - HashAnnotation = "kustomize.config.k8s.io/needs-hash" - BehaviorAnnotation = "kustomize.config.k8s.io/behavior" tmpConfigFilePrefix = "kust-plugin-config-" ) @@ -114,12 +108,12 @@ func (p *ExecPlugin) Generate() (resmap.ResMap, error) { if err != nil { return nil, err } - return p.UpdateResourceOptions(rm) + return UpdateResourceOptions(rm) } func (p *ExecPlugin) Transform(rm resmap.ResMap) error { // add ResIds as annotations to all objects so that we can add them back - inputRM, err := p.getResMapWithIdAnnotation(rm) + inputRM, err := getResMapWithIdAnnotation(rm) if err != nil { return err } @@ -137,7 +131,7 @@ func (p *ExecPlugin) Transform(rm resmap.ResMap) error { } // update the original ResMap based on the output - return p.updateResMapValues(output, rm) + return updateResMapValues(p.path, p.h, output, rm) } // invokePlugin writes plugin config to a temp file, then @@ -184,91 +178,3 @@ func (p *ExecPlugin) getEnv() []string { "KUSTOMIZE_PLUGIN_CONFIG_ROOT="+p.h.Loader().Root()) return env } - -// Returns a new copy of the given ResMap with the ResIds annotated in each Resource -func (p *ExecPlugin) getResMapWithIdAnnotation(rm resmap.ResMap) (resmap.ResMap, error) { - inputRM := rm.DeepCopy() - for _, r := range inputRM.Resources() { - idString, err := yaml.Marshal(r.CurId()) - if err != nil { - return nil, err - } - annotations := r.GetAnnotations() - if annotations == nil { - annotations = make(map[string]string) - } - annotations[idAnnotation] = string(idString) - r.SetAnnotations(annotations) - } - return inputRM, nil -} - -// updateResMapValues updates the Resource value in the given ResMap -// with the emitted Resource values in output. -func (p *ExecPlugin) updateResMapValues(output []byte, rm resmap.ResMap) error { - outputRM, err := p.h.ResmapFactory().NewResMapFromBytes(output) - if err != nil { - return err - } - for _, r := range outputRM.Resources() { - // for each emitted Resource, find the matching Resource in the original ResMap - // using its id - annotations := r.GetAnnotations() - idString, ok := annotations[idAnnotation] - if !ok { - return fmt.Errorf("the transformer %s should not remove annotation %s", - p.path, idAnnotation) - } - id := resid.ResId{} - err := yaml.Unmarshal([]byte(idString), &id) - if err != nil { - return err - } - res, err := rm.GetByCurrentId(id) - if err != nil { - return fmt.Errorf("unable to find unique match to %s", id.String()) - } - // remove the annotation set by Kustomize to track the resource - delete(annotations, idAnnotation) - if len(annotations) == 0 { - annotations = nil - } - r.SetAnnotations(annotations) - - // update the resource value with the transformed object - res.ResetPrimaryData(r) - } - return nil -} - -// updateResourceOptions updates the generator options for each resource in the -// given ResMap based on plugin provided annotations. -func (p *ExecPlugin) UpdateResourceOptions(rm resmap.ResMap) (resmap.ResMap, error) { - for _, r := range rm.Resources() { - // Disable name hashing by default and require plugin to explicitly - // request it for each resource. - annotations := r.GetAnnotations() - behavior := annotations[BehaviorAnnotation] - var needsHash bool - if val, ok := annotations[HashAnnotation]; ok { - b, err := strconv.ParseBool(val) - if err != nil { - return nil, fmt.Errorf( - "the annotation %q contains an invalid value (%q)", - HashAnnotation, val) - } - needsHash = b - } - delete(annotations, HashAnnotation) - delete(annotations, BehaviorAnnotation) - if len(annotations) == 0 { - annotations = nil - } - r.SetAnnotations(annotations) - r.SetOptions(types.NewGenArgs( - &types.GeneratorArgs{ - Behavior: behavior, - Options: &types.GeneratorOptions{DisableNameSuffixHash: !needsHash}})) - } - return rm, nil -} diff --git a/api/internal/plugins/execplugin/execplugin_test.go b/api/internal/plugins/execplugin/execplugin_test.go index 249571dd7..d6b9021cf 100644 --- a/api/internal/plugins/execplugin/execplugin_test.go +++ b/api/internal/plugins/execplugin/execplugin_test.go @@ -4,7 +4,6 @@ package execplugin_test import ( - "fmt" "strings" "testing" @@ -17,7 +16,6 @@ import ( "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resource" valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest" - "sigs.k8s.io/kustomize/api/types" ) func TestExecPluginConfig(t *testing.T) { @@ -91,107 +89,3 @@ metadata: t.Fatalf("unexpected arg array: %#v", p.Args()) } } - -func makeConfigMap(rf *resource.Factory, name, behavior string, hashValue *string) *resource.Resource { - r := rf.FromMap(map[string]interface{}{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": map[string]interface{}{"name": name}, - }) - annotations := map[string]string{} - if behavior != "" { - annotations[BehaviorAnnotation] = behavior - } - if hashValue != nil { - annotations[HashAnnotation] = *hashValue - } - if len(annotations) > 0 { - r.SetAnnotations(annotations) - } - return r -} - -func makeConfigMapOptions(rf *resource.Factory, name, behavior string, disableHash bool) *resource.Resource { - return rf.FromMapAndOption(map[string]interface{}{ - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": map[string]interface{}{"name": name}, - }, &types.GeneratorArgs{ - Behavior: behavior, - Options: &types.GeneratorOptions{DisableNameSuffixHash: disableHash}}) -} - -func strptr(s string) *string { - return &s -} - -func TestUpdateResourceOptions(t *testing.T) { - p := NewExecPlugin("") - if err := p.ErrIfNotExecutable(); err == nil { - t.Fatalf("expected unexecutable error") - } - rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()) - in := resmap.New() - expected := resmap.New() - cases := []struct { - behavior string - needsHash bool - hashValue *string - }{ - {hashValue: strptr("false")}, - {hashValue: strptr("true"), needsHash: true}, - {behavior: "replace"}, - {behavior: "merge"}, - {behavior: "create"}, - {behavior: "nonsense"}, - {behavior: "merge", hashValue: strptr("false")}, - {behavior: "merge", hashValue: strptr("true"), needsHash: true}, - } - for i, c := range cases { - name := fmt.Sprintf("test%d", i) - in.Append(makeConfigMap(rf, name, c.behavior, c.hashValue)) - expected.Append(makeConfigMapOptions(rf, name, c.behavior, !c.needsHash)) - } - actual, err := p.UpdateResourceOptions(in) - if err != nil { - t.Fatalf("unexpected error: %v", err.Error()) - } - for i, a := range expected.Resources() { - b := actual.GetByIndex(i) - if b == nil { - t.Fatalf("resource %d missing from processed map", i) - } - if !a.Equals(b) { - t.Errorf("expected %v got %v", a, b) - } - if a.NeedHashSuffix() != b.NeedHashSuffix() { - t.Errorf("") - } - if a.Behavior() != b.Behavior() { - t.Errorf("expected %v got %v", a.Behavior(), b.Behavior()) - } - } -} - -func TestUpdateResourceOptionsWithInvalidHashAnnotationValues(t *testing.T) { - p := NewExecPlugin("") - if err := p.ErrIfNotExecutable(); err == nil { - t.Fatalf("expected unexecutable error") - } - rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()) - cases := []string{ - "", - "FaLsE", - "TrUe", - "potato", - } - for i, c := range cases { - name := fmt.Sprintf("test%d", i) - in := resmap.New() - in.Append(makeConfigMap(rf, name, "", &c)) - _, err := p.UpdateResourceOptions(in) - if err == nil { - t.Errorf("expected error from value %q", c) - } - } -} diff --git a/api/internal/plugins/fnplugin/fnplugin.go b/api/internal/plugins/execplugin/fnplugin.go similarity index 56% rename from api/internal/plugins/fnplugin/fnplugin.go rename to api/internal/plugins/execplugin/fnplugin.go index 0b2853407..fa56db953 100644 --- a/api/internal/plugins/fnplugin/fnplugin.go +++ b/api/internal/plugins/execplugin/fnplugin.go @@ -1,34 +1,25 @@ // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 -package fnplugin +package execplugin import ( "bytes" "fmt" - "strconv" "github.com/pkg/errors" - "sigs.k8s.io/kustomize/api/resid" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resource" "sigs.k8s.io/kustomize/api/types" - "sigs.k8s.io/yaml" "sigs.k8s.io/kustomize/kyaml/kio" - kyaml "sigs.k8s.io/kustomize/kyaml/yaml" + "sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil" "sigs.k8s.io/kustomize/kyaml/runfn" ) -const ( - idAnnotation = "kustomize.config.k8s.io/id" - HashAnnotation = "kustomize.config.k8s.io/needs-hash" - BehaviorAnnotation = "kustomize.config.k8s.io/behavior" -) - type FnPlugin struct { // Function runner runFns runfn.RunFns @@ -36,19 +27,22 @@ type FnPlugin struct { // Plugin configuration data. cfg []byte + // Plugin name cache for error output + pluginName string + // PluginHelpers h *resmap.PluginHelpers } -func bytesToRNode(yml []byte) (*kyaml.RNode, error) { - rnode, err := kyaml.Parse(string(yml)) +func bytesToRNode(yml []byte) (*yaml.RNode, error) { + rnode, err := yaml.Parse(string(yml)) if err != nil { return nil, err } return rnode, nil } -func resourceToRNode(res *resource.Resource) (*kyaml.RNode, error) { +func resourceToRNode(res *resource.Resource) (*yaml.RNode, error) { yml, err := res.AsYAML() if err != nil { return nil, err @@ -83,7 +77,7 @@ func NewFnPlugin(o *types.FnPluginLoadingOptions) *FnPlugin { //log.Printf("options: %v\n", o) return &FnPlugin{ runFns: runfn.RunFns{ - Functions: []*kyaml.RNode{}, + Functions: []*yaml.RNode{}, Network: o.Network, NetworkName: o.NetworkName, EnableStarlark: o.EnableStar, @@ -100,6 +94,21 @@ func (p *FnPlugin) Cfg() []byte { func (p *FnPlugin) Config(h *resmap.PluginHelpers, config []byte) error { p.h = h p.cfg = config + + rnode, err := bytesToRNode(p.cfg) + if err != nil { + return err + } + + meta, err := rnode.GetMeta() + if err != nil { + return err + } + + p.pluginName = fmt.Sprintf("api: %s, kind: %s, name: %s", + meta.APIVersion, meta.Kind, meta.Name) + //log.Printf("config based pluginName: %s", p.pluginName) + return nil } @@ -112,12 +121,12 @@ func (p *FnPlugin) Generate() (resmap.ResMap, error) { if err != nil { return nil, err } - return p.UpdateResourceOptions(rm) + return UpdateResourceOptions(rm) } func (p *FnPlugin) Transform(rm resmap.ResMap) error { // add ResIds as annotations to all objects so that we can add them back - inputRM, err := p.getResMapWithIdAnnotation(rm) + inputRM, err := getResMapWithIdAnnotation(rm) if err != nil { return err } @@ -135,7 +144,7 @@ func (p *FnPlugin) Transform(rm resmap.ResMap) error { } // update the original ResMap based on the output - return p.updateResMapValues(output, rm) + return updateResMapValues(p.pluginName, p.h, output, rm) } // invokePlugin uses Function runner to run function as plugin @@ -145,7 +154,7 @@ func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) { if err != nil { return nil, err } - err = rnode.PipeE(kyaml.SetAnnotation("config.kubernetes.io/local-config", "true")) + err = rnode.PipeE(yaml.SetAnnotation("config.kubernetes.io/local-config", "true")) if err != nil { return nil, err } @@ -208,91 +217,3 @@ func (p *FnPlugin) invokePlugin(input []byte) ([]byte, error) { return outOut.Bytes(), nil } - -// Returns a new copy of the given ResMap with the ResIds annotated in each Resource -func (p *FnPlugin) getResMapWithIdAnnotation(rm resmap.ResMap) (resmap.ResMap, error) { - inputRM := rm.DeepCopy() - for _, r := range inputRM.Resources() { - idString, err := yaml.Marshal(r.CurId()) - if err != nil { - return nil, err - } - annotations := r.GetAnnotations() - if annotations == nil { - annotations = make(map[string]string) - } - annotations[idAnnotation] = string(idString) - r.SetAnnotations(annotations) - } - return inputRM, nil -} - -// updateResMapValues updates the Resource value in the given ResMap -// with the emitted Resource values in output. -func (p *FnPlugin) updateResMapValues(output []byte, rm resmap.ResMap) error { - outputRM, err := p.h.ResmapFactory().NewResMapFromBytes(output) - if err != nil { - return err - } - for _, r := range outputRM.Resources() { - // for each emitted Resource, find the matching Resource in the original ResMap - // using its id - annotations := r.GetAnnotations() - idString, ok := annotations[idAnnotation] - if !ok { - return fmt.Errorf("the transformer should not remove annotation %s", - idAnnotation) - } - id := resid.ResId{} - err := yaml.Unmarshal([]byte(idString), &id) - if err != nil { - return err - } - res, err := rm.GetByCurrentId(id) - if err != nil { - return fmt.Errorf("unable to find unique match to %s", id.String()) - } - // remove the annotation set by Kustomize to track the resource - delete(annotations, idAnnotation) - if len(annotations) == 0 { - annotations = nil - } - r.SetAnnotations(annotations) - - // update the ResMap resource value with the transformed object - res.Kunstructured = r.Kunstructured - } - return nil -} - -// updateResourceOptions updates the generator options for each resource in the -// given ResMap based on plugin provided annotations. -func (p *FnPlugin) UpdateResourceOptions(rm resmap.ResMap) (resmap.ResMap, error) { - for _, r := range rm.Resources() { - // Disable name hashing by default and require plugin to explicitly - // request it for each resource. - annotations := r.GetAnnotations() - behavior := annotations[BehaviorAnnotation] - var needsHash bool - if val, ok := annotations[HashAnnotation]; ok { - b, err := strconv.ParseBool(val) - if err != nil { - return nil, fmt.Errorf( - "the annotation %q contains an invalid value (%q)", - HashAnnotation, val) - } - needsHash = b - } - delete(annotations, HashAnnotation) - delete(annotations, BehaviorAnnotation) - if len(annotations) == 0 { - annotations = nil - } - r.SetAnnotations(annotations) - r.SetOptions(types.NewGenArgs( - &types.GeneratorArgs{ - Behavior: behavior, - Options: &types.GeneratorOptions{DisableNameSuffixHash: !needsHash}})) - } - return rm, nil -} diff --git a/api/internal/plugins/execplugin/utils.go b/api/internal/plugins/execplugin/utils.go new file mode 100644 index 000000000..4c7ac8e66 --- /dev/null +++ b/api/internal/plugins/execplugin/utils.go @@ -0,0 +1,108 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package execplugin + +import ( + "fmt" + "strconv" + + "sigs.k8s.io/kustomize/api/resid" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/yaml" +) + +const ( + idAnnotation = "kustomize.config.k8s.io/id" + HashAnnotation = "kustomize.config.k8s.io/needs-hash" + BehaviorAnnotation = "kustomize.config.k8s.io/behavior" +) + +// Returns a new copy of the given ResMap with the ResIds annotated in each Resource +func getResMapWithIdAnnotation(rm resmap.ResMap) (resmap.ResMap, error) { + inputRM := rm.DeepCopy() + for _, r := range inputRM.Resources() { + idString, err := yaml.Marshal(r.CurId()) + if err != nil { + return nil, err + } + annotations := r.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations[idAnnotation] = string(idString) + r.SetAnnotations(annotations) + } + return inputRM, nil +} + +// updateResMapValues updates the Resource value in the given ResMap +// with the emitted Resource values in output. +func updateResMapValues(pluginName string, h *resmap.PluginHelpers, output []byte, rm resmap.ResMap) error { + outputRM, err := h.ResmapFactory().NewResMapFromBytes(output) + if err != nil { + return err + } + for _, r := range outputRM.Resources() { + // for each emitted Resource, find the matching Resource in the original ResMap + // using its id + annotations := r.GetAnnotations() + idString, ok := annotations[idAnnotation] + if !ok { + return fmt.Errorf("the transformer %s should not remove annotation %s", + pluginName, idAnnotation) + } + id := resid.ResId{} + err := yaml.Unmarshal([]byte(idString), &id) + if err != nil { + return err + } + res, err := rm.GetByCurrentId(id) + if err != nil { + return fmt.Errorf("unable to find unique match to %s", id.String()) + } + // remove the annotation set by Kustomize to track the resource + delete(annotations, idAnnotation) + if len(annotations) == 0 { + annotations = nil + } + r.SetAnnotations(annotations) + + // update the resource value with the transformed object + res.ResetPrimaryData(r) + } + return nil +} + +// updateResourceOptions updates the generator options for each resource in the +// given ResMap based on plugin provided annotations. +func UpdateResourceOptions(rm resmap.ResMap) (resmap.ResMap, error) { + for _, r := range rm.Resources() { + // Disable name hashing by default and require plugin to explicitly + // request it for each resource. + annotations := r.GetAnnotations() + behavior := annotations[BehaviorAnnotation] + var needsHash bool + if val, ok := annotations[HashAnnotation]; ok { + b, err := strconv.ParseBool(val) + if err != nil { + return nil, fmt.Errorf( + "the annotation %q contains an invalid value (%q)", + HashAnnotation, val) + } + needsHash = b + } + delete(annotations, HashAnnotation) + delete(annotations, BehaviorAnnotation) + if len(annotations) == 0 { + annotations = nil + } + r.SetAnnotations(annotations) + r.SetOptions(types.NewGenArgs( + &types.GeneratorArgs{ + Behavior: behavior, + Options: &types.GeneratorOptions{DisableNameSuffixHash: !needsHash}})) + } + return rm, nil +} diff --git a/api/internal/plugins/execplugin/utils_test.go b/api/internal/plugins/execplugin/utils_test.go new file mode 100644 index 000000000..93f9cb754 --- /dev/null +++ b/api/internal/plugins/execplugin/utils_test.go @@ -0,0 +1,111 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package execplugin_test + +import ( + "fmt" + "testing" + + . "sigs.k8s.io/kustomize/api/internal/plugins/execplugin" + "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" + "sigs.k8s.io/kustomize/api/types" +) + +func makeConfigMap(rf *resource.Factory, name, behavior string, hashValue *string) *resource.Resource { + r := rf.FromMap(map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{"name": name}, + }) + annotations := map[string]string{} + if behavior != "" { + annotations[BehaviorAnnotation] = behavior + } + if hashValue != nil { + annotations[HashAnnotation] = *hashValue + } + if len(annotations) > 0 { + r.SetAnnotations(annotations) + } + return r +} + +func makeConfigMapOptions(rf *resource.Factory, name, behavior string, disableHash bool) *resource.Resource { + return rf.FromMapAndOption(map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{"name": name}, + }, &types.GeneratorArgs{ + Behavior: behavior, + Options: &types.GeneratorOptions{DisableNameSuffixHash: disableHash}}) +} + +func strptr(s string) *string { + return &s +} + +func TestUpdateResourceOptions(t *testing.T) { + rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()) + in := resmap.New() + expected := resmap.New() + cases := []struct { + behavior string + needsHash bool + hashValue *string + }{ + {hashValue: strptr("false")}, + {hashValue: strptr("true"), needsHash: true}, + {behavior: "replace"}, + {behavior: "merge"}, + {behavior: "create"}, + {behavior: "nonsense"}, + {behavior: "merge", hashValue: strptr("false")}, + {behavior: "merge", hashValue: strptr("true"), needsHash: true}, + } + for i, c := range cases { + name := fmt.Sprintf("test%d", i) + in.Append(makeConfigMap(rf, name, c.behavior, c.hashValue)) + expected.Append(makeConfigMapOptions(rf, name, c.behavior, !c.needsHash)) + } + actual, err := UpdateResourceOptions(in) + if err != nil { + t.Fatalf("unexpected error: %v", err.Error()) + } + for i, a := range expected.Resources() { + b := actual.GetByIndex(i) + if b == nil { + t.Fatalf("resource %d missing from processed map", i) + } + if !a.Equals(b) { + t.Errorf("expected %v got %v", a, b) + } + if a.NeedHashSuffix() != b.NeedHashSuffix() { + t.Errorf("") + } + if a.Behavior() != b.Behavior() { + t.Errorf("expected %v got %v", a.Behavior(), b.Behavior()) + } + } +} + +func TestUpdateResourceOptionsWithInvalidHashAnnotationValues(t *testing.T) { + rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()) + cases := []string{ + "", + "FaLsE", + "TrUe", + "potato", + } + for i, c := range cases { + name := fmt.Sprintf("test%d", i) + in := resmap.New() + in.Append(makeConfigMap(rf, name, "", &c)) + _, err := UpdateResourceOptions(in) + if err == nil { + t.Errorf("expected error from value %q", c) + } + } +} diff --git a/api/internal/plugins/loader/loader.go b/api/internal/plugins/loader/loader.go index 393f665f9..c61f264c4 100644 --- a/api/internal/plugins/loader/loader.go +++ b/api/internal/plugins/loader/loader.go @@ -16,7 +16,6 @@ import ( "sigs.k8s.io/kustomize/api/ifc" "sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers" "sigs.k8s.io/kustomize/api/internal/plugins/execplugin" - "sigs.k8s.io/kustomize/api/internal/plugins/fnplugin" "sigs.k8s.io/kustomize/api/internal/plugins/utils" "sigs.k8s.io/kustomize/api/konfig" "sigs.k8s.io/kustomize/api/resid" @@ -168,9 +167,9 @@ func (l *Loader) makeBuiltinPlugin(r resid.Gvk) (resmap.Configurable, error) { } func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error) { - _, err := fnplugin.GetFunctionSpec(res) + _, err := execplugin.GetFunctionSpec(res) if err == nil { - return fnplugin.NewFnPlugin(&l.pc.FnpLoadingOptions), nil + return execplugin.NewFnPlugin(&l.pc.FnpLoadingOptions), nil } return l.loadExecOrGoPlugin(res.OrgId()) }