diff --git a/go.work.sum b/go.work.sum index 55f6b5190..93fe3800d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -5,32 +5,39 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -39,6 +46,7 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY= diff --git a/kyaml/fix/fixsetters/fieldmetav1.go b/kyaml/fix/fixsetters/fieldmetav1.go deleted file mode 100644 index e0e480f43..000000000 --- a/kyaml/fix/fixsetters/fieldmetav1.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package fixsetters - -import ( - "encoding/json" - "fmt" - "strings" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// FieldMeta contains metadata that may be attached to fields as comments -type FieldMetaV1 struct { - Schema spec.Schema - - Extensions XKustomize -} - -type XKustomize struct { - SetBy string `yaml:"setBy,omitempty" json:"setBy,omitempty"` - PartialFieldSetters []PartialFieldSetter `yaml:"partialSetters,omitempty" json:"partialSetters,omitempty"` - FieldSetter *PartialFieldSetter `yaml:"setter,omitempty" json:"setter,omitempty"` -} - -// PartialFieldSetter defines how to set part of a field rather than the full field -// value. e.g. the tag part of an image field -type PartialFieldSetter struct { - // Name is the name of this setter. - Name string `yaml:"name" json:"name"` - - // Value is the current value that has been set. - Value string `yaml:"value" json:"value"` -} - -// UpgradeV1SetterComment reads the FieldMeta from a node and upgrade the -// setters comment to latest -func (fm *FieldMetaV1) UpgradeV1SetterComment(n *yaml.RNode) error { - // check for metadata on head and line comments - comments := []string{n.YNode().LineComment, n.YNode().HeadComment} - for _, c := range comments { - if c == "" { - continue - } - c := strings.TrimLeft(c, "#") - - if err := fm.Schema.UnmarshalJSON([]byte(c)); err != nil { - // note: don't return an error if the comment isn't a fieldmeta struct - return nil - } - - fe := fm.Schema.VendorExtensible.Extensions["x-kustomize"] - if fe == nil { - return nil - } - b, err := json.Marshal(fe) - if err != nil { - return errors.Wrap(err) - } - // delete line comment after parsing info into fieldmeta - n.YNode().HeadComment = "" - n.YNode().LineComment = "" - err = json.Unmarshal(b, &fm.Extensions) - if fm.Extensions.FieldSetter != nil { - n.YNode().LineComment = fmt.Sprintf(`{"%s":"%s"}`, fieldmeta.ShortHandRef(), fm.Extensions.FieldSetter.Name) - } - return err - } - return nil -} diff --git a/kyaml/fix/fixsetters/fixsetters.go b/kyaml/fix/fixsetters/fixsetters.go deleted file mode 100644 index 072fcd850..000000000 --- a/kyaml/fix/fixsetters/fixsetters.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package fixsetters - -import ( - "os" - - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/setters2" - "sigs.k8s.io/kustomize/kyaml/setters2/settersutil" -) - -// SetterFixer fixes setters in the input package -type SetterFixer struct { - // PkgPath is path to the resource package - PkgPath string - - // OpenAPIPath is path to the openAPI file in the package - OpenAPIPath string - - // DryRun only displays the actions without performing - DryRun bool -} - -// SetterFixerV1Result holds the results of V1 setters fix -type SetterFixerV1Result struct { - // NeedFix indicates if the resource in pkgPath are on V1 version of setters - // and need to be fixed - NeedFix bool - - // CreatedSetters are setters created as part of this fix - CreatedSetters []string - - // CreatedSubst are substitutions created as part of this fix - CreatedSubst []string - - // FailedSetters are setters failed to create from current V1 setters - FailedSetters map[string]error - - // FailedSubst are substitutions failed to be created from V1 partial setters - FailedSubst map[string]error -} - -// FixSettersV1 reads the package and upgrades v1 version of setters -// to latest -func (f *SetterFixer) FixV1Setters() (SetterFixerV1Result, error) { - sfr := SetterFixerV1Result{ - FailedSetters: make(map[string]error), - FailedSubst: make(map[string]error), - } - // KrmFile need not exist for dryRun - if !f.DryRun { - _, err := os.Stat(f.OpenAPIPath) - if err != nil { - return sfr, err - } - } - - var err error - // lookup for all setters and partial setters in v1 format - // delete the v1 format comments after lookup - l := UpgradeV1Setters{} - if f.DryRun { - err = applyReadFilter(&l, f.PkgPath) - } else { - err = applyWriteFilter(&l, f.PkgPath) - } - if err != nil { - return sfr, err - } - if len(l.SetterCounts) > 0 { - sfr.NeedFix = true - } else { - return sfr, nil - } - - // for each v1 setter create the equivalent in v2, - for _, setter := range l.SetterCounts { - sd := setters2.SetterDefinition{ - Name: setter.Name, - Value: setter.Value, - Description: setter.Description, - SetBy: setter.SetBy, - Type: setter.Type, - } - var err error - if !f.DryRun { - err = sd.AddToFile(f.OpenAPIPath) - } - - if err != nil { - sfr.FailedSetters[setter.Name] = err - } else { - sfr.CreatedSetters = append(sfr.CreatedSetters, setter.Name) - } - } - - // for each group of partial setters, create equivalent substitution - for _, subst := range l.Substitutions { - sc := settersutil.SubstitutionCreator{ - Name: subst.Name, - FieldValue: subst.FieldVale, - Pattern: subst.Pattern, - ResourcesPath: f.PkgPath, - OpenAPIPath: f.OpenAPIPath, - } - var err error - if !f.DryRun { - err = applyWriteFilter(&sc, f.PkgPath) - } - if err != nil { - sfr.FailedSubst[subst.Name] = err - } else { - sfr.CreatedSubst = append(sfr.CreatedSubst, subst.Name) - } - } - - return sfr, nil -} - -func applyWriteFilter(f kio.Filter, pkgPath string) error { - rw := &kio.LocalPackageReadWriter{ - PackagePath: pkgPath, - } - return kio.Pipeline{ - Inputs: []kio.Reader{rw}, - Filters: []kio.Filter{f}, - Outputs: []kio.Writer{rw}, - }.Execute() -} - -func applyReadFilter(f kio.Filter, pkgPath string) error { - rw := &kio.LocalPackageReader{ - PackagePath: pkgPath, - } - return kio.Pipeline{ - Inputs: []kio.Reader{rw}, - Filters: []kio.Filter{f}, - }.Execute() -} diff --git a/kyaml/fix/fixsetters/fixsetters_test.go b/kyaml/fix/fixsetters/fixsetters_test.go deleted file mode 100644 index e1efa90e3..000000000 --- a/kyaml/fix/fixsetters/fixsetters_test.go +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package fixsetters - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestFixSettersV1(t *testing.T) { - var tests = []struct { - name string - input string - err string - dryRun bool - openAPIFile string - expectedOutput string - expectedOpenAPI string - needFix bool - createdSetters []string - createdSubst []string - failedSetters map[string]error - failedSubst map[string]error - }{ - { - name: "upgrade-delete-partial-setters", - input: ` -apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}} -spec: - profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profile","value":"asm"}}} - cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}} - `, - - openAPIFile: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization`, - - needFix: true, - createdSetters: []string{"cluster", "profile", "project"}, - createdSubst: []string{"project-cluster-54235872"}, - failedSetters: map[string]error{}, - failedSubst: map[string]error{}, - - expectedOutput: `apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - cluster: "someproj/someclus" # {"$openapi":"project-cluster-54235872"} -spec: - profile: asm # {"$openapi":"profile"} - cluster: "someproj/someclus" # {"$openapi":"project-cluster-54235872"} -`, - - expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.cluster: - type: string - x-k8s-cli: - setter: - name: cluster - value: someclus - io.k8s.cli.setters.profile: - type: string - x-k8s-cli: - setter: - name: profile - value: asm - io.k8s.cli.setters.project: - type: string - x-k8s-cli: - setter: - name: project - value: someproj - io.k8s.cli.substitutions.project-cluster-54235872: - x-k8s-cli: - substitution: - name: project-cluster-54235872 - pattern: ${project}/${cluster} - values: - - marker: ${project} - ref: '#/definitions/io.k8s.cli.setters.project' - - marker: ${cluster} - ref: '#/definitions/io.k8s.cli.setters.cluster' -`, - }, - - { - name: "upgrade-delete-partial-setters-dryRun", - dryRun: true, - input: `apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}} -spec: - profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profile","value":"asm"}}} - cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}} -`, - needFix: true, - createdSetters: []string{"cluster", "profile", "project"}, - createdSubst: []string{"project-cluster-54235872"}, - failedSetters: map[string]error{}, - failedSubst: map[string]error{}, - - expectedOutput: `apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}} -spec: - profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profile","value":"asm"}}} - cluster: "someproj/someclus" # {"type":"string","x-kustomize":{"partialSetters":[{"name":"project","value":"someproj"},{"name":"cluster","value":"someclus"}]}} -`, - }, - - { - name: "partial-setters-same-value", - input: ` -apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -spec: - profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profile","value":"asm"}}} - team: asm # {"type":"string","x-kustomize":{"setter":{"name":"team","value":"asm"}}} - profile-team: asm/asm # {"type":"string","x-kustomize":{"partialSetters":[{"name":"profile","value":"asm"},{"name":"team","value":"asm"}]}} - `, - - openAPIFile: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization`, - - needFix: true, - createdSetters: []string{"profile", "team"}, - createdSubst: []string{"profile-team-1851878264"}, - failedSetters: map[string]error{}, - failedSubst: map[string]error{}, - - expectedOutput: `apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -spec: - profile: asm # {"$openapi":"profile"} - team: asm # {"$openapi":"team"} - profile-team: asm/asm # {"$openapi":"profile-team-1851878264"} -`, - - expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.profile: - type: string - x-k8s-cli: - setter: - name: profile - value: asm - io.k8s.cli.setters.team: - type: string - x-k8s-cli: - setter: - name: team - value: asm - io.k8s.cli.substitutions.profile-team-1851878264: - x-k8s-cli: - substitution: - name: profile-team-1851878264 - pattern: ${profile}/${team} - values: - - marker: ${profile} - ref: '#/definitions/io.k8s.cli.setters.profile' - - marker: ${team} - ref: '#/definitions/io.k8s.cli.setters.team' -`, - }, - - { - name: "partial-setters-suffix-subst", - input: ` -apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -spec: - profile: asm-profile # {"type":"string","x-kustomize":{"partialSetters":[{"name":"asm","value":"asm"}]}} - team: asm-team # {"type":"string","x-kustomize":{"partialSetters":[{"name":"asm","value":"asm"}]}} - `, - - openAPIFile: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization`, - - needFix: true, - createdSetters: []string{"asm"}, - createdSubst: []string{"asm-3472570278", "asm-3647054792"}, - failedSetters: map[string]error{}, - failedSubst: map[string]error{}, - - expectedOutput: `apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -spec: - profile: asm-profile # {"$openapi":"asm-3647054792"} - team: asm-team # {"$openapi":"asm-3472570278"} -`, - - expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.asm: - type: string - x-k8s-cli: - setter: - name: asm - value: asm - io.k8s.cli.substitutions.asm-3472570278: - x-k8s-cli: - substitution: - name: asm-3472570278 - pattern: ${asm}-team - values: - - marker: ${asm} - ref: '#/definitions/io.k8s.cli.setters.asm' - io.k8s.cli.substitutions.asm-3647054792: - x-k8s-cli: - substitution: - name: asm-3647054792 - pattern: ${asm}-profile - values: - - marker: ${asm} - ref: '#/definitions/io.k8s.cli.setters.asm' -`, - }, - - { - name: "upgrade-with-both-versions", - - input: ` -apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - clusterName: "project-id/us-east1-d/cluster-name" -spec: - profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profilesetter","value":"asm"}}} - hub: gcr.io/asm-testing # {"$openapi":"hubsetter"} - `, - - openAPIFile: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.hubsetter: - type: string - x-k8s-cli: - setter: - name: hubsetter - value: gcr.io/asm-testing`, - - needFix: true, - createdSetters: []string{"profilesetter"}, - failedSetters: map[string]error{}, - failedSubst: map[string]error{}, - - expectedOutput: `apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - clusterName: "project-id/us-east1-d/cluster-name" -spec: - profile: asm # {"$openapi":"profilesetter"} - hub: gcr.io/asm-testing # {"$openapi":"hubsetter"} -`, - - expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.hubsetter: - type: string - x-k8s-cli: - setter: - name: hubsetter - value: gcr.io/asm-testing - io.k8s.cli.setters.profilesetter: - type: string - x-k8s-cli: - setter: - name: profilesetter - value: asm -`, - }, - - { - name: "setter-already-exists", - - input: ` -apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - clusterName: "project-id/us-east1-d/cluster-name" -spec: - profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profilesetter","value":"asm"}}} - hub: asm # {"$openapi":"profilesetter"} - `, - - openAPIFile: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.profilesetter: - type: string - x-k8s-cli: - setter: - name: profilesetter - value: asm -`, - - needFix: true, - createdSetters: []string{"profilesetter"}, - failedSetters: map[string]error{}, - failedSubst: map[string]error{}, - expectedOutput: `apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - clusterName: "project-id/us-east1-d/cluster-name" -spec: - profile: asm # {"$openapi":"profilesetter"} - hub: asm # {"$openapi":"profilesetter"} -`, - - expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.profilesetter: - type: string - x-k8s-cli: - setter: - name: profilesetter - value: asm -`, - }, - { - name: "do-not-delete-latest setters", - - input: ` -apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - clusterName: "project-id/us-east1-d/cluster-name" -spec: - profile: asm # {"$openapi":"profilesetter"} - hub: gcr.io/asm-testing - `, - failedSetters: map[string]error{}, - failedSubst: map[string]error{}, - - openAPIFile: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.profilesetter: - type: string - x-k8s-cli: - setter: - name: profilesetter - value: asm -`, - - expectedOutput: `apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - clusterName: "project-id/us-east1-d/cluster-name" -spec: - profile: asm # {"$openapi":"profilesetter"} - hub: gcr.io/asm-testing -`, - - expectedOpenAPI: `apiVersion: kustomization.dev/v1alpha1 -kind: Kustomization -openAPI: - definitions: - io.k8s.cli.setters.profilesetter: - type: string - x-k8s-cli: - setter: - name: profilesetter - value: asm -`, - }, - - { - name: "no-openAPI-file-error", - input: ` -apiVersion: install.istio.io/v1alpha2 -kind: IstioControlPlane -metadata: - clusterName: "project-id/us-east1-d/cluster-name" -spec: - profile: asm # {"type":"string","x-kustomize":{"setter":{"name":"profilesetter","value":"asm"}}} - hub: gcr.io/asm-testing - `, - - err: "Krmfile:", - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - openAPIFileName := "Krmfile" - - dir := t.TempDir() - - err := os.WriteFile(filepath.Join(dir, "deploy.yaml"), []byte(test.input), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - - if test.openAPIFile != "" { - err = os.WriteFile(filepath.Join(dir, openAPIFileName), []byte(test.openAPIFile), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - } - sf := SetterFixer{ - PkgPath: dir, - DryRun: test.dryRun, - OpenAPIPath: filepath.Join(dir, "Krmfile"), - } - - sfr, err := sf.FixV1Setters() - if test.err == "" { - if !assert.NoError(t, err) { - t.FailNow() - } - } else { - if !assert.Contains(t, err.Error(), test.err) { - t.FailNow() - } - return - } - - actualOutput, err := os.ReadFile(filepath.Join(dir, "deploy.yaml")) - if !assert.NoError(t, err) { - t.FailNow() - } - assert.Equal(t, test.expectedOutput, string(actualOutput)) - - if test.expectedOpenAPI != "" { - actualOpenAPI, err := os.ReadFile(filepath.Join(dir, openAPIFileName)) - if !assert.NoError(t, err) { - t.FailNow() - } - assert.Equal(t, test.expectedOpenAPI, string(actualOpenAPI)) - } - assert.Equal(t, test.needFix, sfr.NeedFix) - assert.Equal(t, test.createdSetters, sfr.CreatedSetters) - assert.Equal(t, test.createdSubst, sfr.CreatedSubst) - assert.Equal(t, test.failedSubst, sfr.FailedSubst) - assert.Equal(t, test.failedSetters, sfr.FailedSetters) - }) - } -} diff --git a/kyaml/fix/fixsetters/lookupupgrade.go b/kyaml/fix/fixsetters/lookupupgrade.go deleted file mode 100644 index a10038428..000000000 --- a/kyaml/fix/fixsetters/lookupupgrade.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package fixsetters - -import ( - "fmt" - "hash/fnv" - "strings" - - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -var _ yaml.Filter = &upgradeV1Setters{} - -// upgradeV1Setters looks up v1 setters in a Resource and upgrades -// all the setters related comments -type upgradeV1Setters struct { - // Name of the setter to lookup. Optional - Name string - - // Setters is a list of setters that were found - Setters []setter - - // Substitutions is a list of substitutions that were found - Substitutions []substitution -} - -type substitution struct { - Name string - FieldVale string - Pattern string -} - -type setter struct { - PartialFieldSetter - Description string - Type string - SetBy string - SubstName string -} - -func (ls *upgradeV1Setters) Filter(object *yaml.RNode) (*yaml.RNode, error) { - switch object.YNode().Kind { - case yaml.DocumentNode: - // skip the document node - return ls.Filter(yaml.NewRNode(object.YNode().Content[0])) - case yaml.MappingNode: - return object, object.VisitFields(func(node *yaml.MapNode) error { - return node.Value.PipeE(ls) - }) - case yaml.SequenceNode: - return object, object.VisitElements(func(node *yaml.RNode) error { - return node.PipeE(ls) - }) - case yaml.ScalarNode: - return object, ls.lookupAndUpgrade(object) - default: - return object, nil - } -} - -// lookupAndUpgrade finds any setters for a field and upgrades the setters comment -func (ls *upgradeV1Setters) lookupAndUpgrade(field *yaml.RNode) error { - // check if there is a substitution for this field - var fm = FieldMetaV1{} - if err := fm.UpgradeV1SetterComment(field); err != nil { - return err - } - - if fm.Extensions.FieldSetter != nil { - if ls.Name != "" && ls.Name != fm.Extensions.FieldSetter.Name { - // skip this setter, it doesn't match the specified setter - return nil - } - // full setter - ls.Setters = append(ls.Setters, setter{ - PartialFieldSetter: *fm.Extensions.FieldSetter, - Description: fm.Schema.Description, - Type: fm.Schema.Type[0], - SetBy: fm.Extensions.SetBy, - }) - return nil - } - - if len(fm.Extensions.PartialFieldSetters) > 0 { - fieldValue := field.YNode().Value - pattern := fieldValue - - var substName string - // derive substitution pattern from partial setters - for i := range fm.Extensions.PartialFieldSetters { - substName += fm.Extensions.PartialFieldSetters[i].Name + "-" - pattern = strings.Replace(pattern, fm.Extensions.PartialFieldSetters[i].Value, `${`+fm.Extensions.PartialFieldSetters[i].Name+"}", 1) - } - - fvHash, err := FNV32aHash(fieldValue) - if err != nil { - return err - } - - substName += fvHash - ls.Substitutions = append(ls.Substitutions, substitution{ - Name: substName, - FieldVale: fieldValue, - Pattern: pattern, - }) - } - - for i := range fm.Extensions.PartialFieldSetters { - if ls.Name != "" && ls.Name != fm.Extensions.PartialFieldSetters[i].Name { - // skip this setter - continue - } - ls.Setters = append(ls.Setters, setter{ - PartialFieldSetter: fm.Extensions.PartialFieldSetters[i], - Description: fm.Schema.Description, - Type: fm.Schema.Type[0], - SetBy: fm.Extensions.SetBy, - }) - } - return nil -} - -// FNV32aHash generates 32-bit FNV-1a hash for input string -func FNV32aHash(text string) (string, error) { - algorithm := fnv.New32a() - _, err := algorithm.Write([]byte(text)) - if err != nil { - return "", errors.Wrap(err) - } - return fmt.Sprint(algorithm.Sum32()), nil -} diff --git a/kyaml/fix/fixsetters/lookupupgradekio.go b/kyaml/fix/fixsetters/lookupupgradekio.go deleted file mode 100644 index d2503e0ab..000000000 --- a/kyaml/fix/fixsetters/lookupupgradekio.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package fixsetters - -import ( - "sort" - - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -var _ kio.Filter = &UpgradeV1Setters{} - -// UpgradeV1Setters identifies setters for a collection of Resources to upgrade -type UpgradeV1Setters struct { - // Name is the name of the setter to match. Optional. - Name string - - // SetterCounts is populated by Filter and contains the count of fields matching each setter. - SetterCounts []setterCount - - // Substitutions are groups of partial setters - Substitutions []substitution -} - -// setterCount records the identified setters and number of fields matching those setters -type setterCount struct { - // Count is the number of substitutions possible to perform - Count int - - // setter is the substitution found - setter -} - -// Filter implements kio.Filter -func (l *UpgradeV1Setters) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) { - setters := map[string]*setterCount{} - substitutions := map[string]*substitution{} - - for i := range input { - // lookup substitutions for this object - ls := &upgradeV1Setters{Name: l.Name} - if err := input[i].PipeE(ls); err != nil { - return nil, err - } - - // aggregate counts for each setter by name. takes the description and value from - // the first setter for each name encountered. - for j := range ls.Setters { - setter := ls.Setters[j] - curr, found := setters[setter.Name] - if !found { - curr = &setterCount{setter: setter} - setters[setter.Name] = curr - } - curr.Count++ - } - - for j := range ls.Substitutions { - subst := ls.Substitutions[j] - _, found := substitutions[subst.Name] - if !found { - substitutions[subst.Name] = &subst - } - } - } - - // pull out and sort the results by setter name - for _, v := range setters { - l.SetterCounts = append(l.SetterCounts, *v) - } - - for _, subst := range substitutions { - l.Substitutions = append(l.Substitutions, *subst) - } - - sort.Slice(l.Substitutions, func(i, j int) bool { - return l.Substitutions[i].Name < l.Substitutions[j].Name - }) - - sort.Slice(l.SetterCounts, func(i, j int) bool { - return l.SetterCounts[i].Name < l.SetterCounts[j].Name - }) - return input, nil -} diff --git a/kyaml/setters2/add.go b/kyaml/setters2/add.go deleted file mode 100644 index 22bd184e9..000000000 --- a/kyaml/setters2/add.go +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "reflect" - "strings" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// Add creates or updates setter or substitution references from resource fields. -// Requires that at least one of FieldValue and FieldName have been set. -type Add struct { - // FieldValue if set will add the OpenAPI reference to fields if they have this value. - // Optional. If unspecified match all field values. - FieldValue string - - // FieldName if set will add the OpenAPI reference to fields with this name or path - // FieldName may be the full name of the field, full path to the field, or the path suffix. - // e.g. all of the following would match spec.template.spec.containers.image -- - // [image, containers.image, spec.containers.image, template.spec.containers.image, - // spec.template.spec.containers.image] - // Optional. If unspecified match all field names. - FieldName string - - // Ref is the OpenAPI reference to set on the matching fields as a comment. - Ref string - - // ListValues are the value of a list setter. - ListValues []string - - // Type is the type of the setter value - Type string - - // Count is the number of fields the setter applies to - Count int - - SettersSchema *spec.Schema -} - -// Filter implements yaml.Filter -func (a *Add) Filter(object *yaml.RNode) (*yaml.RNode, error) { - if a.FieldName == "" && a.FieldValue == "" { - return nil, errors.Errorf("must specify either fieldName or fieldValue") - } - if a.Ref == "" { - return nil, errors.Errorf("must specify ref") - } - return object, accept(a, object, a.SettersSchema) -} - -func (a *Add) visitSequence(_ *yaml.RNode, _ string, _ *openapi.ResourceSchema) error { - // no-op - return nil -} - -// visitMapping implements visitor -// visitMapping visits the fields in input MappingNode and adds setter/subst ref -// if the path path spec matches with input FiledName -func (a *Add) visitMapping(object *yaml.RNode, p string, _ *openapi.ResourceSchema) error { - return object.VisitFields(func(node *yaml.MapNode) error { - if node.Value.YNode().Kind != yaml.SequenceNode { - return nil - } - - key, err := node.Key.String() - if err != nil { - return err - } - - // derive the list values for the sequence node to write it to openAPI definitions - var values []string - for _, sc := range node.Value.Content() { - values = append(values, sc.Value) - } - - // pathToKey refers to the path address of the key node ex: metadata.annotations - // p is the path till parent node, pathToKey is obtained by appending child key - pathToKey := p + "." + strings.Trim(key, "\n") - if a.FieldName != "" && strings.HasSuffix(pathToKey, a.FieldName) { - // check if there are different values for field path before adding ref to the field - if len(a.ListValues) > 0 && !reflect.DeepEqual(values, a.ListValues) { - return errors.Errorf("setters can only be created for fields with same values, "+ - "encountered different array values for specified field path: %s, %s", values, a.ListValues) - } - a.ListValues = values - a.Count++ - return a.addRef(node.Key) - } - return nil - }) -} - -// visitScalar implements visitor -// visitScalar will set the field metadata on each scalar field whose name + value match -func (a *Add) visitScalar(object *yaml.RNode, p string, _, _ *openapi.ResourceSchema) error { - // check if the field matches - if a.Type == "array" { - return nil - } - if a.FieldName != "" && !strings.HasSuffix(p, a.FieldName) { - return nil - } - if a.FieldValue != "" && a.FieldValue != object.YNode().Value { - return nil - } - a.Count++ - return a.addRef(object) -} - -// addRef adds the setter/subst ref to the object node as a line comment -func (a *Add) addRef(object *yaml.RNode) error { - // read the field metadata - fm := fieldmeta.FieldMeta{SettersSchema: a.SettersSchema} - if err := fm.Read(object); err != nil { - return err - } - - // create the ref on the field metadata - r, err := spec.NewRef(a.Ref) - if err != nil { - return err - } - fm.Schema.Ref = r - - // write the field metadata - if err := fm.Write(object); err != nil { - return err - } - return nil -} - -// SetterDefinition may be used to update a files OpenAPI definitions with a new setter. -type SetterDefinition struct { - // Name is the name of the setter to create or update. - Name string `yaml:"name"` - - // Value is the value of the setter. - Value string `yaml:"value"` - - // ListValues are the value of a list setter. - ListValues []string `yaml:"listValues,omitempty"` - - // SetBy is the person or role that last set the value. - SetBy string `yaml:"setBy,omitempty"` - - // Description is a description of the value. - Description string `yaml:"description,omitempty"` - - // Count is the number of fields set by this setter. - Count int `yaml:"count,omitempty"` - - // Type is the type of the setter value. - Type string `yaml:"type,omitempty"` - - // Schema is the openAPI schema for setter constraints. - Schema string `yaml:"schema,omitempty"` - - // EnumValues is a map of possible setter values to actual field values. - // If EnumValues is specified, then the value set the by user 1) MUST - // be present in the enumValues map as a key, and 2) the map entry value - // MUST be used as the value to set in the configuration (rather than the key) - // Example -- may be used for t-shirt sizing values by allowing cpu to be - // set to small, medium or large, and then mapping these values to cpu values -- 0.5, 2, 8 - EnumValues map[string]string `yaml:"enumValues,omitempty"` - - // Required indicates that the setter must be set by package consumer before - // live apply/preview. This field is added to the setter definition to record - // the package publisher's intent to make the setter required to be set. - Required bool `yaml:"required,omitempty"` - - // IsSet indicates the specified field has been explicitly assigned. - IsSet bool `yaml:"isSet,omitempty"` -} - -func (sd SetterDefinition) AddToFile(path string) error { - return yaml.UpdateFile(sd, path) -} - -func (sd SetterDefinition) Filter(object *yaml.RNode) (*yaml.RNode, error) { - key := fieldmeta.SetterDefinitionPrefix + sd.Name - - definitions, err := object.Pipe(yaml.LookupCreate( - yaml.MappingNode, openapi.SupplementaryOpenAPIFieldName, "definitions")) - if err != nil { - return nil, err - } - - setterDef, err := definitions.Pipe(yaml.LookupCreate(yaml.MappingNode, key)) - if err != nil { - return nil, err - } - - if sd.Schema != "" { - schNode, err := yaml.ConvertJSONToYamlNode(sd.Schema) - if err != nil { - return nil, err - } - - err = definitions.PipeE(yaml.SetField(key, schNode)) - if err != nil { - return nil, err - } - // don't write the schema to the extension - sd.Schema = "" - } - - if sd.Description != "" { - err = setterDef.PipeE(yaml.FieldSetter{Name: "description", StringValue: sd.Description}) - if err != nil { - return nil, err - } - // don't write the description to the extension - sd.Description = "" - } - - if sd.Type != "" { - err = setterDef.PipeE(yaml.FieldSetter{Name: "type", StringValue: sd.Type}) - if err != nil { - return nil, err - } - // don't write the type to the extension - sd.Type = "" - } - - ext, err := setterDef.Pipe(yaml.LookupCreate(yaml.MappingNode, K8sCliExtensionKey)) - if err != nil { - return nil, err - } - - b, err := yaml.Marshal(sd) - if err != nil { - return nil, err - } - y, err := yaml.Parse(string(b)) - if err != nil { - return nil, err - } - - if err := ext.PipeE(yaml.SetField("setter", y)); err != nil { - return nil, err - } - - return object, nil -} - -// SetterDefinition may be used to update a files OpenAPI definitions with a new substitution. -type SubstitutionDefinition struct { - // Name is the name of the substitution to create or update - Name string `yaml:"name"` - - // Pattern is the substitution pattern into which setter values are substituted - Pattern string `yaml:"pattern"` - - // Values are setters which are substituted into pattern to produce a field value - Values []Value `yaml:"values"` -} - -type Value struct { - // Marker is the string marker in pattern that is replace by the referenced setter. - Marker string `yaml:"marker"` - - // Ref is a reference to a setter to pull the replacement value from. - Ref string `yaml:"ref"` -} - -func (sd SubstitutionDefinition) AddToFile(path string) error { - return yaml.UpdateFile(sd, path) -} - -func (sd SubstitutionDefinition) Filter(object *yaml.RNode) (*yaml.RNode, error) { - // create the substitution extension value by marshalling the SubstitutionDefinition itself - b, err := yaml.Marshal(sd) - if err != nil { - return nil, err - } - sub, err := yaml.Parse(string(b)) - if err != nil { - return nil, err - } - - // lookup or create the definition for the substitution - defKey := fieldmeta.SubstitutionDefinitionPrefix + sd.Name - def, err := object.Pipe(yaml.LookupCreate( - yaml.MappingNode, openapi.SupplementaryOpenAPIFieldName, "definitions", defKey, "x-k8s-cli")) - if err != nil { - return nil, err - } - - // set the substitution on the definition - if err := def.PipeE(yaml.SetField("substitution", sub)); err != nil { - return nil, err - } - - return object, nil -} diff --git a/kyaml/setters2/add_test.go b/kyaml/setters2/add_test.go deleted file mode 100644 index 18695d675..000000000 --- a/kyaml/setters2/add_test.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -func TestAdd_Filter(t *testing.T) { - var tests = []struct { - name string - add Add - input string - expected string - err string - }{ - { - name: "add-replicas", - add: Add{ - FieldValue: "3", - Ref: "#/definitions/io.k8s.cli.setters.replicas", - }, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 # {"$openapi":"replicas"} - `, - }, - { - name: "add-replicas-annotations", - add: Add{ - FieldValue: "3", - Ref: "#/definitions/io.k8s.cli.setters.replicas", - }, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - something: 3 -spec: - replicas: 3 - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - something: 3 # {"$openapi":"replicas"} -spec: - replicas: 3 # {"$openapi":"replicas"} - `, - }, - { - name: "add-replicas-name", - add: Add{ - FieldValue: "3", - FieldName: "replicas", - Ref: "#/definitions/io.k8s.cli.setters.replicas", - }, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - something: 3 -spec: - replicas: 3 - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - something: 3 -spec: - replicas: 3 # {"$openapi":"replicas"} - `, - }, - { - name: "add-replicas-2x", - add: Add{ - FieldValue: "3", - FieldName: "replicas", - Ref: "#/definitions/io.k8s.cli.setters.replicas", - }, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - replicas: 3 -spec: - replicas: 3 - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - replicas: 3 # {"$openapi":"replicas"} -spec: - replicas: 3 # {"$openapi":"replicas"} - `, - }, - { - name: "add-replicas-1x", - add: Add{ - FieldValue: "3", - FieldName: "spec.replicas", - Ref: "#/definitions/io.k8s.cli.setters.replicas", - }, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - replicas: 3 -spec: - replicas: 3 - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - replicas: 3 -spec: - replicas: 3 # {"$openapi":"replicas"} - `, - }, - { - name: "add-field-inside-sequence", - add: Add{ - FieldValue: "/usr/share/nginx", - FieldName: "spec.containers.volumeMounts.mountPath", - Ref: "#/definitions/io.k8s.cli.setters.mountPath", - }, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - containers: - - name: nginx - image: nginx - volumeMounts: - - name: nginx - mountPath: /usr/share/nginx - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - containers: - - name: nginx - image: nginx - volumeMounts: - - name: nginx - mountPath: /usr/share/nginx # {"$openapi":"mountPath"} - `, - }, - { - name: "add-replicas-error", - add: Add{ - Ref: "#/definitions/io.k8s.cli.setters.replicas", - }, - err: "must specify either fieldName or fieldValue", - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 - `, - }, - { - name: "ref has . in name of setter", - add: Add{ - FieldValue: "3", - Ref: "#/definitions/io.k8s.cli.setters.foo.bar", - }, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 # {"$openapi":"foo.bar"} - `, - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - // parse the input to be modified - r, err := yaml.Parse(test.input) - if !assert.NoError(t, err) { - t.FailNow() - } - - // invoke add - result, err := test.add.Filter(r) - if test.err != "" { - if !assert.Equal(t, test.err, err.Error()) { - t.FailNow() - } - return - } - - if !assert.NoError(t, err) { - t.FailNow() - } - - // compare the actual and expected output - actual, err := result.String() - if !assert.NoError(t, err) { - t.FailNow() - } - actual = strings.TrimSpace(actual) - expected := strings.TrimSpace(test.expected) - if !assert.Equal(t, expected, actual) { - t.FailNow() - } - }) - } -} - -var resourcefile = `apiVersion: resource.dev/v1alpha1 -kind: resourcefile -metadata: - name: hello-world-set -upstream: - type: git - git: - commit: 5c1c019b59299a4f6c7edd1ff5ff54d720621bbe - directory: /package-examples/helloworld-set - ref: v0.1.0 -packageMetadata: - shortDescription: example package using setters` - -func TestAdd_Filter2(t *testing.T) { - path := filepath.Join(os.TempDir(), "resourcefile") - - // write initial resourcefile to temp path - err := os.WriteFile(path, []byte(resourcefile), 0644) - if !assert.NoError(t, err) { - t.FailNow() - } - - // add a setter definition - sd := SetterDefinition{ - Name: "image", - Value: "1", - } - - err = sd.AddToFile(path) - - if !assert.NoError(t, err) { - t.FailNow() - } - - // update setter definition - sd2 := SetterDefinition{ - Name: "image", - Value: "2", - } - - err = sd2.AddToFile(path) - - if !assert.NoError(t, err) { - t.FailNow() - } - - b, err := os.ReadFile(path) - if err != nil { - t.FailNow() - } - - expected := `apiVersion: resource.dev/v1alpha1 -kind: resourcefile -metadata: - name: hello-world-set -upstream: - type: git - git: - commit: 5c1c019b59299a4f6c7edd1ff5ff54d720621bbe - directory: /package-examples/helloworld-set - ref: v0.1.0 -packageMetadata: - shortDescription: example package using setters -openAPI: - definitions: - io.k8s.cli.setters.image: - x-k8s-cli: - setter: - name: image - value: "2" -` - assert.Equal(t, expected, string(b)) -} - -func TestAddUpdateSubstitution(t *testing.T) { - path := filepath.Join(os.TempDir(), "resourcefile") - - // write initial resourcefile to temp path - err := os.WriteFile(path, []byte(resourcefile), 0644) - if !assert.NoError(t, err) { - t.FailNow() - } - - value1 := Value{ - Marker: "IMAGE_NAME", - Ref: "#/definitions/io.k8s.cli.setters.image-name", - } - - value2 := Value{ - Marker: "IMAGE_TAG", - Ref: "#/definitions/io.k8s.cli.setters.image-tag", - } - - values := []Value{value1, value2} - - // add a setter definition - subd := SubstitutionDefinition{ - Name: "image", - Pattern: "IMAGE_NAME:IMAGE_TAG", - Values: values, - } - - err = subd.AddToFile(path) - - if !assert.NoError(t, err) { - t.FailNow() - } - - // update setter definition - subd2 := SubstitutionDefinition{ - Name: "image", - Pattern: "IMAGE_NAME:IMAGE_TAG2", - } - - err = subd2.AddToFile(path) - - if !assert.NoError(t, err) { - t.FailNow() - } - - b, err := os.ReadFile(path) - if err != nil { - t.FailNow() - } - - expected := `apiVersion: resource.dev/v1alpha1 -kind: resourcefile -metadata: - name: hello-world-set -upstream: - type: git - git: - commit: 5c1c019b59299a4f6c7edd1ff5ff54d720621bbe - directory: /package-examples/helloworld-set - ref: v0.1.0 -packageMetadata: - shortDescription: example package using setters -openAPI: - definitions: - io.k8s.cli.substitutions.image: - x-k8s-cli: - substitution: - name: image - pattern: IMAGE_NAME:IMAGE_TAG2 - values: [] -` - assert.Equal(t, expected, string(b)) -} diff --git a/kyaml/setters2/delete.go b/kyaml/setters2/delete.go deleted file mode 100644 index 5c127a138..000000000 --- a/kyaml/setters2/delete.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "strings" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// Delete delete openAPI definition references from resource fields. -type Delete struct { - // Name is the name of the openAPI definition to delete. - Name string - - // DefinitionPrefix is the prefix of the OpenAPI definition type - DefinitionPrefix string - - SettersSchema *spec.Schema -} - -// Filter implements yaml.Filter -func (d *Delete) Filter(object *yaml.RNode) (*yaml.RNode, error) { - return object, accept(d, object, d.SettersSchema) -} - -func (d *Delete) visitSequence(_ *yaml.RNode, _ string, _ *openapi.ResourceSchema) error { - // no-op - return nil -} - -func (d *Delete) visitMapping(object *yaml.RNode, _ string, _ *openapi.ResourceSchema) error { - fieldRNodes, err := object.FieldRNodes() - if err != nil { - return err - } - - // for each of the field node key visit it as scalar to delete the array setter comment - for _, fieldRNode := range fieldRNodes { - err := d.visitScalar(fieldRNode, "", nil, nil) - if err != nil { - return err - } - } - return nil -} - -// visitScalar implements visitor -// visitScalar will remove the reference on each scalar field whose name matches. -func (d *Delete) visitScalar(object *yaml.RNode, _ string, _, _ *openapi.ResourceSchema) error { - // read the field metadata - fm := fieldmeta.FieldMeta{SettersSchema: d.SettersSchema} - if err := fm.Read(object); err != nil { - return err - } - - // Delete the reference iff the ref string matches with DefinitionPrefix - if strings.HasSuffix(fm.Schema.Ref.String(), d.DefinitionPrefix+d.Name) { - // remove the ref on the metadata - fm.Schema.Ref = spec.Ref{} - - // write the field metadata - if err := fm.Write(object); err != nil { - return err - } - } - - return nil -} - -// DeleterDefinition may be used to update a files OpenAPI definitions with a new setter. -type DeleterDefinition struct { - // Name is the name of the openAPI definition to delete. - Name string `yaml:"name"` - - // DefinitionPrefix is the prefix of the OpenAPI definition type - DefinitionPrefix string `yaml:"definitionPrefix"` -} - -func (dd DeleterDefinition) DeleteFromFile(path string) error { - return yaml.UpdateFile(dd, path) -} - -// SubstReferringDefinition check if the definition used in substitution and return the substitution name if true -func SubstReferringDefinition(definitions *yaml.RNode, key string) string { - fieldNames, err := definitions.Fields() - if err != nil { - return "" - } - for _, fieldName := range fieldNames { - // the definition key -- contains the substitution name - subkey := definitions.Field(fieldName).Key.YNode().Value - if strings.HasPrefix(subkey, fieldmeta.SubstitutionDefinitionPrefix) { - substNode, err := definitions.Field(fieldName).Value.Pipe(yaml.Lookup(K8sCliExtensionKey, "substitution")) - if err != nil { - continue - } - - b, err := substNode.MarshalJSON() - if err != nil { - continue - } - - subst := SubstitutionDefinition{} - if err := yaml.Unmarshal(b, &subst); err != nil { - continue - } - // Check the ref in value to see if it contains the setter key - for _, v := range subst.Values { - if strings.HasSuffix(v.Ref, key) { - return subst.Name - } - } - } - } - - return "" -} - -func (dd DeleterDefinition) Filter(object *yaml.RNode) (*yaml.RNode, error) { - key := dd.DefinitionPrefix + dd.Name - var defType string - - switch dd.DefinitionPrefix { - case fieldmeta.SubstitutionDefinitionPrefix: - defType = "substitution" - case fieldmeta.SetterDefinitionPrefix: - defType = "setter" - default: - return nil, errors.Errorf("the input delete definitionPrefix does't match any of openAPI definitions, "+ - "allowed values [%s, %s]", fieldmeta.SetterDefinitionPrefix, fieldmeta.SubstitutionDefinitionPrefix) - } - - definitions, err := object.Pipe(yaml.Lookup(openapi.SupplementaryOpenAPIFieldName, "definitions")) - if err != nil { - return nil, err - } - // return error if the setter to be deleted doesn't exist - if definitions == nil || definitions.Field(key) == nil { - return nil, errors.Errorf("%s %q does not exist", defType, dd.Name) - } - - subst := SubstReferringDefinition(definitions, key) - - if subst != "" { - return nil, errors.Errorf("%s %q is used in substitution %q, please delete the parent substitution first", defType, dd.Name, subst) - } - - _, err = definitions.Pipe(yaml.FieldClearer{Name: key}) - if err != nil { - return nil, err - } - // remove definitions if it's empty - _, err = object.Pipe(yaml.Lookup(openapi.SupplementaryOpenAPIFieldName), yaml.FieldClearer{Name: "definitions", IfEmpty: true}) - if err != nil { - return nil, err - } - - // remove openApi if it's empty - _, err = object.Pipe(yaml.FieldClearer{Name: openapi.SupplementaryOpenAPIFieldName, IfEmpty: true}) - if err != nil { - return nil, err - } - - return object, nil -} diff --git a/kyaml/setters2/delete_test.go b/kyaml/setters2/delete_test.go deleted file mode 100644 index 03850f1aa..000000000 --- a/kyaml/setters2/delete_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" -) - -var resourcefile2 = `apiVersion: resource.dev/v1alpha1 -kind: resourcefile -metadata: - name: hello-world-set -upstream: - type: git - git: - commit: 5c1c019b59299a4f6c7edd1ff5ff54d720621bbe - directory: /package-examples/helloworld-set - ref: v0.1.0 -packageMetadata: - shortDescription: example package using setters -openAPI: - definitions: - io.k8s.cli.setters.image: - x-k8s-cli: - setter: - name: image - value: "2" - io.k8s.cli.setters.tag: - x-k8s-cli: - setter: - name: tag - value: "sometag" -` - -func TestDelete_Filter2(t *testing.T) { - path := filepath.Join(os.TempDir(), "resourcefile2") - - // write initial resourcefile to temp path - err := os.WriteFile(path, []byte(resourcefile2), 0644) - if !assert.NoError(t, err) { - t.FailNow() - } - - // add a deleter definition - dd := DeleterDefinition{ - Name: "image", - DefinitionPrefix: fieldmeta.SetterDefinitionPrefix, - } - - err = dd.DeleteFromFile(path) - if !assert.NoError(t, err) { - t.FailNow() - } - - b, err := os.ReadFile(path) - if err != nil { - t.FailNow() - } - - expected := `apiVersion: resource.dev/v1alpha1 -kind: resourcefile -metadata: - name: hello-world-set -upstream: - type: git - git: - commit: 5c1c019b59299a4f6c7edd1ff5ff54d720621bbe - directory: /package-examples/helloworld-set - ref: v0.1.0 -packageMetadata: - shortDescription: example package using setters -openAPI: - definitions: - io.k8s.cli.setters.tag: - x-k8s-cli: - setter: - name: tag - value: "sometag" -` - assert.Equal(t, expected, string(b)) -} diff --git a/kyaml/setters2/doc.go b/kyaml/setters2/doc.go deleted file mode 100644 index 90ae3ed90..000000000 --- a/kyaml/setters2/doc.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 -// -// Package setters2 contains libraries for setting resource field values from OpenAPI setter -// extensions. -// -// Setters -// -// Setters are used to programmatically set configuration field values -- e.g. through a cli or ui. -// -// Setters are defined through OpenAPI definitions using the x-k8s-cli extension. -// Note: additional OpenAPI definitions may be registered through openapi.AddSchema([]byte) -// -// Example OpenAPI schema containing a setter: -// -// { -// "definitions": { -// "io.k8s.cli.setters.replicas": { -// "x-k8s-cli": { -// "setter": { -// "name": "replicas", -// "value": "4" -// } -// } -// } -// } -// } -// -// Setter fields: -// -// x-k8s-cli.setter.name: name of the setter -// x-k8s-cli.setter.value: value of the setter that should be applied to fields -// -// The setter definition key must be of the form "io.k8s.cli.setters.NAME", where NAME matches the -// value of "x-k8s-cli.setter.name". -// -// When Set.Filter is called, the named setter will have its value applied to all resource -// fields referencing it. -// -// Fields may reference setters through a yaml comment containing the serialized JSON OpenAPI. -// -// Example Deployment resource with a "spec.replicas" field set by the "replicas" setter: -// -// apiVersion: apps/v1 -// kind: Deployment -// metadata: -// name: nginx-deployment -// spec: -// replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} -// -// If the OpenAPI io.k8s.cli.setters.replicas x-k8s-cli.setter.value was changed from "4" to "5", -// then calling Set{Name: "replicas"}.Filter(deployment) would update the Deployment spec.replicas -// value from 4 to 5. -// -// Updated OpenAPI: -// -// { -// "definitions": { -// "io.k8s.cli.setters.replicas": { -// "x-k8s-cli": { -// "setter": { -// "name": "replicas", -// "value": "5" -// } -// } -// } -// } -// } -// -// Updated Deployment Configuration: -// -// apiVersion: apps/v1 -// kind: Deployment -// metadata: -// name: nginx-deployment -// spec: -// replicas: 5 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} -// -// Substitutions -// -// Substitutions are used to programmatically set configuration field values using multiple -// setters which are substituted into a pattern string. -// -// Substitutions may be used when a field value does not cleanly map to a single setter, but -// instead matches some string pattern where setters may be substituted in. -// -// Fields may reference substitutions the same way they do setters, however substitutions -// reference setters from which they are derived. -// -// Example OpenAPI schema containing a substitution derived from 2 setters: -// -// { -// "definitions": { -// "io.k8s.cli.setters.image-name": { -// "x-k8s-cli": { -// "setter": { -// "name": "image-name", -// "value": "nginx" -// } -// } -// }, -// "io.k8s.cli.setters.image-tag": { -// "x-k8s-cli": { -// "setter": { -// "name": "image-tag", -// "value": "1.8.1" -// } -// } -// }, -// "io.k8s.cli.substitutions.image-name-tag": { -// "x-k8s-cli": { -// "substitution": { -// "name": "image-name-tag", -// "pattern": "IMAGE_NAME:IMAGE_TAG", -// "values": [ -// {"marker": "IMAGE_NAME", "ref": "#/definitions/io.k8s.cli.setters.image-name"} -// {"marker": "IMAGE_TAG", "ref": "#/definitions/io.k8s.cli.setters.image-tag"} -// ] -// } -// } -// } -// } -// } -// -// Substitution Fields. -// -// x-k8s-cli.substitution.name: name of the substitution -// x-k8s-cli.substitution.pattern: string pattern to substitute markers into -// x-k8s-cli.substitution.values.marker: the marker substring within pattern to replace -// x-k8s-cli.substitution.values.ref: the setter ref containing the value to replace the marker with -// -// The substitution is composed of a "pattern" containing markers, and a list of setter "values" -// which are substituted into the markers. -// -// Example Deployment with substitution: -// -// apiVersion: apps/v1 -// kind: Deployment -// metadata: -// name: nginx-deployment -// spec: -// template: -// spec: -// containers: -// - name: nginx -// image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image-name-tag"} -// -// spec.template.spec.containers[name=nginx].image is set by the "image" substitution any time -// either "image-name" or "image-tag" is set. Whenever any setter referenced by a substitution -// is set, the substitution will be recalculated by substituting its values into its pattern. -// -// -// If the OpenAPI io.k8s.cli.setters.image-name x-k8s-cli.setter.value was changed from "1.8.1" -// to "1.8.2", then calling either Set{Name: "image-name"}.Filter(deployment) or -// Set{Name: "image-tag"}.Filter(deployment) would update the Deployment field -// spec.template.spec.container[name=nginx].image from "nginx:1.8.1" to "nginx:1.8.2". -// -// Adding Field References -// -// References to setters and substitutions may be added to fields using the Add Filter. -// Add will write a JSON OpenAPI string as a comment to any fields matching the specified -// FieldName add FieldValue. -package setters2 diff --git a/kyaml/setters2/example_test.go b/kyaml/setters2/example_test.go deleted file mode 100644 index 9fd15cffa..000000000 --- a/kyaml/setters2/example_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "fmt" - - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// ExampleAdd demonstrates adding a setter reference to fields. -func ExampleAdd_fieldName() { - deployment := ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - something: 3 -spec: - replicas: 3 -` - - object := yaml.MustParse(deployment) // parse the configuration - err := object.PipeE(&Add{ - Ref: "#/definitions/io.k8s.cli.setters.replicas", - FieldName: "replicas", - }) - if err != nil { - panic(err) - } - - // Print the object with the update value - fmt.Println(object.MustString()) - - // Output: - // apiVersion: apps/v1 - // kind: Deployment - // metadata: - // name: nginx-deployment - // annotations: - // something: 3 - // spec: - // replicas: 3 # {"$openapi":"replicas"} -} - -// ExampleAdd demonstrates adding a setter reference to fields. -func ExampleAdd_fieldValue() { - deployment := ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - something: 3 -spec: - replicas: 3 -` - - object := yaml.MustParse(deployment) // parse the configuration - err := object.PipeE(&Add{ - Ref: "#/definitions/io.k8s.cli.setters.replicas", - FieldValue: "3", - }) - if err != nil { - panic(err) - } - - // Print the object with the update value - fmt.Println(object.MustString()) - - // Output: - // apiVersion: apps/v1 - // kind: Deployment - // metadata: - // name: nginx-deployment - // annotations: - // something: 3 # {"$openapi":"replicas"} - // spec: - // replicas: 3 # {"$openapi":"replicas"} -} diff --git a/kyaml/setters2/list.go b/kyaml/setters2/list.go deleted file mode 100644 index 17a39ad7f..000000000 --- a/kyaml/setters2/list.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "sort" - "strings" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// List lists the setters specified in the OpenAPI -// excludes the subpackages which contain file with -// name OpenAPIFileName in them -type List struct { - Name string - - OpenAPIFileName string - - Setters []SetterDefinition - - Substitutions []SubstitutionDefinition - - SettersSchema *spec.Schema -} - -// ListSetters initializes l.Setters with the setters from the OpenAPI definitions in the file -func (l *List) ListSetters(openAPIPath, resourcePath string) error { - y, err := yaml.ReadFile(openAPIPath) - if err != nil { - return err - } - return l.listSetters(y, resourcePath) -} - -// ListSubst initializes l.Substitutions with the substitutions from the OpenAPI definitions in the file -func (l *List) ListSubst(openAPIPath string) error { - y, err := yaml.ReadFile(openAPIPath) - if err != nil { - return err - } - return l.listSubst(y) -} - -func (l *List) listSetters(object *yaml.RNode, resourcePath string) error { - // read the OpenAPI definitions - def, err := object.Pipe(yaml.LookupCreate(yaml.MappingNode, "openAPI", "definitions")) - if err != nil { - return err - } - if yaml.IsMissingOrNull(def) { - return nil - } - - // iterate over definitions -- find those that are setters - err = def.VisitFields(func(node *yaml.MapNode) error { - setter := SetterDefinition{} - - // the definition key -- contains the setter name - key := node.Key.YNode().Value - - if !strings.HasPrefix(key, fieldmeta.SetterDefinitionPrefix) { - // not a setter -- doesn't have the right prefix - return nil - } - - setterNode, err := node.Value.Pipe(yaml.Lookup(K8sCliExtensionKey, "setter")) - if err != nil { - return err - } - if yaml.IsMissingOrNull(setterNode) { - // has the setter prefix, but missing the setter extension - return errors.Errorf("missing x-k8s-cli.setter for %s", key) - } - - // unmarshal the yaml for the setter extension into the definition struct - b, err := setterNode.String() - if err != nil { - return err - } - if err := yaml.Unmarshal([]byte(b), &setter); err != nil { - return err - } - - if l.Name != "" && l.Name != setter.Name { - // not the setter that was requested by list - return nil - } - - // the description is not part of the extension, and should be pulled out - // separately from the extension values. - description := node.Value.Field("description") - if description != nil { - setter.Description = description.Value.YNode().Value - } - - // count the number of fields set by this setter - setter.Count, err = l.count(resourcePath, setter.Name) - if err != nil { - return err - } - - l.Setters = append(l.Setters, setter) - return nil - }) - if err != nil { - return err - } - - // sort the setters by their name - sort.Slice(l.Setters, func(i, j int) bool { - return l.Setters[i].Name < l.Setters[j].Name - }) - - return nil -} - -func (l *List) listSubst(object *yaml.RNode) error { - // read the OpenAPI definitions - def, err := object.Pipe(yaml.LookupCreate(yaml.MappingNode, "openAPI", "definitions")) - if err != nil { - return err - } - if yaml.IsMissingOrNull(def) { - return nil - } - - // iterate over definitions -- find those that are substitutions - err = def.VisitFields(func(node *yaml.MapNode) error { - subst := SubstitutionDefinition{} - - // the definition key -- contains the substitution name - key := node.Key.YNode().Value - - if !strings.HasPrefix(key, fieldmeta.SubstitutionDefinitionPrefix) { - // not a substitution -- doesn't have the right prefix - return nil - } - - substNode, err := node.Value.Pipe(yaml.Lookup(K8sCliExtensionKey, "substitution")) - if err != nil { - return err - } - if yaml.IsMissingOrNull(substNode) { - // has the substitution prefix, but missing the setter extension - return errors.Errorf("missing x-k8s-cli.substitution for %s", key) - } - - // unmarshal the yaml for the substitution extension into the definition struct - b, err := substNode.String() - if err != nil { - return err - } - if err := yaml.Unmarshal([]byte(b), &subst); err != nil { - return err - } - - if l.Name != "" && l.Name != subst.Name { - // not the substitution that was requested by list - return nil - } - - l.Substitutions = append(l.Substitutions, subst) - return nil - }) - if err != nil { - return err - } - - // sort the substitutions by their name - sort.Slice(l.Substitutions, func(i, j int) bool { - return l.Substitutions[i].Name < l.Substitutions[j].Name - }) - - return nil -} - -// count returns the number of fields set by the setter with name -// this excludes all the subpackages with openAPI file in them -// set filter is leveraged for this but the resources are not written -// back to files as only LocalPackageReader is invoked and not writer -func (l *List) count(path, name string) (int, error) { - s := &Set{Name: name, SettersSchema: l.SettersSchema} - err := kio.Pipeline{ - Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: path, PackageFileName: l.OpenAPIFileName}}, - Filters: []kio.Filter{kio.FilterAll(s)}, - }.Execute() - - return s.Count, err -} diff --git a/kyaml/setters2/list_test.go b/kyaml/setters2/list_test.go deleted file mode 100644 index 98993a943..000000000 --- a/kyaml/setters2/list_test.go +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" - "sigs.k8s.io/kustomize/kyaml/openapi" -) - -func TestList(t *testing.T) { - var tests = []struct { - name string - setter string - openapi string - input string - expected []SetterDefinition - }{ - { - name: "list-replicas", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me - description: "hello world" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, - expected: []SetterDefinition{ - {Name: "replicas", Value: "3", SetBy: "me", Description: "hello world", Count: 1}, - }, - }, - { - name: "list-multiple", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - description: "hello world 1" - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me1 - io.k8s.cli.setters.image: - description: "hello world 2" - x-k8s-cli: - setter: - name: image - value: "nginx" - setBy: me2 - io.k8s.cli.setters.tag: - description: "hello world 3" - x-k8s-cli: - setter: - name: tag - value: "1.7.9" - setBy: me3 - io.k8s.cli.substitutions.image: - x-k8s-cli: - substitution: - name: image - pattern: IMAGE:TAG - values: - - marker: IMAGE - ref: '#/definitions/io.k8s.cli.setters.image' - - marker: TAG - ref: '#/definitions/io.k8s.cli.setters.tag' - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - template: - spec: - containers: - - name: nginx - image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - - name: nginx2 - image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"} - `, - expected: []SetterDefinition{ - {Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 2}, - {Name: "replicas", Value: "3", SetBy: "me1", Description: "hello world 1", Count: 1}, - {Name: "tag", Value: "1.7.9", SetBy: "me3", Description: "hello world 3", Count: 1}, - }, - }, - { - name: "list-multiple-resources", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - description: "hello world 1" - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me1 - io.k8s.cli.setters.image: - description: "hello world 2" - x-k8s-cli: - setter: - name: image - value: "nginx" - setBy: me2 - io.k8s.cli.setters.tag: - description: "hello world 3" - x-k8s-cli: - setter: - name: tag - value: "1.7.9" - setBy: me3 - io.k8s.cli.substitutions.image: - x-k8s-cli: - substitution: - name: image - pattern: IMAGE:TAG - values: - - marker: IMAGE - ref: '#/definitions/io.k8s.cli.setters.image' - - marker: TAG - ref: '#/definitions/io.k8s.cli.setters.tag' - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment-1 -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - template: - spec: - containers: - - name: nginx - image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - - name: nginx2 - image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"} ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment-2 -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - template: - spec: - containers: - - name: nginx - image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - - name: nginx2 - image: nginx -`, - expected: []SetterDefinition{ - {Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 3}, - {Name: "replicas", Value: "3", SetBy: "me1", Description: "hello world 1", Count: 2}, - {Name: "tag", Value: "1.7.9", SetBy: "me3", Description: "hello world 3", Count: 2}, - }, - }, - { - name: "list-name", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - description: "hello world 1" - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me1 - io.k8s.cli.setters.image: - description: "hello world 2" - x-k8s-cli: - setter: - name: image - value: "nginx" - setBy: me2 - io.k8s.cli.setters.tag: - description: "hello world 3" - x-k8s-cli: - setter: - name: tag - value: "1.7.9" - setBy: me3 - io.k8s.cli.substitutions.image: - x-k8s-cli: - substitution: - name: image - pattern: IMAGE:TAG - values: - - marker: IMAGE - ref: '#/definitions/io.k8s.cli.setters.image' - - marker: TAG - ref: '#/definitions/io.k8s.cli.setters.tag' - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment-1 -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - template: - spec: - containers: - - name: nginx - image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - - name: nginx2 - image: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.image"} ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment-2 -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - template: - spec: - containers: - - name: nginx - image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - - name: nginx2 - image: nginx -`, - setter: "image", - expected: []SetterDefinition{ - {Name: "image", Value: "nginx", SetBy: "me2", Description: "hello world 2", Count: 3}, - }, - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - // reset the openAPI afterward - defer openapi.ResetOpenAPI() - - f, err := os.CreateTemp("", "k8s-cli-") - if !assert.NoError(t, err) { - t.FailNow() - } - defer os.Remove(f.Name()) - err = os.WriteFile(f.Name(), []byte(test.openapi), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - - r, err := os.CreateTemp("", "k8s-cli-*.yaml") - if !assert.NoError(t, err) { - t.FailNow() - } - defer os.Remove(r.Name()) - err = os.WriteFile(r.Name(), []byte(test.input), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - - // invoke the setter - instance := &List{Name: test.setter, SettersSchema: SettersSchema(t, test.openapi)} - err = instance.ListSetters(f.Name(), r.Name()) - if !assert.NoError(t, err) { - t.FailNow() - } - - if !assert.Equal(t, test.expected, instance.Setters) { - t.FailNow() - } - }) - } -} diff --git a/kyaml/setters2/set.go b/kyaml/setters2/set.go deleted file mode 100644 index 4b078d1c7..000000000 --- a/kyaml/setters2/set.go +++ /dev/null @@ -1,540 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "fmt" - "strings" - "text/template" - - "k8s.io/kube-openapi/pkg/validation/spec" - "k8s.io/kube-openapi/pkg/validation/strfmt" - "k8s.io/kube-openapi/pkg/validation/validate" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - goyaml "sigs.k8s.io/kustomize/kyaml/internal/forked/github.com/go-yaml/yaml" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/kio/kioutil" - "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/sets" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// Set sets resource field values from an OpenAPI setter -type Set struct { - // Name is the name of the setter to set on the object. i.e. matches the x-k8s-cli.setter.name - // of the setter that should have its value applied to fields which reference it. - Name string - - // Count is the number of fields that were updated by calling Filter - Count int - - // SetAll if set to true will set all setters regardless of name - SetAll bool - - SettersSchema *spec.Schema -} - -// Filter implements Set as a yaml.Filter -func (s *Set) Filter(object *yaml.RNode) (*yaml.RNode, error) { - return object, accept(s, object, s.SettersSchema) -} - -// isMatch returns true if the setter with name should have the field -// value set -func (s *Set) isMatch(name string) bool { - return s.SetAll || s.Name == name -} - -func (s *Set) visitMapping(_ *yaml.RNode, p string, _ *openapi.ResourceSchema) error { - return nil -} - -// visitSequence will perform setters for sequences -func (s *Set) visitSequence(object *yaml.RNode, p string, schema *openapi.ResourceSchema) error { - ext, err := getExtFromComment(schema) - if err != nil { - return err - } - if ext == nil || ext.Setter == nil || !s.isMatch(ext.Setter.Name) || - len(ext.Setter.ListValues) == 0 { - // setter was not invoked for this sequence - return nil - } - s.Count++ - - // set the values on the sequences - var elements []*yaml.Node - if len(ext.Setter.ListValues) > 0 { - if err := validateAgainstSchema(ext, schema.Schema); err != nil { - return err - } - } - for i := range ext.Setter.ListValues { - v := ext.Setter.ListValues[i] - n := yaml.NewScalarRNode(v).YNode() - n.Style = yaml.DoubleQuotedStyle - elements = append(elements, n) - } - object.YNode().Content = elements - object.YNode().Style = yaml.FoldedStyle - return nil -} - -// visitScalar -func (s *Set) visitScalar(object *yaml.RNode, p string, oa, settersSchema *openapi.ResourceSchema) error { - // get the openAPI for this field describing how to apply the setter - ext, err := getExtFromComment(settersSchema) - if err != nil { - return err - } - if ext == nil { - return nil - } - - var k8sSchema *spec.Schema - if oa != nil { - k8sSchema = oa.Schema - } - - // perform a direct set of the field if it matches - ok, err := s.set(object, ext, k8sSchema, settersSchema.Schema) - if err != nil { - return err - } - if ok { - s.Count++ - return nil - } - - // perform a substitution of the field if it matches - sub, err := s.substitute(object, ext) - if err != nil { - return err - } - if sub { - s.Count++ - } - return nil -} - -// substitute updates the value of field from ext if ext contains a substitution that -// depends on a setter whose name matches s.Name. -func (s *Set) substitute(field *yaml.RNode, ext *CliExtension) (bool, error) { - // check partial setters to see if they contain the setter as part of a - // substitution - if ext.Substitution == nil { - return false, nil - } - - // track the visited nodes to detect cycles in nested substitutions - visited := sets.String{} - - // nameMatch indicates if the input substitution depends on the specified setter, - // the substitution in ext is parsed recursively and if the setter in Set is hit while - // parsing, it indicates the match - nameMatch := false - - res, err := s.substituteUtil(ext, visited, &nameMatch) - if err != nil { - return false, err - } - - if !nameMatch { - // doesn't depend on the setter, don't modify its value - return false, nil - } - - field.YNode().Value = res - - // substitutions are always strings - field.YNode().Tag = yaml.NodeTagString - - return true, nil -} - -// substituteUtil recursively parses nested substitutions in ext and sets the setter value -// returns error if cyclic substitution is detected or any other unexpected errors -func (s *Set) substituteUtil(ext *CliExtension, visited sets.String, nameMatch *bool) (string, error) { - // check if the substitution has already been visited and throw error as cycles - // are not allowed in nested substitutions - if visited.Has(ext.Substitution.Name) { - return "", errors.Errorf( - "cyclic substitution detected with name " + ext.Substitution.Name) - } - - visited.Insert(ext.Substitution.Name) - pattern := ext.Substitution.Pattern - - // substitute each setter into the pattern to get the new value - // if substitution references to another substitution, recursively - // process the nested substitutions to replace the pattern with setter values - for _, v := range ext.Substitution.Values { - if v.Ref == "" { - return "", errors.Errorf( - "missing reference on substitution " + ext.Substitution.Name) - } - ref, err := spec.NewRef(v.Ref) - if err != nil { - return "", errors.Wrap(err) - } - def, err := openapi.Resolve(&ref, s.SettersSchema) // resolve the def to its openAPI def - if err != nil { - return "", errors.Wrap(err) - } - defExt, err := GetExtFromSchema(def) // parse the extension out of the openAPI - if err != nil { - return "", errors.Wrap(err) - } - - if defExt.Substitution != nil { - // parse recursively if it reference is substitution - substVal, err := s.substituteUtil(defExt, visited, nameMatch) - if err != nil { - return "", err - } - pattern = strings.ReplaceAll(pattern, v.Marker, substVal) - continue - } - - // if code reaches this point, this is a setter, so validate the setter schema - if err := validateAgainstSchema(defExt, def); err != nil { - return "", err - } - - if s.isMatch(defExt.Setter.Name) { - // the substitution depends on the specified setter - *nameMatch = true - } - - if val, found := defExt.Setter.EnumValues[defExt.Setter.Value]; found { - // the setter has an enum-map. we should replace the marker with the - // enum value looked up from the map rather than the enum key - pattern = strings.ReplaceAll(pattern, v.Marker, val) - } else { - pattern = strings.ReplaceAll(pattern, v.Marker, defExt.Setter.Value) - } - } - - return pattern, nil -} - -// set applies the value from ext to field if its name matches s.Name -func (s *Set) set(field *yaml.RNode, ext *CliExtension, k8sSch, sch *spec.Schema) (bool, error) { - // check full setter - if ext.Setter == nil || !s.isMatch(ext.Setter.Name) { - return false, nil - } - - if err := validateAgainstSchema(ext, sch); err != nil { - return false, err - } - - if val, found := ext.Setter.EnumValues[ext.Setter.Value]; found { - // the setter has an enum-map. we should replace the marker with the - // enum value looked up from the map rather than the enum key - field.YNode().Value = val - return true, nil - } - - // this has a full setter, set its value - field.YNode().Value = ext.Setter.Value - - // format the node so it is quoted if it is a string. If there is - // type information on the setter schema, we use it. Otherwise we - // fall back to the field schema if it exists. - if len(sch.Type) > 0 { - yaml.FormatNonStringStyle(field.YNode(), *sch) - } else if k8sSch != nil { - yaml.FormatNonStringStyle(field.YNode(), *k8sSch) - } - return true, nil -} - -// validateAgainstSchema validates the input setter value against user provided -// openAI schema -func validateAgainstSchema(ext *CliExtension, sch *spec.Schema) error { - fixSchemaTypes(sch) - sc := spec.Schema{} - sc.Properties = map[string]spec.Schema{} - sc.Properties[ext.Setter.Name] = *sch - - var inputYAML string - if len(ext.Setter.ListValues) > 0 { - // tmplText contains the template we will use to produce a yaml - // document that we can use for validation. - var tmplText string - if sch.Items != nil && sch.Items.Schema != nil && - shouldQuoteSetterValue(ext.Setter.ListValues, sch.Items.Schema.Type) { - // If string is one of the legal types for the value, we - // output it with quotes in the yaml document to make sure it - // is later parsed as a string. - tmplText = `{{.key}}:{{block "list" .values}}{{"\n"}}{{range .}}{{printf "- %q\n" .}}{{end}}{{end}}` - } else { - // If string is not specifically set as the type, we just - // let the yaml unmarshaller detect the correct type. Thus, we - // do not add quotes around the value. - tmplText = `{{.key}}:{{block "list" .values}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}` - } - - tmpl, err := template.New("validator").Parse(tmplText) - if err != nil { - return err - } - var builder strings.Builder - err = tmpl.Execute(&builder, map[string]interface{}{ - "key": ext.Setter.Name, - "values": ext.Setter.ListValues, - }) - if err != nil { - return err - } - inputYAML = builder.String() - } else { - var format string - // Only add quotes around the value is string is one of the - // types in the schema. - if shouldQuoteSetterValue([]string{ext.Setter.Value}, sch.Type) { - format = "%s: \"%s\"" - } else { - format = "%s: %s" - } - inputYAML = fmt.Sprintf(format, ext.Setter.Name, ext.Setter.Value) - } - - input := map[string]interface{}{} - err := goyaml.Unmarshal([]byte(inputYAML), &input) - if err != nil { - return err - } - err = validate.AgainstSchema(&sc, input, strfmt.Default) - if err != nil { - return errors.Errorf("The input value doesn't validate against provided OpenAPI schema: %v\n", err.Error()) - } - return nil -} - -// shouldQuoteSetterValue returns true if string is one of the types in the -// schema, or if the value ends in a ':' (the yaml parser gets confused by -// the ':' at the end unless the value is quoted) -func shouldQuoteSetterValue(a []string, schType spec.StringOrArray) bool { - if schType.Contains("string") { - return true - } - - for _, s := range a { - if strings.HasSuffix(s, ":") { - return true - } - } - return false -} - -// fixSchemaTypes traverses the schema and checks for some common -// errors for the type field. This currently involves users using -// 'int' instead of 'integer' and 'bool' instead of 'boolean'. Early versions -// of setters didn't validate this, so there are users that have invalid -// types in their packages. -func fixSchemaTypes(sc *spec.Schema) { - for i := range sc.Type { - currentType := sc.Type[i] - if currentType == "int" { - sc.Type[i] = "integer" - } - if currentType == "bool" { - sc.Type[i] = "boolean" - } - } - - if items := sc.Items; items != nil { - if items.Schema != nil { - fixSchemaTypes(items.Schema) - } - for i := range items.Schemas { - schema := items.Schemas[i] - fixSchemaTypes(&schema) - } - } -} - -// SetOpenAPI updates a setter value -type SetOpenAPI struct { - // Name is the name of the setter to add - Name string `yaml:"name"` - // Value is the current value of the setter - Value string `yaml:"value"` - - // ListValue is the current value for a list of items - ListValues []string `yaml:"listValue"` - - Description string `yaml:"description"` - - SetBy string `yaml:"setBy"` - - IsSet bool `yaml:"isSet"` -} - -// UpdateFile updates the OpenAPI definitions in a file with the given setter value. -func (s SetOpenAPI) UpdateFile(path string) error { - return yaml.UpdateFile(s, path) -} - -func (s SetOpenAPI) Filter(object *yaml.RNode) (*yaml.RNode, error) { - key := fieldmeta.SetterDefinitionPrefix + s.Name - oa, err := object.Pipe(yaml.Lookup("openAPI", "definitions", key)) - if err != nil { - return nil, err - } - if oa == nil { - return nil, errors.Errorf("setter %q is not found", s.Name) - } - def, err := oa.Pipe(yaml.Lookup("x-k8s-cli", "setter")) - if err != nil { - return nil, err - } - if def == nil { - return nil, errors.Errorf("setter %q is not found", s.Name) - } - - // record the OpenAPI type for the setter - var t string - if n := oa.Field("type"); n != nil { - t = n.Value.YNode().Value - } - - // if the setter contains an enumValues map, then ensure the set value appears - // as a key in the map - if values, err := def.Pipe( - yaml.Lookup("enumValues")); err != nil { - // error looking up the enumValues - return nil, err - } else if values != nil { - // contains enumValues map -- validate the set value against the map entries - - // get the enumValues keys - fields, err := values.Fields() - if err != nil { - return nil, err - } - - // search for the user provided value in the set of allowed values - var match bool - for i := range fields { - if fields[i] == s.Value { - // found a match, we are good - match = true - break - } - } - if !match { - // no match found -- provide an informative error to the user - return nil, errors.Errorf("%s does not match the possible values for %s: [%s]", - s.Value, s.Name, strings.Join(fields, ",")) - } - } - - v := yaml.NewScalarRNode(s.Value) - // values are always represented as strings the OpenAPI - // since the are unmarshalled into strings. Use double quote style to - // ensure this consistently. - v.YNode().Tag = yaml.NodeTagString - v.YNode().Style = yaml.DoubleQuotedStyle - - if t != "array" { - // set a scalar value - if err := def.PipeE(&yaml.FieldSetter{Name: "value", Value: v}); err != nil { - return nil, err - } - } else { - // set a list value - if err := def.PipeE(&yaml.FieldClearer{Name: "value"}); err != nil { - return nil, err - } - // create the list values - var elements []*yaml.Node - n := yaml.NewScalarRNode(s.Value).YNode() - n.Tag = yaml.NodeTagString - n.Style = yaml.DoubleQuotedStyle - elements = append(elements, n) - for i := range s.ListValues { - v := s.ListValues[i] - n := yaml.NewScalarRNode(v).YNode() - n.Style = yaml.DoubleQuotedStyle - elements = append(elements, n) - } - l := yaml.NewRNode(&yaml.Node{ - Kind: yaml.SequenceNode, - Content: elements, - }) - - def.YNode().Style = yaml.FoldedStyle - if err := def.PipeE(&yaml.FieldSetter{Name: "listValues", Value: l}); err != nil { - return nil, err - } - } - - if err := def.PipeE(&yaml.FieldSetter{Name: "setBy", StringValue: s.SetBy}); err != nil { - return nil, err - } - - if s.IsSet { - n := &yaml.Node{ - Kind: yaml.ScalarNode, - Value: "true", - Tag: yaml.NodeTagBool, - } - if err := def.PipeE(&yaml.FieldSetter{Name: "isSet", Value: yaml.NewRNode(n)}); err != nil { - return nil, err - } - } - - if s.Description != "" { - d, err := object.Pipe(yaml.LookupCreate( - yaml.MappingNode, "openAPI", "definitions", key)) - if err != nil { - return nil, err - } - if err := d.PipeE(&yaml.FieldSetter{Name: "description", StringValue: s.Description}); err != nil { - return nil, err - } - } - - return object, nil -} - -// SetAll applies the set filter for all yaml nodes and only returns the nodes whose -// corresponding file has at least one node with input setter -func SetAll(s *Set) kio.Filter { - return kio.FilterFunc(func(nodes []*yaml.RNode) ([]*yaml.RNode, error) { - filesToUpdate := sets.String{} - // for each node record the set fields count before and after filter is applied and - // store the corresponding file paths if there is an increment in setters count - for i := range nodes { - preCount := s.Count - _, err := s.Filter(nodes[i]) - if err != nil { - return nil, errors.Wrap(err) - } - if s.Count > preCount { - path, _, err := kioutil.GetFileAnnotations(nodes[i]) - if err != nil { - return nil, errors.Wrap(err) - } - filesToUpdate.Insert(path) - } - } - var nodesInUpdatedFiles []*yaml.RNode - // return only the nodes whose corresponding file has at least one node with input setter - for i := range nodes { - path, _, err := kioutil.GetFileAnnotations(nodes[i]) - if err != nil { - return nil, errors.Wrap(err) - } - if filesToUpdate.Has(path) { - nodesInUpdatedFiles = append(nodesInUpdatedFiles, nodes[i]) - } - } - return nodesInUpdatedFiles, nil - }) -} diff --git a/kyaml/setters2/set_test.go b/kyaml/setters2/set_test.go deleted file mode 100644 index caa428b7f..000000000 --- a/kyaml/setters2/set_test.go +++ /dev/null @@ -1,1620 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -func TestSet_Filter(t *testing.T) { - var tests = []struct { - name string - description string - setter string - openapi string - input string - expected string - }{ - { - name: "set-replicas", - setter: "replicas", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4" - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, - }, - { - name: "set-foo-type", - description: "if a type is specified for a setter, ensure the field is of provided type", - setter: "foo", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.foo: - x-k8s-cli: - setter: - name: foo - value: "4" - type: integer - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - foo: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - foo: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - }, - { - name: "set-foo-type-float", - description: "if a type is specified for a setter, ensure the field is of provided type", - setter: "foo", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.foo: - x-k8s-cli: - setter: - name: foo - value: "4.0" - type: number - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - foo: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - foo: 4.0 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - }, - { - name: "set-foo-no-type", - description: "if a type is not specified for a setter or k8s schema, keep existing quoting", - setter: "foo", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.foo: - x-k8s-cli: - setter: - name: foo - value: "4" - `, - input: ` -apiVersion: custom/v1 -kind: Example -metadata: - name: nginx-deployment - annotations: - foo: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - expected: ` -apiVersion: custom/v1 -kind: Example -metadata: - name: nginx-deployment - annotations: - foo: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - }, - { - name: "set-replicas-enum", - setter: "replicas", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "medium" - enumValues: - small: "1" - medium: "5" - large: "50" - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 1 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 5 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, - }, - { - name: "set-replicas-enum-large", - setter: "replicas", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "large" - enumValues: - small: "1" - medium: "5" - large: "50" - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 1 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - replicas: 50 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, - }, - { - name: "set-arg", - setter: "arg1", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4" - io.k8s.cli.setters.arg1: - x-k8s-cli: - setter: - name: arg1 - value: "some value" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - args: - - a - - b # {"$ref": "#/definitions/io.k8s.cli.setters.arg1"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - args: - - a - - some value # {"$ref": "#/definitions/io.k8s.cli.setters.arg1"}`, - }, - { - name: "substitute-image-tag", - setter: "image-tag", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.image-name: - x-k8s-cli: - setter: - name: image-name - value: "nginx" - io.k8s.cli.setters.image-tag: - x-k8s-cli: - setter: - name: image-tag - value: "1.8.1" - io.k8s.cli.substitutions.image: - x-k8s-cli: - substitution: - name: image - pattern: IMAGE_NAME:IMAGE_TAG - values: - - marker: "IMAGE_NAME" - ref: "#/definitions/io.k8s.cli.setters.image-name" - - marker: "IMAGE_TAG" - ref: "#/definitions/io.k8s.cli.setters.image-tag" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - `, - }, - { - name: "substitute-image-name-enum", - setter: "image-tag", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.image-name: - x-k8s-cli: - setter: - name: image-name - value: "helloworld" - enumValues: - nginx: gcr.io/nginx - helloworld: us.gcr.io/helloworld - io.k8s.cli.setters.image-tag: - x-k8s-cli: - setter: - name: image-tag - value: "1.8.1" - io.k8s.cli.substitutions.image: - x-k8s-cli: - substitution: - name: image - pattern: IMAGE_NAME:IMAGE_TAG - values: - - marker: "IMAGE_NAME" - ref: "#/definitions/io.k8s.cli.setters.image-name" - - marker: "IMAGE_TAG" - ref: "#/definitions/io.k8s.cli.setters.image-tag" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - image: us.gcr.io/helloworld:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - `, - }, - { - name: "substitute-annotation", - setter: "project", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.project: - x-k8s-cli: - setter: - name: project - value: "a" - io.k8s.cli.setters.location: - x-k8s-cli: - setter: - name: location - value: "b" - io.k8s.cli.setters.cluster: - x-k8s-cli: - setter: - name: cluster - value: "c" - io.k8s.cli.substitutions.key: - x-k8s-cli: - substitution: - name: key - pattern: https://container.googleapis.com/v1/projects/PROJECT/locations/LOCATION/clusters/CLUSTER - values: - - marker: "PROJECT" - ref: "#/definitions/io.k8s.cli.setters.project" - - marker: "LOCATION" - ref: "#/definitions/io.k8s.cli.setters.location" - - marker: "CLUSTER" - ref: "#/definitions/io.k8s.cli.setters.cluster" -`, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - key: 'https://container.googleapis.com/v1/projects/a/locations/a/clusters/a' # {"$ref": "#/definitions/io.k8s.cli.substitutions.key"} -`, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - key: 'https://container.googleapis.com/v1/projects/a/locations/b/clusters/c' # {"$ref": "#/definitions/io.k8s.cli.substitutions.key"} -`, - }, - { - name: "substitute-not-match-setter", - setter: "not-real", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.project: - x-k8s-cli: - setter: - name: project - value: "a" - io.k8s.cli.setters.location: - x-k8s-cli: - setter: - name: location - value: "b" - io.k8s.cli.setters.cluster: - x-k8s-cli: - setter: - name: cluster - value: "c" - io.k8s.cli.substitutions.key: - x-k8s-cli: - substitution: - name: key - pattern: https://container.googleapis.com/v1/projects/PROJECT/locations/LOCATION/clusters/CLUSTER - values: - - marker: "PROJECT" - ref: "#/definitions/io.k8s.cli.setters.project" - - marker: "LOCATION" - ref: "#/definitions/io.k8s.cli.setters.location" - - marker: "CLUSTER" - ref: "#/definitions/io.k8s.cli.setters.cluster" -`, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - key: 'https://container.googleapis.com/v1/projects/a/locations/a/clusters/a' # {"$ref": "#/definitions/io.k8s.cli.substitutions.key"} -`, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - key: 'https://container.googleapis.com/v1/projects/a/locations/a/clusters/a' # {"$ref": "#/definitions/io.k8s.cli.substitutions.key"} -`, - }, - { - name: "substitute-image-name", - setter: "image-name", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.image-name: - x-k8s-cli: - setter: - name: image-name - value: "foo" - io.k8s.cli.setters.image-tag: - x-k8s-cli: - setter: - name: image-tag - value: "1.7.9" - io.k8s.cli.substitutions.image: - x-k8s-cli: - substitution: - name: image - pattern: IMAGE_NAME:IMAGE_TAG - values: - - marker: "IMAGE_NAME" - ref: "#/definitions/io.k8s.cli.setters.image-name" - - marker: "IMAGE_TAG" - ref: "#/definitions/io.k8s.cli.setters.image-tag" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - image: nginx:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - image: foo:1.7.9 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - `, - }, - { - name: "substitute-substring", - setter: "image-tag", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.image-name: - x-k8s-cli: - setter: - name: image-name - value: "nginx" - io.k8s.cli.setters.image-tag: - x-k8s-cli: - setter: - name: image-tag - value: "1.8.1" - io.k8s.cli.substitutions.image: - x-k8s-cli: - substitution: - name: image - pattern: IMAGE_NAME:IMAGE_TAG - values: - - marker: "IMAGE_NAME" - ref: "#/definitions/io.k8s.cli.setters.image-name" - - marker: "IMAGE_TAG" - ref: "#/definitions/io.k8s.cli.setters.image-tag" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - image: a:a # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - template: - spec: - containers: - - name: nginx - image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"} - `, - }, - { - name: "set-args-list", - setter: "args", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.args: - x-k8s-cli: - type: array - setter: - name: args - listValues: ["1", "2", "3"] - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - # {"$ref": "#/definitions/io.k8s.cli.setters.args"} - replicas: [] - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - # {"$ref": "#/definitions/io.k8s.cli.setters.args"} - replicas: - - "1" - - "2" - - "3" - `, - }, - { - name: "set-args-list-replace", - setter: "args", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.args: - x-k8s-cli: - type: array - setter: - name: args - listValues: ["1", "2", "3"] - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - # {"$ref": "#/definitions/io.k8s.cli.setters.args"} - replicas: ["4", "5"] - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - # {"$ref": "#/definitions/io.k8s.cli.setters.args"} - replicas: - - "1" - - "2" - - "3" - `, - }, - { - name: "set-with-invalid-type-int", - description: "if a type is set to int instead of integer, we accept it", - setter: "foo", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.foo: - x-k8s-cli: - setter: - name: foo - value: "4" - type: int - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - foo: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - foo: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - }, - { - name: "set-with-invalid-type-bool", - description: "if a type is set to bool instead of boolean, we accept it", - setter: "foo", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.foo: - x-k8s-cli: - setter: - name: foo - value: "true" - type: bool - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - foo: false # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - foo: true # {"$ref": "#/definitions/io.k8s.cli.setters.foo"} - `, - }, - { - name: "set-quoted-value-with-colon", - description: "if a value ends in ':', we should accept it", - setter: "app", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.app: - x-k8s-cli: - setter: - name: app - value: "value:" - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - app: nginx # {"$ref": "#/definitions/io.k8s.cli.setters.app"} - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - app: "value:" # {"$ref": "#/definitions/io.k8s.cli.setters.app"} - `, - }, - { - name: "set-quoted-list-values-with-colon", - setter: "args", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.args: - x-k8s-cli: - type: array - setter: - name: args - listValues: ["1:", "2:", "3:"] - `, - input: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - # {"$ref": "#/definitions/io.k8s.cli.setters.args"} - replicas: - - 4 - - 5 - `, - expected: ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment -spec: - # {"$ref": "#/definitions/io.k8s.cli.setters.args"} - replicas: - - "1:" - - "2:" - - "3:" - `, - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - // reset the openAPI afterward - defer openapi.ResetOpenAPI() - - // parse the input to be modified - r, err := yaml.Parse(test.input) - if !assert.NoError(t, err) { - t.FailNow() - } - - // invoke the setter - instance := &Set{Name: test.setter, SettersSchema: SettersSchema(t, test.openapi)} - result, err := instance.Filter(r) - if !assert.NoError(t, err) { - t.FailNow() - } - - // compare the actual and expected output - actual, err := result.String() - if !assert.NoError(t, err) { - t.FailNow() - } - actual = strings.TrimSpace(actual) - expected := strings.TrimSpace(test.expected) - if !assert.Equal(t, expected, actual) { - t.FailNow() - } - }) - } -} - -func TestSet_SetAll(t *testing.T) { - var tests = []struct { - name string - description string - setter string - openapi string - input []string - expected []string - }{ - { - name: "set-replicas-same-file", - setter: "replicas", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4" - `, - input: []string{` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'cluster.yaml' - internal.config.kubernetes.io/index: '0' - internal.config.kubernetes.io/path: 'cluster.yaml' -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment2 - annotations: - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'cluster.yaml' - internal.config.kubernetes.io/index: '1' - internal.config.kubernetes.io/path: 'cluster.yaml' -spec: - replicas: 10 - `}, - expected: []string{` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'cluster.yaml' - internal.config.kubernetes.io/index: '0' - internal.config.kubernetes.io/path: 'cluster.yaml' -spec: - replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment2 - annotations: - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'cluster.yaml' - internal.config.kubernetes.io/index: '1' - internal.config.kubernetes.io/path: 'cluster.yaml' -spec: - replicas: 10 - `}, - }, - { - name: "set-replicas-different-file", - setter: "replicas", - openapi: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4" - `, - input: []string{` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'cluster.yaml' - internal.config.kubernetes.io/index: '0' - internal.config.kubernetes.io/path: 'cluster.yaml' -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `, ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment2 - annotations: - config.kubernetes.io/index: '1' - config.kubernetes.io/path: 'another_cluster.yaml' - internal.config.kubernetes.io/index: '1' - internal.config.kubernetes.io/path: 'another_cluster.yaml' -spec: - replicas: 10 - `}, - expected: []string{` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - config.kubernetes.io/index: '0' - config.kubernetes.io/path: 'cluster.yaml' - internal.config.kubernetes.io/index: '0' - internal.config.kubernetes.io/path: 'cluster.yaml' -spec: - replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"} - `}, - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - // reset the openAPI afterward - defer openapi.ResetOpenAPI() - - // parse the input to be modified - var inputNodes []*yaml.RNode - for _, s := range test.input { - r, err := yaml.Parse(s) - if !assert.NoError(t, err) { - t.FailNow() - } - inputNodes = append(inputNodes, r) - } - - // invoke the setter - instance := &Set{Name: test.setter, SettersSchema: SettersSchema(t, test.openapi)} - result, err := SetAll(instance).Filter(inputNodes) - if !assert.NoError(t, err) { - t.FailNow() - } - - // compare the actual and expected output - if !assert.NoError(t, err) { - t.FailNow() - } - if !assert.Equal(t, len(result), len(test.expected)) { - t.FailNow() - } - - for i := range result { - actual, _ := result[i].String() - actual = strings.TrimSpace(actual) - expected := strings.TrimSpace(test.expected[i]) - if !assert.Equal(t, expected, actual) { - t.FailNow() - } - } - }) - } -} - -// initSchema initializes the openAPI with the definitions from s -func SettersSchema(t *testing.T, s string) *spec.Schema { - t.Helper() - dir := t.TempDir() - err := os.WriteFile(filepath.Join(dir, "Krmfile"), []byte(s), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - sc, err := openapi.SchemaFromFile(filepath.Join(dir, "Krmfile")) - if !assert.NoError(t, err) { - t.FailNow() - } - return sc -} - -func TestSetOpenAPI_Filter(t *testing.T) { - var tests = []struct { - name string - setter string - value string - values []string - input string - expected string - description string - setBy string - err string - isSet bool - }{ - { - name: "set-replicas", - setter: "replicas", - value: "3", - isSet: true, - input: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4" - required: true - isSet: true - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - `, - expected: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "3" - required: true - isSet: true - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" -`, - }, - { - name: "set-annotation-quoted", - setter: "replicas", - value: "3", - isSet: true, - input: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: 4 - `, - expected: ` -openAPI: - definitions: - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "3" - isSet: true -`, - }, - { - name: "set-replicas-description", - setter: "replicas", - value: "3", - description: "hello world", - isSet: true, - input: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4" - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - `, - expected: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "3" - isSet: true - description: hello world - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" -`, - }, - { - name: "set-replicas-set-by", - setter: "replicas", - value: "3", - setBy: "carl", - isSet: true, - input: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4" - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - `, - expected: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: carl - isSet: true - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" -`, - }, - { - name: "set-replicas-set-by-empty", - setter: "replicas", - value: "3", - isSet: true, - input: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - setBy: "package-default" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4" - setBy: "package-default" - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - setBy: "package-default" - `, - expected: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - setBy: "package-default" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "3" - isSet: true - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - setBy: "package-default" -`, - }, - { - name: "set-replicas-with-enum", - setter: "replicas", - value: "baz", - input: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - setBy: "package-default" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "foo" - enumValues: - foo: bar - baz: biz - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - setBy: "package-default" - `, - expected: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - setBy: "package-default" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "baz" - enumValues: - foo: bar - baz: biz - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - setBy: "package-default" -`, - }, - { - name: "set-replicas-fail", - setter: "replicas", - value: "hello", - isSet: true, - input: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - setBy: "package-default" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "foo" - enumValues: - foo: bar - baz: biz - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - setBy: "package-default" - `, - err: "hello does not match the possible values for replicas: [foo,baz]", - }, - { - name: "error", - setter: "replicas", - err: `setter "replicas" is not found`, - isSet: true, - input: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - `, - expected: ` -openAPI: - definitions: - io.k8s.cli.setters.no-match-1': - x-k8s-cli: - setter: - name: no-match-1 - value: "1" - io.k8s.cli.setters.no-match-2': - x-k8s-cli: - setter: - name: no-match-2 - value: "2" - `, - }, - - { - name: "set-args-list", - setter: "args", - value: "2", - values: []string{"3", "4"}, - input: ` -openAPI: - definitions: - io.k8s.cli.setters.args: - type: array - x-k8s-cli: - setter: - name: args - listValues: ["1"] - required: true - `, - expected: ` -openAPI: - definitions: - io.k8s.cli.setters.args: - type: array - x-k8s-cli: - setter: - name: args - listValues: ["2", "3", "4"] - required: true -`, - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - in, err := yaml.Parse(test.input) - if !assert.NoError(t, err) { - t.FailNow() - } - - // invoke the setter - instance := &SetOpenAPI{ - Name: test.setter, Value: test.value, ListValues: test.values, - SetBy: test.setBy, Description: test.description, IsSet: test.isSet} - result, err := instance.Filter(in) - if test.err != "" { - if !assert.EqualError(t, err, test.err) { - t.FailNow() - } - return - } - if !assert.NoError(t, err) { - t.FailNow() - } - - // compare the actual and expected output - actual, err := result.String() - if !assert.NoError(t, err) { - t.FailNow() - } - actual = strings.TrimSpace(actual) - expected := strings.TrimSpace(test.expected) - if !assert.Equal(t, expected, actual) { - t.FailNow() - } - }) - } -} - -func TestValidateAgainstSchema(t *testing.T) { - maxLength := int64(3) - - testCases := []struct { - name string - setter *setter - schema spec.SchemaProps - shouldValidate bool - expectedErrorMsg string - }{ - { - name: "no schema", - setter: &setter{ - Name: "foo", - Value: "bar", - }, - schema: spec.SchemaProps{}, - shouldValidate: true, - }, - { - name: "simple string value", - setter: &setter{ - Name: "foo", - Value: "bar", - }, - schema: spec.SchemaProps{ - Type: []string{"string"}, - }, - shouldValidate: true, - }, - { - name: "simple bool value", - setter: &setter{ - Name: "foo", - Value: "false", - }, - schema: spec.SchemaProps{ - Type: []string{"boolean"}, - }, - shouldValidate: true, - }, - { - name: "simple null value", - setter: &setter{ - Name: "foo", - Value: "null", - }, - schema: spec.SchemaProps{ - Type: []string{"null"}, - }, - shouldValidate: true, - }, - { - name: "bool value in yaml but not openapi", - setter: &setter{ - Name: "foo", - Value: "yes", - }, - schema: spec.SchemaProps{ - Type: []string{"string"}, - }, - shouldValidate: true, - }, - { - name: "number value should be accepted as integer", - setter: &setter{ - Name: "foo", - Value: "45", - }, - schema: spec.SchemaProps{ - Type: []string{"integer"}, - }, - shouldValidate: true, - }, - { - name: "string type allows string-specific validations", - setter: &setter{ - Name: "foo", - Value: "1234", - }, - schema: spec.SchemaProps{ - Type: []string{"string"}, - MaxLength: &maxLength, - }, - shouldValidate: false, - expectedErrorMsg: "foo in body should be at most 3 chars long", - }, - { - name: "list with int values", - setter: &setter{ - Name: "foo", - ListValues: []string{"123", "456"}, - }, - schema: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - }, - }, - }, - }, - shouldValidate: true, - }, - { - name: "list expecting int values, but with a string", - setter: &setter{ - Name: "foo", - ListValues: []string{"123", "456", "abc"}, - }, - schema: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - }, - }, - }, - }, - shouldValidate: false, - expectedErrorMsg: "foo[2] in body must be of type integer", - }, - { - name: "all values should satisfy type string", - setter: &setter{ - Name: "foo", - Value: "1234", - }, - schema: spec.SchemaProps{ - Type: []string{"string"}, - }, - shouldValidate: true, - }, - { - name: "all values should satisfy type string even in arrays", - setter: &setter{ - Name: "foo", - ListValues: []string{"123", "456"}, - }, - schema: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - }, - }, - }, - }, - shouldValidate: true, - }, - { - name: "List values without any schema", - setter: &setter{ - Name: "foo", - ListValues: []string{"123", "true", "abc"}, - }, - schema: spec.SchemaProps{}, - shouldValidate: true, - }, - } - - for i := range testCases { - test := testCases[i] - t.Run(test.name, func(t *testing.T) { - ext := &CliExtension{ - Setter: test.setter, - } - - schema := &spec.Schema{ - SchemaProps: test.schema, - } - - err := validateAgainstSchema(ext, schema) - - if test.shouldValidate { - assert.NoError(t, err) - return - } - - if !assert.Error(t, err) { - t.FailNow() - } - assert.Contains(t, err.Error(), test.expectedErrorMsg) - }) - } -} diff --git a/kyaml/setters2/settersutil/deletecreator_test.go b/kyaml/setters2/settersutil/deletecreator_test.go deleted file mode 100644 index f1ebd3250..000000000 --- a/kyaml/setters2/settersutil/deletecreator_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package settersutil - -import ( - "os" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - "sigs.k8s.io/kustomize/kyaml/openapi" -) - -var openAPIFile = ` -openAPI: - definitions: - io.k8s.cli.setters.image: - x-k8s-cli: - setter: - name: image - value: "2" - io.k8s.cli.setters.tag: - x-k8s-cli: - setter: - name: tag - value: "sometag" -` - -var resourceFile = ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - image: 3 # {"$openapi":"image"} -spec: - image: 3 # {"$openapi":"image"} -` - -func TestDeleterCreator_Delete(t *testing.T) { - openapi.ResetOpenAPI() - defer openapi.ResetOpenAPI() - openAPI, err := os.CreateTemp("", "openAPI.yaml") - if !assert.NoError(t, err) { - t.FailNow() - } - defer os.Remove(openAPI.Name()) - // write openapi to temp dir - err = os.WriteFile(openAPI.Name(), []byte(openAPIFile), 0644) - if !assert.NoError(t, err) { - t.FailNow() - } - - // write resource file to temp dir - resource, err := os.CreateTemp("", "k8s-cli-*.yaml") - if !assert.NoError(t, err) { - t.FailNow() - } - defer os.Remove(resource.Name()) - err = os.WriteFile(resource.Name(), []byte(resourceFile), 0644) - if !assert.NoError(t, err) { - t.FailNow() - } - - sc, err := openapi.SchemaFromFile(openAPI.Name()) - if !assert.NoError(t, err) { - t.FailNow() - } - // add a delete creator - dc := DeleterCreator{ - Name: "image", - DefinitionPrefix: fieldmeta.SetterDefinitionPrefix, - SettersSchema: sc, - } - - dc.OpenAPIPath = openAPI.Name() - dc.ResourcesPath = resource.Name() - - err = dc.Delete() - if !assert.NoError(t, err) { - t.FailNow() - } - - actualOpenAPI, err := os.ReadFile(openAPI.Name()) - if err != nil { - t.FailNow() - } - - actualResource, err := os.ReadFile(resource.Name()) - if err != nil { - t.FailNow() - } - - expectedOpenAPI := ` -openAPI: - definitions: - io.k8s.cli.setters.tag: - x-k8s-cli: - setter: - name: tag - value: "sometag" -` - expectedResoure := ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - annotations: - image: 3 -spec: - image: 3 -` - - assert.Equal(t, strings.TrimSpace(expectedOpenAPI), strings.TrimSpace(string(actualOpenAPI))) - assert.Equal(t, strings.TrimSpace(expectedResoure), strings.TrimSpace(string(actualResource))) -} diff --git a/kyaml/setters2/settersutil/deletercreator.go b/kyaml/setters2/settersutil/deletercreator.go deleted file mode 100644 index 13c2d1815..000000000 --- a/kyaml/setters2/settersutil/deletercreator.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package settersutil - -import ( - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/setters2" -) - -// DeleterCreator delete a definition in the OpenAPI definitions, and removes references -// to the definition from matching resource fields. -type DeleterCreator struct { - // Name is the name of the setter or substitution to delete - Name string - - // DefinitionPrefix is the prefix of the OpenAPI definition type - DefinitionPrefix string - - RecurseSubPackages bool - - OpenAPIFileName string - - // Path to openAPI file - OpenAPIPath string - - // Path to resources folder - ResourcesPath string - - SettersSchema *spec.Schema -} - -func (d DeleterCreator) Delete() error { - dd := setters2.DeleterDefinition{ - Name: d.Name, - DefinitionPrefix: d.DefinitionPrefix, - } - if err := dd.DeleteFromFile(d.OpenAPIPath); err != nil { - return err - } - - // Update the resources with the deleter reference - inout := &kio.LocalPackageReadWriter{PackagePath: d.ResourcesPath, PackageFileName: d.OpenAPIFileName} - return kio.Pipeline{ - Inputs: []kio.Reader{inout}, - Filters: []kio.Filter{kio.FilterAll( - &setters2.Delete{ - Name: d.Name, - DefinitionPrefix: d.DefinitionPrefix, - SettersSchema: d.SettersSchema, - })}, - Outputs: []kio.Writer{inout}, - }.Execute() -} diff --git a/kyaml/setters2/settersutil/fieldsetter.go b/kyaml/setters2/settersutil/fieldsetter.go deleted file mode 100644 index 2e2bc5416..000000000 --- a/kyaml/setters2/settersutil/fieldsetter.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package settersutil - -import ( - "os" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/setters2" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// FieldSetter sets the value for a field setter. -type FieldSetter struct { - // Name is the name of the setter to set - Name string - - // Value is the value to set - Value string - - // ListValues contains a list of values to set on a Sequence - ListValues []string - - Description string - - SetBy string - - Count int - - OpenAPIPath string - - OpenAPIFileName string - - ResourcesPath string - - RecurseSubPackages bool - - IsSet bool - - SettersSchema *spec.Schema -} - -func (fs *FieldSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) { - fs.Count, _ = fs.Set() - return nil, nil -} - -// Set updates the OpenAPI definitions and resources with the new setter value -func (fs *FieldSetter) Set() (int, error) { - // Update the OpenAPI definitions - soa := setters2.SetOpenAPI{ - Name: fs.Name, - Value: fs.Value, - ListValues: fs.ListValues, - Description: fs.Description, - SetBy: fs.SetBy, - IsSet: fs.IsSet, - } - - // the input field value is updated in the openAPI file and then parsed - // at to get the value and set it to resource files, but if there is error - // after updating openAPI file and while updating resources, the openAPI - // file should be reverted, as set operation failed - stat, err := os.Stat(fs.OpenAPIPath) - if err != nil { - return 0, err - } - - curOpenAPI, err := os.ReadFile(fs.OpenAPIPath) - if err != nil { - return 0, err - } - - // write the new input value to openAPI file - if err := soa.UpdateFile(fs.OpenAPIPath); err != nil { - return 0, err - } - - // Load the updated definitions - sc, err := openapi.SchemaFromFile(fs.OpenAPIPath) - if err != nil { - return 0, err - } - fs.SettersSchema = sc - - // Update the resources with the new value - // Set NoDeleteFiles to true as SetAll will return only the nodes of files which should be updated and - // hence, rest of the files should not be deleted - inout := &kio.LocalPackageReadWriter{PackagePath: fs.ResourcesPath, NoDeleteFiles: true, PackageFileName: fs.OpenAPIFileName} - s := &setters2.Set{Name: fs.Name, SettersSchema: sc} - err = kio.Pipeline{ - Inputs: []kio.Reader{inout}, - Filters: []kio.Filter{setters2.SetAll(s)}, - Outputs: []kio.Writer{inout}, - }.Execute() - - // revert openAPI file if set operation fails - if err != nil { - if writeErr := os.WriteFile(fs.OpenAPIPath, curOpenAPI, stat.Mode().Perm()); writeErr != nil { - return 0, writeErr - } - } - return s.Count, err -} - -// SetAllSetterDefinitions reads all the Setter Definitions from the input OpenAPI -// file and sets all values for the resource configs in the provided destination directories. -// If syncOpenAPI is true, the openAPI files in destination directories are also -// updated with the setter values in the input openAPI file -func SetAllSetterDefinitions(openAPIPath string, dirs ...string) error { - sc, err := openapi.SchemaFromFile(openAPIPath) - if err != nil { - return err - } - for _, destDir := range dirs { - rw := &kio.LocalPackageReadWriter{ - PackagePath: destDir, - // set output won't include resources from files which - // weren't modified. make sure we don't delete them. - NoDeleteFiles: true, - } - - // apply all of the setters to the directory - err := kio.Pipeline{ - Inputs: []kio.Reader{rw}, - // Set all of the setters - Filters: []kio.Filter{setters2.SetAll(&setters2.Set{SetAll: true, SettersSchema: sc})}, - Outputs: []kio.Writer{rw}, - }.Execute() - if err != nil { - return err - } - } - return nil -} diff --git a/kyaml/setters2/settersutil/fieldsetter_test.go b/kyaml/setters2/settersutil/fieldsetter_test.go deleted file mode 100644 index 98db5b8d8..000000000 --- a/kyaml/setters2/settersutil/fieldsetter_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package settersutil - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSetAllSetterDefinitions(t *testing.T) { - var tests = []struct { - name string - srcOpenAPIFile string - destFile string - destOpenAPI string - expectedDestFile string - expectedDestOpenAPIFile string - }{ - { - name: "set values only to resources and not the openAPI", - srcOpenAPIFile: `openAPI: - definitions: - io.k8s.cli.setters.namespace: - x-k8s-cli: - setter: - name: namespace - value: "project-namespace" - io.k8s.cli.setters.replicas: - x-k8s-cli: - setter: - name: replicas - value: "4"`, - - destFile: `apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - namespace: some-other-namespace # {"$ref": "#/definitions/io.k8s.cli.setters.namespace"} -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas-new"}`, - - destOpenAPI: `openAPI: - definitions: - io.k8s.cli.setters.namespace: - x-k8s-cli: - setter: - name: namespace - value: "some-other-namespace" - io.k8s.cli.setters.replicas-new: - x-k8s-cli: - setter: - name: replicas-new - value: "3"`, - - expectedDestFile: `apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - namespace: project-namespace # {"$ref": "#/definitions/io.k8s.cli.setters.namespace"} -spec: - replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas-new"} -`, - - expectedDestOpenAPIFile: `openAPI: - definitions: - io.k8s.cli.setters.namespace: - x-k8s-cli: - setter: - name: namespace - value: "some-other-namespace" - io.k8s.cli.setters.replicas-new: - x-k8s-cli: - setter: - name: replicas-new - value: "3"`, - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - srcDir := t.TempDir() - destDir := t.TempDir() - - err := os.WriteFile(filepath.Join(srcDir, "Krmfile"), []byte(test.srcOpenAPIFile), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - err = os.WriteFile(filepath.Join(destDir, "destFile.yaml"), []byte(test.destFile), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - err = os.WriteFile(filepath.Join(destDir, "Krmfile"), []byte(test.destOpenAPI), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - - err = SetAllSetterDefinitions(filepath.Join(srcDir, "Krmfile"), destDir) - if !assert.NoError(t, err) { - t.FailNow() - } - - actualDestFile1, err := os.ReadFile(filepath.Join(destDir, "destFile.yaml")) - if !assert.NoError(t, err) { - t.FailNow() - } - if !assert.Equal(t, test.expectedDestFile, string(actualDestFile1)) { - t.FailNow() - } - - actualDestOpenAPIFile1, err := os.ReadFile(filepath.Join(destDir, "Krmfile")) - if !assert.NoError(t, err) { - t.FailNow() - } - - if !assert.Equal(t, test.expectedDestOpenAPIFile, string(actualDestOpenAPIFile1)) { - t.FailNow() - } - }) - } -} diff --git a/kyaml/setters2/settersutil/settercreator.go b/kyaml/setters2/settersutil/settercreator.go deleted file mode 100644 index ded388a8a..000000000 --- a/kyaml/setters2/settersutil/settercreator.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package settersutil - -import ( - "fmt" - "strings" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/setters2" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// SetterCreator creates or updates a setter in the OpenAPI definitions, and inserts references -// to the setter from matching resource fields. -type SetterCreator struct { - // Name is the name of the setter to create or update. - Name string - - SetBy string - - Description string - - Type string - - Schema string - - // FieldName if set will add the OpenAPI reference to fields with this name or path - // FieldName may be the full name of the field, full path to the field, or the path suffix. - // e.g. all of the following would match spec.template.spec.containers.image -- - // [image, containers.image, spec.containers.image, template.spec.containers.image, - // spec.template.spec.containers.image] - // Optional. If unspecified match all field names. - FieldName string - - // FieldValue if set will add the OpenAPI reference to fields if they have this value. - // Optional. If unspecified match all field values. - FieldValue string - - // Required indicates that the setter must be set by package consumer before - // live apply/preview. This field is added to the setter definition to record - // the package publisher's intent to make the setter required to be set. - Required bool - - RecurseSubPackages bool - - OpenAPIFileName string - - // Path to openAPI file - OpenAPIPath string - - // Path to resources folder - ResourcesPath string - - SettersSchema *spec.Schema -} - -func (c *SetterCreator) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) { - return nil, c.Create() -} - -func (c *SetterCreator) Create() error { - err := c.validateSetterInfo() - if err != nil { - return err - } - err = validateSchema(c.Schema) - if err != nil { - return errors.Errorf("invalid schema: %v", err) - } - - // Update the resources with the setter reference - inout := &kio.LocalPackageReadWriter{PackagePath: c.ResourcesPath} - a := &setters2.Add{ - FieldName: c.FieldName, - FieldValue: c.FieldValue, - Ref: fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + c.Name, - Type: c.Type, - SettersSchema: c.SettersSchema, - } - err = kio.Pipeline{ - Inputs: []kio.Reader{inout}, - Filters: []kio.Filter{kio.FilterAll(a)}, - Outputs: []kio.Writer{inout}, - }.Execute() - if a.Count == 0 { - fmt.Printf("setter %q doesn't match any field in resource configs, "+ - "but creating setter definition\n", c.Name) - } - if err != nil { - return err - } - - // Update the OpenAPI definitions to hace the setter - sd := setters2.SetterDefinition{ - Name: c.Name, Value: c.FieldValue, SetBy: c.SetBy, Description: c.Description, - Type: c.Type, Schema: c.Schema, Required: c.Required, - } - if err := sd.AddToFile(c.OpenAPIPath); err != nil { - return err - } - - // Load the updated definitions - sc, err := openapi.SchemaFromFile(c.OpenAPIPath) - if err != nil { - return err - } - c.SettersSchema = sc - // if the setter is of array type write the derived list values back to - // openAPI definitions - if len(a.ListValues) > 0 { - sd.ListValues = a.ListValues - sd.Value = "" - if err := sd.AddToFile(c.OpenAPIPath); err != nil { - return err - } - } - - return nil -} - -// The types recognized by by the go openapi validation library: -// https://github.com/go-openapi/validate/blob/master/helpers.go#L35 -var validTypeValues = []string{ - "object", "array", "string", "integer", "number", "boolean", "file", "null", -} - -// validateSchema parses the provided schema and validates it. -func validateSchema(schema string) error { - var sc spec.Schema - err := sc.UnmarshalJSON([]byte(schema)) - if err != nil { - return errors.Errorf("unable to parse schema: %v", err) - } - return validateSchemaTypes(&sc) -} - -// validateSchemaTypes traverses the schema and checks that only valid types -// are used. -func validateSchemaTypes(sc *spec.Schema) error { - if len(sc.Type) > 1 { - return errors.Errorf("only one type is supported: %s", strings.Join(sc.Type, ", ")) - } - - if len(sc.Type) == 1 { - t := sc.Type[0] - var match bool - for _, validType := range validTypeValues { - if t == validType { - match = true - } - } - if !match { - return errors.Errorf("type %q is not supported. Must be one of: %s", - t, strings.Join(validTypeValues, ", ")) - } - } - - if items := sc.Items; items != nil { - if items.Schema != nil { - err := validateSchemaTypes(items.Schema) - if err != nil { - return err - } - } - for i := range items.Schemas { - schema := items.Schemas[i] - err := validateSchemaTypes(&schema) - if err != nil { - return err - } - } - } - return nil -} - -func (c SetterCreator) validateSetterInfo() error { - // check if substitution with same name exists and throw error - ref, err := spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + c.Name) - if err != nil { - return err - } - - subst, _ := openapi.Resolve(&ref, c.SettersSchema) - // if substitution already exists with the input setter name, throw error - if subst != nil { - return errors.Errorf("substitution with name %q already exists, "+ - "substitution and setter can't have same name", c.Name) - } - - // check if setter with same name exists and throw error - ref, err = spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + c.Name) - if err != nil { - return err - } - - setter, _ := openapi.Resolve(&ref, c.SettersSchema) - // if setter already exists with the input setter name, throw error - if setter != nil { - return errors.Errorf("setter with name %q already exists, "+ - "if you want to modify it, please delete the existing setter and recreate it", c.Name) - } - return nil -} diff --git a/kyaml/setters2/settersutil/substitutioncreator.go b/kyaml/setters2/settersutil/substitutioncreator.go deleted file mode 100644 index 5d8b24837..000000000 --- a/kyaml/setters2/settersutil/substitutioncreator.go +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package settersutil - -import ( - "fmt" - "os" - "regexp" - "strings" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - "sigs.k8s.io/kustomize/kyaml/kio" - "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/sets" - "sigs.k8s.io/kustomize/kyaml/setters2" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// SubstitutionCreator creates or updates a substitution in the OpenAPI definitions, and -// inserts references to the substitution from matching resource fields. -type SubstitutionCreator struct { - // Name is the name of the substitution to create - Name string - - // Pattern is the substitution pattern - Pattern string - - // Values are the substitution values for the pattern - Values []setters2.Value - - // FieldName if set will add the OpenAPI reference to fields with this name or path - // FieldName may be the full name of the field, full path to the field, or the path suffix. - // e.g. all of the following would match spec.template.spec.containers.image -- - // [image, containers.image, spec.containers.image, template.spec.containers.image, - // spec.template.spec.containers.image] - // Optional. If unspecified match all field names. - FieldName string - - // FieldValue if set will add the OpenAPI reference to fields if they have this value. - // Optional. If unspecified match all field values. - FieldValue string - - // Path to openAPI file - OpenAPIPath string - - OpenAPIFileName string - - RecurseSubPackages bool - - // Path to resources folder - ResourcesPath string - - SettersSchema *spec.Schema -} - -func (c *SubstitutionCreator) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) { - return nil, c.Create() -} - -func (c SubstitutionCreator) Create() error { - err := c.validateSubstitutionInfo() - if err != nil { - return err - } - values, err := c.markersAndRefs(c.Name, c.Pattern) - if err != nil { - return err - } - c.Values = values - d := setters2.SubstitutionDefinition{ - Name: c.Name, - Values: c.Values, - Pattern: c.Pattern, - } - - // the input substitution definition is updated in the openAPI file and then parsed - // to check if there are any cycles in nested substitutions, if there are - // any, the openAPI file will be reverted to current state and error is thrown - stat, err := os.Stat(c.OpenAPIPath) - if err != nil { - return err - } - - curOpenAPI, err := os.ReadFile(c.OpenAPIPath) - if err != nil { - return err - } - - if err := d.AddToFile(c.OpenAPIPath); err != nil { - return err - } - - // Load the updated definitions - sc, err := openapi.SchemaFromFile(c.OpenAPIPath) - if err != nil { - return err - } - c.SettersSchema = sc - - visited := sets.String{} - ref, err := spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + c.Name) - if err != nil { - return err - } - - schema, err := openapi.Resolve(&ref, c.SettersSchema) - if err != nil { - return err - } - - ext, err := setters2.GetExtFromSchema(schema) - if err != nil { - return err - } - - err = c.CreateSettersForSubstitution(c.OpenAPIPath) - if err != nil { - return err - } - - // Load the updated definitions after setters are created - sc, err = openapi.SchemaFromFile(c.OpenAPIPath) - if err != nil { - return err - } - c.SettersSchema = sc - - // revert openAPI file if there are cycles detected in created input substitution - if err := c.checkForCycles(ext, visited); err != nil { - if writeErr := os.WriteFile(c.OpenAPIPath, curOpenAPI, stat.Mode().Perm()); writeErr != nil { - return writeErr - } - return err - } - - a := &setters2.Add{ - FieldName: c.FieldName, - FieldValue: c.FieldValue, - Ref: fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + c.Name, - SettersSchema: c.SettersSchema, - } - - // Update the resources with the substitution reference - inout := &kio.LocalPackageReadWriter{PackagePath: c.ResourcesPath, PackageFileName: c.OpenAPIFileName} - err = kio.Pipeline{ - Inputs: []kio.Reader{inout}, - Filters: []kio.Filter{kio.FilterAll(a)}, - Outputs: []kio.Writer{inout}, - }.Execute() - - if a.Count == 0 { - fmt.Printf("substitution %s doesn't match any field value in resource configs, "+ - "but creating substitution definition\n", c.Name) - } - return err -} - -// createMarkersAndRefs takes the input pattern, creates setter/substitution markers -// and corresponding openAPI refs -func (c *SubstitutionCreator) markersAndRefs(substName, pattern string) ([]setters2.Value, error) { - var values []setters2.Value - // extract setter name tokens from pattern enclosed in ${} - re := regexp.MustCompile(`\$\{([^}]*)\}`) - markers := re.FindAllString(pattern, -1) - if len(markers) == 0 { - return nil, errors.Errorf("unable to find setter or substitution names in pattern, " + - "setter names must be enclosed in ${}") - } - - for _, marker := range markers { - name := strings.TrimSuffix(strings.TrimPrefix(marker, "${"), "}") - if name == substName { - return nil, fmt.Errorf("setters must have different name than the substitution: %s", name) - } - - ref, err := spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + name) - if err != nil { - return nil, err - } - - var markerRef string - subst, _ := openapi.Resolve(&ref, c.SettersSchema) - // check if the substitution exists with the marker name or fall back to creating setter - // ref with the name - if subst != nil { - markerRef = fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + name - } else { - markerRef = fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + name - } - - values = append( - values, - setters2.Value{Marker: marker, Ref: markerRef}, - ) - } - return values, nil -} - -// CreateSettersForSubstitution creates the setters for all the references in the substitution -// values if they don't already exist in openAPIPath file. -func (c SubstitutionCreator) CreateSettersForSubstitution(openAPIPath string) error { - y, err := yaml.ReadFile(openAPIPath) - if err != nil { - return err - } - - m, err := c.GetValuesForMarkers() - if err != nil { - return err - } - - // for each ref in values, check if the setter or substitution already exists, if not create setter - for _, value := range c.Values { - // continue if ref is a substitution, as it has already been checked if it exists - // as part of preRunE - if strings.Contains(value.Ref, fieldmeta.SubstitutionDefinitionPrefix) { - fmt.Printf("found a substitution with name %q\n", value.Marker) - continue - } - setterObj, err := y.Pipe(yaml.Lookup( - // get the setter key from ref. Ex: from #/definitions/io.k8s.cli.setters.image_setter - // extract io.k8s.cli.setters.image_setter - "openAPI", "definitions", strings.TrimPrefix(value.Ref, fieldmeta.DefinitionsPrefix))) - - if err != nil { - return err - } - - if setterObj == nil { - name := strings.TrimPrefix(value.Ref, fieldmeta.DefinitionsPrefix+fieldmeta.SetterDefinitionPrefix) - value := m[value.Marker] - fmt.Printf("unable to find setter with name %s, creating new setter with value %s\n", name, value) - sd := setters2.SetterDefinition{ - // get the setter name from ref. Ex: from #/definitions/io.k8s.cli.setters.image_setter - // extract image_setter - Name: name, - Value: value, - } - err := sd.AddToFile(openAPIPath) - if err != nil { - return err - } - } - } - return nil -} - -func (c SubstitutionCreator) checkForCycles(ext *setters2.CliExtension, visited sets.String) error { - // check if the substitution has already been visited and throw error as cycles - // are not allowed in nested substitutions - if visited.Has(ext.Substitution.Name) { - return errors.Errorf( - "cyclic substitution detected with name " + ext.Substitution.Name) - } - - visited.Insert(ext.Substitution.Name) - - // substitute each setter into the pattern to get the new value - // if substitution references to another substitution, recursively - // process the nested substitutions to replace the pattern with setter values - for _, v := range ext.Substitution.Values { - if v.Ref == "" { - return errors.Errorf( - "missing reference on substitution " + ext.Substitution.Name) - } - ref, err := spec.NewRef(v.Ref) - if err != nil { - return errors.Wrap(err) - } - def, err := openapi.Resolve(&ref, c.SettersSchema) // resolve the def to its openAPI def - if err != nil { - return errors.Wrap(err) - } - defExt, err := setters2.GetExtFromSchema(def) // parse the extension out of the openAPI - if err != nil { - return errors.Wrap(err) - } - - if defExt.Substitution != nil { - // parse recursively if it reference is substitution - err := c.checkForCycles(defExt, visited) - if err != nil { - return err - } - } - } - - return nil -} - -// GetValuesForMarkers parses the pattern and field value to derive values for the -// markers in the pattern string. Returns error if the marker values can't be derived -func (c SubstitutionCreator) GetValuesForMarkers() (map[string]string, error) { - m := make(map[string]string) - indices, err := c.GetStartIndices() - if err != nil { - return nil, err - } - fv := c.FieldValue - pattern := c.Pattern - fvInd := 0 - patternInd := 0 - // iterate fv, pattern with indices fvInd, patternInd respectively and when patternInd hits the index of a marker, - // freeze patternInd and iterate fvInd and capture string till we find the substring just after current marker - // and before next marker - - // Ex: fv = "something/ubuntu:0.1.0", pattern = "something/IMAGE:VERSION", till patternInd reaches 10 - // just proceed fvInd and patternInd and check if fv[fvInd]==pattern[patternInd] when patternInd is 10, - // freeze patternInd and move fvInd till it sees substring ':' which derives IMAGE = ubuntu and so on. - for fvInd < len(fv) && patternInd < len(pattern) { - // if we hit marker index, extract its corresponding value - if marker, ok := indices[patternInd]; ok { - // increment the patternInd to end of marker. This helps us to extract the substring before next marker. - patternInd += len(marker) - var value string - if value, fvInd, err = c.extractValueForMarker(fvInd, fv, patternInd, indices); err != nil { - return nil, err - } - // if marker is repeated in the pattern, make sure that the corresponding values - // are same and throw error if not. - if prevValue, ok := m[marker]; ok && prevValue != value { - return nil, errors.Errorf( - "marker %s is found to have different values %s and %s", marker, prevValue, value) - } - m[marker] = value - } else { - // Ex: fv = "samething/ubuntu:0.1.0" pattern = "something/IMAGE:VERSION". Error out at 'a' in fv. - if fv[fvInd] != pattern[patternInd] { - return nil, errors.Errorf( - "unable to derive values for markers, " + - "create setters for all markers and then try again") - } - fvInd++ - patternInd++ - } - } - // check if both strings are completely visited or throw error - if fvInd < len(fv) || patternInd < len(pattern) { - return nil, errors.Errorf( - "unable to derive values for markers, " + - "create setters for all markers and then try again") - } - return m, nil -} - -// GetStartIndices returns the start indices of all the markers in the pattern -func (c SubstitutionCreator) GetStartIndices() (map[int]string, error) { - indices := make(map[int]string) - for _, value := range c.Values { - found := false - for i := range c.Pattern { - if strings.HasPrefix(c.Pattern[i:], value.Marker) { - indices[i] = value.Marker - found = true - } - } - if !found { - return nil, errors.Errorf("unable to find marker " + value.Marker + " in the pattern") - } - } - if err := validateMarkers(indices); err != nil { - return nil, err - } - return indices, nil -} - -// validateMarkers takes the indices map, checks if any of 2 markers not have delimiters, -// checks if any marker is substring of other and returns error -func validateMarkers(indices map[int]string) error { - for k1, v1 := range indices { - for k2, v2 := range indices { - if k1 != k2 && k1+len(v1) == k2 { - return errors.Errorf( - "markers %s and %s are found to have no delimiters between them,"+ - " pre-create setters and try again", v1, v2) - } - if v1 != v2 && strings.Contains(v1, v2) { - return errors.Errorf( - "markers %s is substring of %s,"+ - " no marker should be substring of other", v2, v1) - } - } - } - return nil -} - -// extractValueForMarker returns the value string for a marker and the incremented index -func (c SubstitutionCreator) extractValueForMarker(fvInd int, fv string, patternInd int, indices map[int]string) (string, int, error) { - nonMarkerStr := strTillNextMarker(indices, patternInd, c.Pattern) - - // return the remaining string of fv till end if patternInd is at end of pattern - if patternInd == len(c.Pattern) { - return fv[fvInd:], len(fv), nil - } - - // split remaining fv starting from fvInd with the non marker substring delimiter and get the first value - // In example fv = "something/ubuntu::0.1.0", pattern = "something/IMAGE::VERSION", - // split with "::" delimiter in fv which gives markerValue = ubuntu for marker IMAGE - // increment fvInd by length of extracted marker value and return fvInd - if markerValues := strings.Split(fv[fvInd:], nonMarkerStr); len(markerValues) > 0 { - return markerValues[0], fvInd + len(markerValues[0]), nil - } - - return "", -1, errors.Errorf( - "unable to derive values for markers," + - " create setters for all markers and then try again") -} - -// substrOfLen takes a string, start index and length and returns substring of given length -// or till end of string -func substrOfLen(str string, startInd int, length int) string { - return str[startInd:min(len(str), startInd+length)] -} - -// strTillNextMarker takes in the indices map, a start index and returns the substring till -// start of next marker -func strTillNextMarker(indices map[int]string, startInd int, pattern string) string { - // initialize with max value which is length of pattern - nextMarkerStartInd := len(pattern) - for ind := range indices { - if ind > startInd { - nextMarkerStartInd = min(ind-startInd, nextMarkerStartInd) - } - } - return substrOfLen(pattern, startInd, nextMarkerStartInd) -} - -func min(a int, b int) int { - if a < b { - return a - } - return b -} - -func (c SubstitutionCreator) validateSubstitutionInfo() error { - // check if substitution with same name exists and throw error - ref, err := spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SubstitutionDefinitionPrefix + c.Name) - if err != nil { - return err - } - - subst, _ := openapi.Resolve(&ref, c.SettersSchema) - // if substitution already exists with the input substitution name, throw error - if subst != nil { - return errors.Errorf("substitution with name %q already exists", c.Name) - } - - // check if setter with same name exists and throw error - ref, err = spec.NewRef(fieldmeta.DefinitionsPrefix + fieldmeta.SetterDefinitionPrefix + c.Name) - if err != nil { - return err - } - - setter, _ := openapi.Resolve(&ref, c.SettersSchema) - // if setter already exists with input substitution name, throw error - if setter != nil { - return errors.Errorf(fmt.Sprintf("setter with name %q already exists, "+ - "substitution and setter can't have same name", c.Name)) - } - - return nil -} diff --git a/kyaml/setters2/settersutil/substitutioncreator_test.go b/kyaml/setters2/settersutil/substitutioncreator_test.go deleted file mode 100644 index 493856cea..000000000 --- a/kyaml/setters2/settersutil/substitutioncreator_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package settersutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/setters2" -) - -func TestGetValuesForMarkers(t *testing.T) { - var tests = []struct { - name string - pattern string - fieldValue string - markers []string - expectedError error - expectedOutput map[string]string - }{ - { - name: "positive example", - markers: []string{"IMAGE", "VERSION"}, - pattern: "something/IMAGE::VERSION/otherthing/IMAGE::VERSION/", - fieldValue: "something/nginx::0.1.0/otherthing/nginx::0.1.0/", - expectedOutput: map[string]string{"IMAGE": "nginx", "VERSION": "0.1.0"}, - }, - { - name: "marker with different values", - markers: []string{"IMAGE", "VERSION"}, - pattern: "something/IMAGE:VERSION/IMAGE", - fieldValue: "something/nginx:0.1.0/ubuntu", - expectedError: errors.Errorf("marker IMAGE is found to have different values nginx and ubuntu"), - }, - { - name: "unmatched pattern", - markers: []string{"IMAGE", "VERSION"}, - pattern: "something/IMAGE:VERSION", - fieldValue: "otherthing/nginx:0.1.0", - expectedError: errors.Errorf("unable to derive values for markers"), - }, - { - name: "unmatched pattern at the end", - markers: []string{"IMAGE", "VERSION"}, - pattern: "something/IMAGE:VERSION/abc", - fieldValue: "something/nginx:0.1.0/abcd", - expectedError: errors.Errorf("unable to derive values for markers"), - }, - { - name: "substring markers", - markers: []string{"IMAGE", "VERSION", "MAGE"}, - pattern: "something/IMAGE:VERSION/abc/MAGE", - fieldValue: "something/nginx:0.1.0/abc/ubuntu", - expectedError: errors.Errorf("no marker should be substring of other"), - }, - { - name: "markers with no delimiters", - markers: []string{"IMAGE", "VERSION"}, - pattern: "something/IMAGEVERSION/", - fieldValue: "something/nginx0.1.0/", - expectedError: errors.Errorf("no delimiters between them"), - }, - { - name: "unmatched delimiter", - markers: []string{"IMAGE", "VERSION"}, - pattern: "something/IMAGE:^VERSION/otherthing/IMAGE::VERSION/", - fieldValue: "something/nginx::0.1.0/otherthing/nginx::0.1.0/", - expectedError: errors.Errorf("unable to derive values for markers"), - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - values := []setters2.Value{} - for _, marker := range test.markers { - value := setters2.Value{ - Marker: marker, - } - values = append(values, value) - } - - sc := SubstitutionCreator{ - Pattern: test.pattern, - Values: values, - FieldValue: test.fieldValue, - } - - m, err := sc.GetValuesForMarkers() - - if test.expectedError == nil { - // fail if expectedError is nil but actual error is not - if !assert.NoError(t, err) { - t.FailNow() - } - // check if all the expected markers and values are present in actual map - for k, v := range test.expectedOutput { - if val, ok := m[k]; ok { - assert.Equal(t, v, val) - } else { - t.FailNow() - } - } - } else { - // if expectedError is not nil, check for correctness of error message - assert.Contains(t, err.Error(), test.expectedError.Error()) - } - }) - } -} diff --git a/kyaml/setters2/types.go b/kyaml/setters2/types.go deleted file mode 100644 index b389835e3..000000000 --- a/kyaml/setters2/types.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "encoding/json" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/openapi" -) - -type CliExtension struct { - Setter *setter `yaml:"setter,omitempty" json:"setter,omitempty"` - Substitution *substitution `yaml:"substitution,omitempty" json:"substitution,omitempty"` -} - -type setter struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - Value string `yaml:"value,omitempty" json:"value,omitempty"` - ListValues []string `yaml:"listValues,omitempty" json:"listValues,omitempty"` - EnumValues map[string]string `yaml:"enumValues,omitempty" json:"enumValues,omitempty"` - Required bool `yaml:"required,omitempty" json:"required,omitempty"` - IsSet bool `yaml:"isSet,omitempty" json:"isSet,omitempty"` -} - -type substitution struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - Pattern string `yaml:"pattern,omitempty" json:"pattern,omitempty"` - Values []substitutionSetterReference `yaml:"values,omitempty" json:"values,omitempty"` -} - -type substitutionSetterReference struct { - Ref string `yaml:"ref,omitempty" json:"ref,omitempty"` - Marker string `yaml:"marker,omitempty" json:"marker,omitempty"` -} - -// K8sCliExtensionKey is the name of the OpenAPI field containing the setter extensions -const K8sCliExtensionKey = "x-k8s-cli" - -// GetExtFromSchema returns the cliExtension openAPI extension if it is present in schema -func GetExtFromSchema(schema *spec.Schema) (*CliExtension, error) { - cep := schema.VendorExtensible.Extensions[K8sCliExtensionKey] - if cep == nil { - return nil, nil - } - b, err := json.Marshal(cep) - if err != nil { - return nil, err - } - val := &CliExtension{} - if err := json.Unmarshal(b, val); err != nil { - return nil, err - } - return val, nil -} - -// getExtFromComment returns the cliExtension openAPI extension if it is present as -// a comment on the field. -func getExtFromComment(schema *openapi.ResourceSchema) (*CliExtension, error) { - if schema == nil { - // no schema found - // TODO(pwittrock): should this be an error if it doesn't resolve? - return nil, nil - } - - // get the cli extension from the openapi (contains setter information) - ext, err := GetExtFromSchema(schema.Schema) - if err != nil { - return nil, errors.Wrap(err) - } - return ext, nil -} diff --git a/kyaml/setters2/util.go b/kyaml/setters2/util.go deleted file mode 100644 index 7b16c9730..000000000 --- a/kyaml/setters2/util.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "strings" - - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/errors" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" -) - -// CheckRequiredSettersSet iterates through all the setter definitions in openAPI -// schema and returns error if any of the setter has required field true and isSet false -func CheckRequiredSettersSet(settersSchema *spec.Schema) error { - for key := range settersSchema.Definitions { - if strings.Contains(key, fieldmeta.SetterDefinitionPrefix) { - val := settersSchema.Definitions[key] - defExt, err := GetExtFromSchema(&val) // parse the extension out of the openAPI - if err != nil { - return errors.Wrap(err) - } - if defExt.Setter != nil && defExt.Setter.Required && !defExt.Setter.IsSet { - return errors.Errorf("setter %s is required but not set, "+ - "please set it to new value and try again", strings.TrimPrefix(key, fieldmeta.SetterDefinitionPrefix)) - } - } - } - return nil -} diff --git a/kyaml/setters2/util_test.go b/kyaml/setters2/util_test.go deleted file mode 100644 index 8b81f50f1..000000000 --- a/kyaml/setters2/util_test.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "sigs.k8s.io/kustomize/kyaml/openapi" -) - -func TestCheckRequiredSettersSet(t *testing.T) { - var tests = []struct { - name string - inputOpenAPIfile string - expectedError bool - }{ - { - name: "no required, no isSet", - inputOpenAPIfile: ` -apiVersion: v1alpha1 -kind: OpenAPIfile -openAPI: - definitions: - io.k8s.cli.setters.gcloud.project.projectNumber: - description: hello world - x-k8s-cli: - setter: - name: gcloud.project.projectNumber - value: "123" - setBy: me - io.k8s.cli.setters.replicas: - description: hello world - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me - `, - expectedError: false, - }, - { - name: "required true, no isSet", - inputOpenAPIfile: ` -apiVersion: v1alpha1 -kind: Example -openAPI: - definitions: - io.k8s.cli.setters.replicas: - description: hello world - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me - required: true - `, - expectedError: true, - }, - { - name: "required true, isSet true", - inputOpenAPIfile: ` -apiVersion: v1alpha1 -kind: Example -openAPI: - definitions: - io.k8s.cli.setters.replicas: - description: hello world - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me - required: true - isSet: true - `, - expectedError: false, - }, - - { - name: "required false, isSet true", - inputOpenAPIfile: ` -apiVersion: v1alpha1 -kind: OpenAPIfile -openAPI: - definitions: - io.k8s.cli.setters.gcloud.project.projectNumber: - description: hello world - x-k8s-cli: - setter: - name: gcloud.project.projectNumber - value: "123" - setBy: me - io.k8s.cli.setters.replicas: - description: hello world - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me - required: false - isSet: true - `, - expectedError: false, - }, - - { - name: "required true, isSet false", - inputOpenAPIfile: ` -apiVersion: v1alpha1 -kind: OpenAPIfile -openAPI: - definitions: - io.k8s.cli.setters.gcloud.project.projectNumber: - description: hello world - x-k8s-cli: - setter: - name: gcloud.project.projectNumber - value: "123" - setBy: me - io.k8s.cli.setters.replicas: - description: hello world - x-k8s-cli: - setter: - name: replicas - value: "3" - setBy: me - required: true - isSet: false - `, - expectedError: true, - }, - } - for i := range tests { - test := tests[i] - t.Run(test.name, func(t *testing.T) { - openapi.ResetOpenAPI() - defer openapi.ResetOpenAPI() - dir := t.TempDir() - err := os.WriteFile(filepath.Join(dir, "Krmfile"), []byte(test.inputOpenAPIfile), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - sc, err := openapi.SchemaFromFile(filepath.Join(dir, "Krmfile")) - if !assert.NoError(t, err) { - t.FailNow() - } - if err != nil { - // do nothing if openAPI file or schema doesn't exist, CheckRequiredSettersSet() - // should not throw any error - fmt.Println("Unable to load schema from file, continuing...") - } - err = CheckRequiredSettersSet(sc) - if test.expectedError && !assert.Error(t, err) { - t.FailNow() - } - if !test.expectedError && !assert.NoError(t, err) { - t.FailNow() - } - }) - } -} diff --git a/kyaml/setters2/walk.go b/kyaml/setters2/walk.go deleted file mode 100644 index 88c71b077..000000000 --- a/kyaml/setters2/walk.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package setters2 - -import ( - "k8s.io/kube-openapi/pkg/validation/spec" - "sigs.k8s.io/kustomize/kyaml/fieldmeta" - "sigs.k8s.io/kustomize/kyaml/openapi" - "sigs.k8s.io/kustomize/kyaml/yaml" -) - -// visitor is implemented by structs which need to walk the configuration. -// visitor is provided to accept to walk configuration -type visitor interface { - // visitScalar is called for each scalar field value on a resource - // node is the scalar field value - // path is the path to the field; path elements are separated by '.' - // oa is the OpenAPI schema for the field - visitScalar(node *yaml.RNode, path string, oa *openapi.ResourceSchema, fieldOA *openapi.ResourceSchema) error - - // visitSequence is called for each sequence field value on a resource - // node is the sequence field value - // path is the path to the field - // oa is the OpenAPI schema for the field - visitSequence(node *yaml.RNode, path string, oa *openapi.ResourceSchema) error - - // visitMapping is called for each Mapping field value on a resource - // node is the mapping field value - // path is the path to the field - // oa is the OpenAPI schema for the field - visitMapping(node *yaml.RNode, path string, oa *openapi.ResourceSchema) error -} - -// accept invokes the appropriate function on v for each field in object -func accept(v visitor, object *yaml.RNode, settersSchema *spec.Schema) error { - // get the OpenAPI for the type if it exists - oa := getSchema(object, nil, "", settersSchema) - return acceptImpl(v, object, "", oa, settersSchema) -} - -// acceptImpl implements accept using recursion -func acceptImpl(v visitor, object *yaml.RNode, p string, oa *openapi.ResourceSchema, settersSchema *spec.Schema) error { - switch object.YNode().Kind { - case yaml.DocumentNode: - // Traverse the child of the document - return accept(v, yaml.NewRNode(object.YNode()), settersSchema) - case yaml.MappingNode: - if err := v.visitMapping(object, p, oa); err != nil { - return err - } - return object.VisitFields(func(node *yaml.MapNode) error { - // get the schema for the field and propagate it - fieldSchema := getSchema(node.Key, oa, node.Key.YNode().Value, settersSchema) - // Traverse each field value - return acceptImpl(v, node.Value, p+"."+node.Key.YNode().Value, fieldSchema, settersSchema) - }) - case yaml.SequenceNode: - // get the schema for the sequence node, use the schema provided if not present - // on the field - if err := v.visitSequence(object, p, oa); err != nil { - return err - } - // get the schema for the elements - schema := getSchema(object, oa, "", settersSchema) - return object.VisitElements(func(node *yaml.RNode) error { - // Traverse each list element - return acceptImpl(v, node, p, schema, settersSchema) - }) - case yaml.ScalarNode: - // Visit the scalar field - fieldSchema := getSchema(object, oa, "", settersSchema) - return v.visitScalar(object, p, oa, fieldSchema) - } - return nil -} - -// getSchema returns OpenAPI schema for an RNode or field of the -// RNode. It will overriding the provide schema with field specific values -// if they are found -// r is the Node to get the Schema for -// s is the provided schema for the field if known -// field is the name of the field -func getSchema(r *yaml.RNode, s *openapi.ResourceSchema, field string, settersSchema *spec.Schema) *openapi.ResourceSchema { - // get the override schema if it exists on the field - fm := fieldmeta.FieldMeta{SettersSchema: settersSchema} - if err := fm.Read(r); err == nil && !fm.IsEmpty() { - // per-field schema, this is fine - if fm.Schema.Ref.String() != "" { - // resolve the reference - s, err := openapi.Resolve(&fm.Schema.Ref, settersSchema) - if err == nil && s != nil { - fm.Schema = *s - } - } - return &openapi.ResourceSchema{Schema: &fm.Schema} - } - - // get the schema for a field of the node if the field is provided - if s != nil && field != "" { - return s.Field(field) - } - - // get the schema for the elements if this is a list - if s != nil && r.YNode().Kind == yaml.SequenceNode { - return s.Elements() - } - - // use the provided schema if present - if s != nil { - return s - } - - if yaml.IsMissingOrNull(r) { - return nil - } - - // lookup the schema for the type - m, _ := r.GetMeta() - if m.Kind == "" || m.APIVersion == "" { - return nil - } - return openapi.SchemaForResourceType(yaml.TypeMeta{Kind: m.Kind, APIVersion: m.APIVersion}) -}