mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-18 10:12:08 +00:00
Assert keeps going after failure, but require immediately fails the tests, making it easier to find the output related to the test failure, rather than having to comb through a bunch of subsequent assertion failures. For equality tests, we may or may not want to continue, but for error checks we almost always want to immediately fail the test. Exceptions can be changed as-needed.
396 lines
9.0 KiB
Go
396 lines
9.0 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package openapi
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
)
|
|
|
|
func TestAddSchema(t *testing.T) {
|
|
// reset package vars
|
|
globalSchema = openapiData{}
|
|
|
|
err := AddSchema(additionalSchema)
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.config.setters.replicas"}`, Schema())
|
|
if !assert.Greater(t, len(globalSchema.schema.Definitions), 200) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
assert.Equal(t, `map[x-kustomize:map[setBy:Jane setter:map[name:replicas value:5]]]`,
|
|
fmt.Sprintf("%v", s.Schema.Extensions))
|
|
}
|
|
|
|
var additionalSchema = []byte(`
|
|
{
|
|
"definitions": {
|
|
"io.k8s.config.setters.replicas": {
|
|
"description": "replicas description.",
|
|
"type": "integer",
|
|
"x-kustomize": {"setBy":"Jane","setter": {"name":"replicas","value":"5"}}
|
|
}
|
|
},
|
|
"invalid": "field"
|
|
}
|
|
`)
|
|
|
|
func TestSchemaForResourceType(t *testing.T) {
|
|
// reset package vars
|
|
globalSchema = openapiData{}
|
|
|
|
s := SchemaForResourceType(
|
|
yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
|
|
if !assert.NotNil(t, s) {
|
|
t.FailNow()
|
|
}
|
|
|
|
f := s.Field("spec")
|
|
if !assert.NotNil(t, f) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.Equal(t, "DeploymentSpec is the specification of the desired behavior of the Deployment.",
|
|
f.Schema.Description) {
|
|
t.FailNow()
|
|
}
|
|
|
|
replicas := f.Field("replicas")
|
|
if !assert.NotNil(t, replicas) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.Equal(t, "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.",
|
|
replicas.Schema.Description) {
|
|
t.FailNow()
|
|
}
|
|
|
|
temp := f.Field("template")
|
|
if !assert.NotNil(t, temp) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.Equal(t, "PodTemplateSpec describes the data a pod should have when created from a template",
|
|
temp.Schema.Description) {
|
|
t.FailNow()
|
|
}
|
|
|
|
containers := temp.Field("spec").Field("containers").Elements()
|
|
if !assert.NotNil(t, containers) {
|
|
t.FailNow()
|
|
}
|
|
|
|
targetPort := containers.Field("ports").Elements().Field("containerPort")
|
|
if !assert.NotNil(t, targetPort) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.Equal(t, "Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536.",
|
|
targetPort.Schema.Description) {
|
|
t.FailNow()
|
|
}
|
|
|
|
arg := containers.Field("args").Elements()
|
|
if !assert.NotNil(t, arg) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.Equal(t, "string", arg.Schema.Type[0]) {
|
|
t.FailNow()
|
|
}
|
|
}
|
|
|
|
func TestSchemaFromFile(t *testing.T) {
|
|
ResetOpenAPI()
|
|
inputyaml := `
|
|
openAPI:
|
|
definitions:
|
|
io.k8s.cli.setters.image-name:
|
|
x-k8s-cli:
|
|
setter:
|
|
name: image-name
|
|
value: "nginx"
|
|
`
|
|
f, err := os.CreateTemp("", "openapi-")
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.NoError(t, os.WriteFile(f.Name(), []byte(inputyaml), 0o600)) {
|
|
t.FailNow()
|
|
}
|
|
|
|
sc, err := SchemaFromFile(f.Name())
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
|
|
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.cli.setters.image-name"}`, sc)
|
|
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.Greater(t, len(sc.Definitions), 0) {
|
|
t.FailNow()
|
|
}
|
|
assert.Equal(t, `map[x-k8s-cli:map[setter:map[name:image-name value:nginx]]]`,
|
|
fmt.Sprintf("%v", s.Schema.Extensions))
|
|
}
|
|
|
|
func TestPopulateDefsInOpenAPI_Substitution(t *testing.T) {
|
|
ResetOpenAPI()
|
|
inputyaml := `
|
|
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"
|
|
`
|
|
|
|
f, err := os.CreateTemp("", "openapi-")
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.NoError(t, os.WriteFile(f.Name(), []byte(inputyaml), 0o600)) {
|
|
t.FailNow()
|
|
}
|
|
|
|
sc, err := SchemaFromFile(f.Name())
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
|
|
s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.cli.substitutions.image"}`, sc)
|
|
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.Equal(t, len(sc.Definitions), 3) {
|
|
t.FailNow()
|
|
}
|
|
|
|
assert.Equal(t,
|
|
`map[x-k8s-cli:map[substitution:map[name:image pattern:IMAGE_NAME:IMAGE_TAG`+
|
|
` values:[map[marker:IMAGE_NAME ref:#/definitions/io.k8s.cli.setters.image-name]`+
|
|
` map[marker:IMAGE_TAG ref:#/definitions/io.k8s.cli.setters.image-tag]]]]]`,
|
|
fmt.Sprintf("%v", s.Schema.Extensions))
|
|
}
|
|
|
|
func TestAddSchemaFromFile_empty(t *testing.T) {
|
|
ResetOpenAPI()
|
|
inputyaml := `
|
|
kind: Example
|
|
`
|
|
|
|
f, err := os.CreateTemp("", "openapi-")
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
if !assert.NoError(t, os.WriteFile(f.Name(), []byte(inputyaml), 0o600)) {
|
|
t.FailNow()
|
|
}
|
|
|
|
sc, err := SchemaFromFile(f.Name())
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
|
|
if !assert.Nil(t, sc) {
|
|
t.FailNow()
|
|
}
|
|
}
|
|
|
|
func TestIsNamespaceScoped_builtin(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
typeMeta yaml.TypeMeta
|
|
expectIsFound bool
|
|
expectIsNamespaced bool
|
|
}{
|
|
{
|
|
name: "namespacescoped resource",
|
|
typeMeta: yaml.TypeMeta{
|
|
APIVersion: "apps/v1",
|
|
Kind: "Deployment",
|
|
},
|
|
expectIsFound: true,
|
|
expectIsNamespaced: true,
|
|
},
|
|
{
|
|
name: "clusterscoped resource",
|
|
typeMeta: yaml.TypeMeta{
|
|
APIVersion: "v1",
|
|
Kind: "Namespace",
|
|
},
|
|
expectIsFound: true,
|
|
expectIsNamespaced: false,
|
|
},
|
|
{
|
|
name: "unknown resource",
|
|
typeMeta: yaml.TypeMeta{
|
|
APIVersion: "custom.io/v1",
|
|
Kind: "Custom",
|
|
},
|
|
expectIsFound: false,
|
|
},
|
|
}
|
|
|
|
for i := range testCases {
|
|
test := testCases[i]
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ResetOpenAPI()
|
|
isNamespaceable, isFound := IsNamespaceScoped(test.typeMeta)
|
|
|
|
if !test.expectIsFound {
|
|
assert.False(t, isFound)
|
|
return
|
|
}
|
|
assert.True(t, isFound)
|
|
assert.Equal(t, test.expectIsNamespaced, isNamespaceable)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsNamespaceScopedPrecompute checks that the precomputed result meets the actual result
|
|
func TestIsNamespaceScopedPrecompute(t *testing.T) {
|
|
initSchema()
|
|
if diff := cmp.Diff(globalSchema.namespaceabilityByResourceType, precomputedIsNamespaceScoped); diff != "" {
|
|
t.Fatalf(diff)
|
|
}
|
|
}
|
|
|
|
func TestIsNamespaceScoped_custom(t *testing.T) {
|
|
SuppressBuiltInSchemaUse()
|
|
err := AddSchema([]byte(`
|
|
{
|
|
"definitions": {},
|
|
"paths": {
|
|
"/apis/custom.io/v1/namespaces/{namespace}/customs/{name}": {
|
|
"get": {
|
|
"x-kubernetes-action": "get",
|
|
"x-kubernetes-group-version-kind": {
|
|
"group": "custom.io",
|
|
"kind": "Custom",
|
|
"version": "v1"
|
|
}
|
|
}
|
|
},
|
|
"/apis/custom.io/v1/clustercustoms": {
|
|
"get": {
|
|
"x-kubernetes-action": "get",
|
|
"x-kubernetes-group-version-kind": {
|
|
"group": "custom.io",
|
|
"kind": "ClusterCustom",
|
|
"version": "v1"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`))
|
|
require.NoError(t, err)
|
|
|
|
isNamespaceable, isFound := IsNamespaceScoped(yaml.TypeMeta{
|
|
APIVersion: "custom.io/v1",
|
|
Kind: "ClusterCustom",
|
|
})
|
|
assert.True(t, isFound)
|
|
assert.False(t, isNamespaceable)
|
|
|
|
isNamespaceable, isFound = IsNamespaceScoped(yaml.TypeMeta{
|
|
APIVersion: "custom.io/v1",
|
|
Kind: "Custom",
|
|
})
|
|
assert.True(t, isFound)
|
|
assert.True(t, isNamespaceable)
|
|
}
|
|
|
|
func TestCanSetAndResetSchemaConcurrently(t *testing.T) {
|
|
t.Run("SetSchema doesn't cause a data race when called concurrently", func(t *testing.T) {
|
|
set := func(wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
err := SetSchema(
|
|
map[string]string{
|
|
"/apis/custom.io/v1": "true",
|
|
},
|
|
[]byte(`
|
|
{
|
|
"definitions": {},
|
|
"paths": {
|
|
"/apis/custom.io/v1/namespaces/{namespace}/customs/{name}": {
|
|
"get": {
|
|
"x-kubernetes-action": "get",
|
|
"x-kubernetes-group-version-kind": {
|
|
"group": "custom.io",
|
|
"kind": "Custom",
|
|
"version": "v1"
|
|
}
|
|
}
|
|
},
|
|
"/apis/custom.io/v1/clustercustoms": {
|
|
"get": {
|
|
"x-kubernetes-action": "get",
|
|
"x-kubernetes-group-version-kind": {
|
|
"group": "custom.io",
|
|
"kind": "ClusterCustom",
|
|
"version": "v1"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`),
|
|
true,
|
|
)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
require.NotPanics(t, func() {
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(1)
|
|
go set(&wg)
|
|
}
|
|
})
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ResetOpenAPI doesn't cause a data race when called concurrently", func(t *testing.T) {
|
|
reset := func(wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
ResetOpenAPI()
|
|
}
|
|
var wg sync.WaitGroup
|
|
require.NotPanics(t, func() {
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(1)
|
|
go reset(&wg)
|
|
}
|
|
})
|
|
wg.Wait()
|
|
})
|
|
}
|