mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-23 23:37:00 +00:00
@@ -55,7 +55,7 @@ func (fm *FieldMeta) Read(n *yaml.RNode) error {
|
||||
}
|
||||
b, err := json.Marshal(fe)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
return json.Unmarshal(b, &fm.Extensions)
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func (fm *FieldMeta) Write(n *yaml.RNode) error {
|
||||
fm.Schema.VendorExtensible.AddExtension("x-kustomize", fm.Extensions)
|
||||
b, err := json.Marshal(fm.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
n.YNode().LineComment = string(b)
|
||||
return nil
|
||||
|
||||
@@ -49,6 +49,11 @@ func AddSchema(s []byte) (*spec.Schema, error) {
|
||||
return parse(s)
|
||||
}
|
||||
|
||||
// ResetOpenAPI resets the openapi data to empty
|
||||
func ResetOpenAPI() {
|
||||
globalSchema = openapiData{}
|
||||
}
|
||||
|
||||
// AddDefinitions adds the definitions to the global schema.
|
||||
func AddDefinitions(definitions spec.Definitions) {
|
||||
// initialize values if they have not yet been set
|
||||
@@ -123,7 +128,7 @@ func GetSchema(s string) (*ResourceSchema, error) {
|
||||
// schema as part of the global schema.
|
||||
// Must be called before the schema is used.
|
||||
func SuppressBuiltInSchemaUse() {
|
||||
globalSchema.noUseBuiltInSchema = false
|
||||
globalSchema.noUseBuiltInSchema = true
|
||||
}
|
||||
|
||||
// Elements returns the Schema for the elements of an array.
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestNoUseBuiltInSchema_AddSchema(t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.config.setters.replicas"}`)
|
||||
if !assert.Greater(t, len(globalSchema.schema.Definitions), 1) {
|
||||
if !assert.Equal(t, len(globalSchema.schema.Definitions), 1) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.NoError(t, err) {
|
||||
|
||||
157
kyaml/setters2/doc.go
Normal file
157
kyaml/setters2/doc.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// 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".
|
||||
package setters2
|
||||
130
kyaml/setters2/example_test.go
Normal file
130
kyaml/setters2/example_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package setters2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// ExampleSet demonstrates using Set to replace the current field value in an object
|
||||
func ExampleSet() {
|
||||
openapi.ResetOpenAPI()
|
||||
|
||||
// OpenAPI definitions with setter extensions on definitions
|
||||
schema := `
|
||||
{
|
||||
"definitions": {
|
||||
"io.k8s.cli.setters.replicas": {
|
||||
"x-k8s-cli": {
|
||||
"setter": {
|
||||
"name": "replicas",
|
||||
"value": "4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
// Resource with field referencing OpenAPI definition
|
||||
deployment := `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
spec:
|
||||
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
|
||||
`
|
||||
_, err := openapi.AddSchema([]byte(schema)) // add the schema definitions
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
object := yaml.MustParse(deployment) // parse the configuration
|
||||
err = object.PipeE(&Set{Name: "replicas"}) // set replicas from the setter
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(object.MustString())
|
||||
|
||||
// Output:
|
||||
// apiVersion: apps/v1
|
||||
// kind: Deployment
|
||||
// metadata:
|
||||
// name: nginx-deployment
|
||||
// spec:
|
||||
// replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
|
||||
}
|
||||
|
||||
// ExampleSet_Substitution demonstrates using Set to substitute a value into the field of
|
||||
// an object. Only part of the field value is modified.
|
||||
func ExampleSet_substitution() {
|
||||
openapi.ResetOpenAPI()
|
||||
|
||||
// set the version setter
|
||||
schema := `
|
||||
{
|
||||
"definitions": {
|
||||
"io.k8s.cli.setters.version": {
|
||||
"x-k8s-cli": {
|
||||
"setter": {
|
||||
"name": "version",
|
||||
"value": "1.8.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"io.k8s.cli.substitutions.image": {
|
||||
"x-k8s-cli": {
|
||||
"substitution": {
|
||||
"name": "image",
|
||||
"pattern": "nginx:VERSION",
|
||||
"values": [
|
||||
{"marker": "VERSION", "ref": "#/definitions/io.k8s.cli.setters.version"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
deployment := `
|
||||
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"}
|
||||
`
|
||||
|
||||
_, err := openapi.AddSchema([]byte(schema)) // add the schema definitions
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
object := yaml.MustParse(deployment) // parse the configuration
|
||||
err = object.PipeE(&Set{Name: "version"}) // set replicas from the setter
|
||||
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
|
||||
// spec:
|
||||
// template:
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: nginx
|
||||
// image: nginx:1.8.1 # {"$ref": "#/definitions/io.k8s.cli.substitutions.image"}
|
||||
}
|
||||
113
kyaml/setters2/set.go
Normal file
113
kyaml/setters2/set.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package setters2
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"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
|
||||
}
|
||||
|
||||
// Filter implements Set as a yaml.Filter
|
||||
func (s *Set) Filter(object *yaml.RNode) (*yaml.RNode, error) {
|
||||
return object, accept(s, object)
|
||||
}
|
||||
|
||||
// visitScalar
|
||||
func (s *Set) visitScalar(object *yaml.RNode, _ string) error {
|
||||
// get the openAPI for this field describing how to apply the setter
|
||||
ext, err := getExtFromComment(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ext == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// perform a direct set of the field if it matches
|
||||
if s.set(object, ext) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// perform a substitution of the field if it matches
|
||||
if sub, err := s.substitute(object, ext); sub || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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) {
|
||||
nameMatch := false
|
||||
|
||||
// check partial setters to see if they contain the setter as part of a
|
||||
// substitution
|
||||
if ext.Substitution == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
p := ext.Substitution.Pattern
|
||||
|
||||
// substitute each setter into the pattern to get the new value
|
||||
for _, v := range ext.Substitution.Values {
|
||||
if v.Ref == "" {
|
||||
return false, errors.Errorf(
|
||||
"missing reference on substitution " + ext.Substitution.Name)
|
||||
}
|
||||
ref, err := spec.NewRef(v.Ref)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err)
|
||||
}
|
||||
setter, err := openapi.Resolve(&ref) // resolve the setter to its openAPI def
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err)
|
||||
}
|
||||
subSetter, err := getExtFromSchema(setter) // parse the extension out of the openAPI
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err)
|
||||
}
|
||||
// substitute the setters current value into the substitution pattern
|
||||
p = strings.ReplaceAll(p, v.Marker, subSetter.Setter.Value)
|
||||
|
||||
if subSetter.Setter.Name == s.Name {
|
||||
// the substitution depends on the specified setter
|
||||
nameMatch = true
|
||||
}
|
||||
}
|
||||
if !nameMatch {
|
||||
// doesn't depend on the setter, don't modify its value
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// TODO(pwittrock): validate the field value
|
||||
|
||||
field.YNode().Value = p
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// set applies the value from ext to field if its name matches s.Name
|
||||
func (s *Set) set(field *yaml.RNode, ext *cliExtension) bool {
|
||||
// check full setter
|
||||
if ext.Setter == nil || ext.Setter.Name != s.Name {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(pwittrock): validate the field value
|
||||
|
||||
// this has a full setter, set its value
|
||||
field.YNode().Value = ext.Setter.Value
|
||||
return true
|
||||
}
|
||||
440
kyaml/setters2/set_test.go
Normal file
440
kyaml/setters2/set_test.go
Normal file
@@ -0,0 +1,440 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package setters2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestSet_Filter(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name 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-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-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"}
|
||||
`,
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// reset the openAPI afterward
|
||||
defer openapi.ResetOpenAPI()
|
||||
initSchema(t, test.openapi)
|
||||
|
||||
// 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}
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// initSchema initializes the openAPI with the definitions from s
|
||||
func initSchema(t *testing.T, s string) {
|
||||
// parse out the schema from the input openAPI
|
||||
y, err := yaml.Parse(s)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
// get the field containing the openAPI
|
||||
f := y.Field("openAPI")
|
||||
if !assert.NotNil(t, f) {
|
||||
t.FailNow()
|
||||
}
|
||||
defs, err := f.Value.String()
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// convert the yaml openAPI to an interface{}
|
||||
// which can be marshalled into json
|
||||
var o interface{}
|
||||
err = yaml.Unmarshal([]byte(defs), &o)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// convert the interface{} into a json string
|
||||
j, err := json.Marshal(o)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// reset the openAPI to clear existing definitions
|
||||
openapi.ResetOpenAPI()
|
||||
|
||||
// add the json schema to the global schema
|
||||
_, err = openapi.AddSchema(j)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
87
kyaml/setters2/types.go
Normal file
87
kyaml/setters2/types.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package setters2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/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"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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(object *yaml.RNode) (*cliExtension, error) {
|
||||
// TODO(pwittrock): also use path to the field to get openapi, not just comments
|
||||
// parse comment containing the extended openapi for this field
|
||||
fm := fieldmeta.FieldMeta{}
|
||||
if err := fm.Read(object); err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
if fm.Schema.Ref.String() == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// resolve the comment reference to the extended openapi definitions
|
||||
r, err := openapi.Resolve(&fm.Schema.Ref)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
if r == 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(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err)
|
||||
}
|
||||
return ext, nil
|
||||
}
|
||||
45
kyaml/setters2/walk.go
Normal file
45
kyaml/setters2/walk.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package setters2
|
||||
|
||||
import (
|
||||
"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 '.'
|
||||
visitScalar(node *yaml.RNode, path string) error
|
||||
}
|
||||
|
||||
// accept invokes the appropriate function on v for each field in object
|
||||
func accept(v visitor, object *yaml.RNode) error {
|
||||
return acceptImpl(v, object, "")
|
||||
}
|
||||
|
||||
// acceptImpl implements accept using recursion
|
||||
func acceptImpl(v visitor, object *yaml.RNode, p string) error {
|
||||
switch object.YNode().Kind {
|
||||
case yaml.DocumentNode:
|
||||
// Traverse the child of the document
|
||||
return accept(v, yaml.NewRNode(object.YNode()))
|
||||
case yaml.MappingNode:
|
||||
return object.VisitFields(func(node *yaml.MapNode) error {
|
||||
// Traverse each field value
|
||||
return acceptImpl(v, node.Value, p+"."+node.Key.YNode().Value)
|
||||
})
|
||||
case yaml.SequenceNode:
|
||||
return object.VisitElements(func(node *yaml.RNode) error {
|
||||
// Traverse each list element
|
||||
return acceptImpl(v, node, p)
|
||||
})
|
||||
case yaml.ScalarNode:
|
||||
// Visit the scalar field
|
||||
return v.visitScalar(object, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user