mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
* update go 1.24.6 * fix non-constant format string error * update golang.org/x/tools@v0.36.0 and github.com/golangci/golangci-lint@v1.64.8 to pass execute golangci-lint * add a verpose diff output to prow test * remove pluginator binary version from generated files
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("%s", 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()
|
|
})
|
|
}
|