Files
kustomize/kyaml/setters2/set_test.go

1624 lines
34 KiB
Go

// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package setters2
import (
"io/ioutil"
"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, err := ioutil.TempDir("", "")
assert.NoError(t, err)
defer os.RemoveAll(dir)
err = ioutil.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 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)
})
}
}