diff --git a/kyaml/openapi/openapi.go b/kyaml/openapi/openapi.go index 6401a3360..2eb53f081 100644 --- a/kyaml/openapi/openapi.go +++ b/kyaml/openapi/openapi.go @@ -62,6 +62,8 @@ func SchemaForResourceType(t yaml.TypeMeta) *ResourceSchema { // supplementary OpenAPI definitions. const SupplementaryOpenAPIFieldName = "openAPI" +const Definitions = "definitions" + // AddSchemaFromFile reads the file at path and parses the OpenAPI definitions // from the field "openAPI" func AddSchemaFromFile(path string) error { @@ -70,39 +72,42 @@ func AddSchemaFromFile(path string) error { // DeleteSchemaInFile reads the file at path and removes all the openAPI definitions // present in file from global schema -func DeleteSchemaInFile(path string) error { - b, err := ioutil.ReadFile(path) - if err != nil { - return err - } - - object, err := yaml.Parse(string(b)) - if err != nil { - return err - } - - definitions, err := object.Pipe(yaml.Lookup(SupplementaryOpenAPIFieldName, "definitions")) - if definitions == nil { - return nil - } - if err != nil { - return err - } - - fields, err := definitions.Fields() +func DeleteSchemaInFile(openAPIPath string) error { + fields, err := DefinitionRefs(openAPIPath) if err != nil { return err } for _, field := range fields { - _, ok := globalSchema.schema.Definitions[field] - if ok { - delete(globalSchema.schema.Definitions, field) - } + delete(globalSchema.schema.Definitions, field) } return nil } +// DefinitionRefs returns the list of openAPI definition references present in the +// input openAPIPath +func DefinitionRefs(openAPIPath string) ([]string, error) { + b, err := ioutil.ReadFile(openAPIPath) + if err != nil { + return nil, err + } + + object, err := yaml.Parse(string(b)) + if err != nil { + return nil, err + } + + definitions, err := object.Pipe(yaml.Lookup(SupplementaryOpenAPIFieldName, Definitions)) + if definitions == nil { + return nil, err + } + if err != nil { + return nil, err + } + + return definitions.Fields() +} + // AddSchemaFromFileUsingField reads the file at path and parses the OpenAPI definitions // from the specified field. If field is the empty string, use the whole document as // OpenAPI. diff --git a/kyaml/setters2/settersutil/fieldsetter.go b/kyaml/setters2/settersutil/fieldsetter.go index 6efafed41..79d273b34 100644 --- a/kyaml/setters2/settersutil/fieldsetter.go +++ b/kyaml/setters2/settersutil/fieldsetter.go @@ -6,6 +6,7 @@ package settersutil import ( "io/ioutil" "os" + "path/filepath" "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/openapi" @@ -99,16 +100,24 @@ func (fs FieldSetter) Set() (int, error) { return s.Count, err } -// SetAllSetterDefinitions reads all the Setter Definitions from the OpenAPI -// file and sets all values in the provided directories. -func SetAllSetterDefinitions(openAPIPath string, dirs ...string) error { +// 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(syncOpenAPI bool, openAPIPath string, dirs ...string) error { if err := openapi.AddSchemaFromFile(openAPIPath); err != nil { return err } - - for _, dir := range dirs { + for _, destDir := range dirs { + if syncOpenAPI { + openAPIFileName := filepath.Base(openAPIPath) + err := syncOpenAPIValuesWithSchema(filepath.Join(destDir, openAPIFileName)) + if err != nil { + return err + } + } rw := &kio.LocalPackageReadWriter{ - PackagePath: dir, + PackagePath: destDir, // set output won't include resources from files which //weren't modified. make sure we don't delete them. NoDeleteFiles: true, @@ -127,3 +136,30 @@ func SetAllSetterDefinitions(openAPIPath string, dirs ...string) error { } return nil } + +// syncOpenAPIValuesWithSchema syncs the setter values in global openAPI schema +// with the ones in openAPIPath and writes them back +func syncOpenAPIValuesWithSchema(openAPIPath string) error { + refs, err := openapi.DefinitionRefs(openAPIPath) + if err != nil { + return err + } + for _, ref := range refs { + sch := openapi.Schema().Definitions[ref] + cliExt, err := setters2.GetExtFromSchema(&sch) + if cliExt == nil || cliExt.Setter == nil || err != nil { + // if the ref doesn't exist in global schema or if it is not a setter + // continue, as there might be setters which are not present global schema + continue + } + soa := setters2.SetOpenAPI{ + Name: cliExt.Setter.Name, + Value: cliExt.Setter.Value, + ListValues: cliExt.Setter.ListValues, + } + if err := soa.UpdateFile(openAPIPath); err != nil { + return err + } + } + return nil +} diff --git a/kyaml/setters2/settersutil/fieldsetter_test.go b/kyaml/setters2/settersutil/fieldsetter_test.go index 22de33ece..5933271b4 100644 --- a/kyaml/setters2/settersutil/fieldsetter_test.go +++ b/kyaml/setters2/settersutil/fieldsetter_test.go @@ -6,14 +6,26 @@ package settersutil import ( "io/ioutil" "os" - "strings" + "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestSetAllSetterDefinitions(t *testing.T) { - srcOpenAPIFile := `openAPI: + var tests = []struct { + name string + srcOpenAPIFile string + destFile string + destOpenAPI string + expectedDestFile string + expectedDestOpenAPIFile string + syncSchema bool + }{ + { + name: "set definitions with syncSchema", + syncSchema: true, + srcOpenAPIFile: `openAPI: definitions: io.k8s.cli.setters.namespace: x-k8s-cli: @@ -24,83 +36,163 @@ func TestSetAllSetterDefinitions(t *testing.T) { x-k8s-cli: setter: name: replicas - value: "4"` + value: "4"`, - destFile1 := `apiVersion: apps/v1 + 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"}` + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas-new"}`, - destFile2 := `apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - namespace: some-other-namespace2 # {"$ref": "#/definitions/io.k8s.cli.setters.namespace"} -spec: - replicas: 2 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}` + 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 + expectedDestFile: `apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: project-namespace # {"$ref": "#/definitions/io.k8s.cli.setters.namespace"} spec: - replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}` + replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas-new"} +`, - srcDir, err := ioutil.TempDir("", "") - if !assert.NoError(t, err) { - t.FailNow() - } + expectedDestOpenAPIFile: `openAPI: + definitions: + io.k8s.cli.setters.namespace: + x-k8s-cli: + setter: + name: namespace + value: "project-namespace" + isSet: true + io.k8s.cli.setters.replicas-new: + x-k8s-cli: + setter: + name: replicas-new + value: "3" +`, + }, + { + name: "set values only to resources and not the openAPI", + syncSchema: false, + 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"`, - destDir1, err := ioutil.TempDir("", "") - if !assert.NoError(t, err) { - t.FailNow() - } + 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"}`, - destDir2, err := ioutil.TempDir("", "") - if !assert.NoError(t, err) { - t.FailNow() - } + 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"`, - defer os.RemoveAll(srcDir) - defer os.RemoveAll(destDir1) - defer os.RemoveAll(destDir2) + 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"} +`, - err = ioutil.WriteFile(srcDir+"/OpenAPIFile", []byte(srcOpenAPIFile), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } - err = ioutil.WriteFile(destDir1+"/destFile.yaml", []byte(destFile1), 0600) - if !assert.NoError(t, err) { - t.FailNow() + 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, err := ioutil.TempDir("", "") + if !assert.NoError(t, err) { + t.FailNow() + } - err = ioutil.WriteFile(destDir2+"/destFile.yaml", []byte(destFile2), 0600) - if !assert.NoError(t, err) { - t.FailNow() - } + destDir, err := ioutil.TempDir("", "") + if !assert.NoError(t, err) { + t.FailNow() + } - err = SetAllSetterDefinitions(srcDir+"/OpenAPIFile", destDir1, destDir2) - if !assert.NoError(t, err) { - t.FailNow() - } + defer os.RemoveAll(srcDir) + defer os.RemoveAll(destDir) - actualdestFile1, err := ioutil.ReadFile(destDir1 + "/destFile.yaml") - if !assert.NoError(t, err) { - t.FailNow() - } - if !assert.Equal(t, strings.Trim(string(actualdestFile1), "\n"), expectedDestFile) { - t.FailNow() - } + err = ioutil.WriteFile(filepath.Join(srcDir, "Krmfile"), []byte(test.srcOpenAPIFile), 0600) + if !assert.NoError(t, err) { + t.FailNow() + } + err = ioutil.WriteFile(filepath.Join(destDir, "destFile.yaml"), []byte(test.destFile), 0600) + if !assert.NoError(t, err) { + t.FailNow() + } + err = ioutil.WriteFile(filepath.Join(destDir, "Krmfile"), []byte(test.destOpenAPI), 0600) + if !assert.NoError(t, err) { + t.FailNow() + } - actualdestFile2, err := ioutil.ReadFile(destDir2 + "/destFile.yaml") - if !assert.NoError(t, err) { - t.FailNow() - } - if !assert.Equal(t, strings.Trim(string(actualdestFile2), "\n"), expectedDestFile) { - t.FailNow() + err = SetAllSetterDefinitions(test.syncSchema, filepath.Join(srcDir, "Krmfile"), destDir) + if !assert.NoError(t, err) { + t.FailNow() + } + + actualDestFile1, err := ioutil.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 := ioutil.ReadFile(filepath.Join(destDir, "Krmfile")) + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, test.expectedDestOpenAPIFile, string(actualDestOpenAPIFile1)) { + t.FailNow() + } + }) } }