Files
kustomize/kyaml/openapi/openapi_test.go
yugo kobayashi 87d0629bd1 update go 1.24.6 (#5959)
* 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
2025-08-17 13:05:12 -07:00

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()
})
}