mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-12 01:14:22 +00:00
Merge pull request #2035 from pwittrock/openapi
Introduce OpenAPI kyaml libraries
This commit is contained in:
@@ -9,7 +9,6 @@ require (
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
k8s.io/apimachinery v0.17.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.0.0
|
||||
)
|
||||
|
||||
@@ -101,6 +101,8 @@ github.com/posener/complete/v2 v2.0.1-alpha.12/go.mod h1://JlL91cS2JV7rOl6LVHrRq
|
||||
github.com/posener/script v1.0.4 h1:nSuXW5ZdmFnQIueLB2s0qvs4oNsUloM1Zydzh75v42w=
|
||||
github.com/posener/script v1.0.4/go.mod h1:Rg3ijooqulo05aGLyGsHoLmIOUzHUVK19WVgrYBPU/E=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
|
||||
@@ -68,6 +68,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
@@ -213,6 +214,7 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
@@ -331,6 +333,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
|
||||
@@ -59,6 +59,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
|
||||
@@ -217,6 +218,7 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nL
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright 2019 The Kubernetes Authors.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
.PHONY: generate license fix vet fmt test lint tidy
|
||||
.PHONY: generate license fix vet fmt test lint tidy openapi
|
||||
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
|
||||
@@ -32,3 +32,7 @@ test:
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
openapi:
|
||||
(which $(GOPATH)/bin/go-bindata || go get -v github.com/go-bindata/go-bindata)
|
||||
go-bindata --pkg openapi -o openapi/swagger.go openapi/swagger.json
|
||||
|
||||
@@ -6,11 +6,13 @@ package copyutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"sigs.k8s.io/kustomize/kyaml/sets"
|
||||
)
|
||||
|
||||
@@ -119,6 +121,9 @@ func Diff(sourceDir, destDir string) (sets.String, error) {
|
||||
return diff, err
|
||||
}
|
||||
if !bytes.Equal(b1, b2) {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(string(b1), string(b2), false)
|
||||
fmt.Println(dmp.DiffPrettyText(diffs))
|
||||
diff.Insert(f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/go-openapi/spec v0.19.5
|
||||
github.com/sergi/go-diff v1.1.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d
|
||||
)
|
||||
|
||||
@@ -27,6 +27,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
@@ -45,6 +47,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
@@ -54,7 +55,9 @@ func FormatFileOrDirectory(path string) error {
|
||||
}.Execute()
|
||||
}
|
||||
|
||||
type FormatFilter struct{}
|
||||
type FormatFilter struct {
|
||||
Process func(n *yaml.Node) error
|
||||
}
|
||||
|
||||
var _ kio.Filter = FormatFilter{}
|
||||
|
||||
@@ -75,7 +78,9 @@ func (f FormatFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
continue
|
||||
}
|
||||
kind, apiVersion := kindNode.YNode().Value, apiVersionNode.YNode().Value
|
||||
err = (&formatter{apiVersion: apiVersion, kind: kind}).fmtNode(slice[i].YNode(), "")
|
||||
s := openapi.SchemaForResourceType(yaml.TypeMeta{APIVersion: apiVersion, Kind: kind})
|
||||
err = (&formatter{apiVersion: apiVersion, kind: kind, process: f.Process}).
|
||||
fmtNode(slice[i].YNode(), "", s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -86,10 +91,18 @@ func (f FormatFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
type formatter struct {
|
||||
apiVersion string
|
||||
kind string
|
||||
process func(n *yaml.Node) error
|
||||
}
|
||||
|
||||
// fmtNode recursively formats the Document Contents.
|
||||
func (f *formatter) fmtNode(n *yaml.Node, path string) error {
|
||||
// See: https://godoc.org/gopkg.in/yaml.v3#Node
|
||||
func (f *formatter) fmtNode(n *yaml.Node, path string, schema *openapi.ResourceSchema) error {
|
||||
if n.Kind == yaml.ScalarNode && schema != nil && schema.Schema != nil {
|
||||
// ensure values that are interpreted as non-string values (e.g. "true")
|
||||
// are properly quoted
|
||||
yaml.FormatNonStringStyle(n, *schema.Schema)
|
||||
}
|
||||
|
||||
// sort the order of mapping fields
|
||||
if n.Kind == yaml.MappingNode {
|
||||
sort.Sort(sortedMapContents(*n))
|
||||
@@ -104,12 +117,43 @@ func (f *formatter) fmtNode(n *yaml.Node, path string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// format the Content
|
||||
for i := range n.Content {
|
||||
p := path
|
||||
if n.Kind == yaml.MappingNode && i%2 == 1 {
|
||||
p = fmt.Sprintf("%s.%s", path, n.Content[i-1].Value)
|
||||
// MappingNode are structured as having their fields as Content,
|
||||
// with the field-key and field-value alternating. e.g. Even elements
|
||||
// are the keys and odd elements are the values
|
||||
isFieldKey := n.Kind == yaml.MappingNode && i%2 == 0
|
||||
isFieldValue := n.Kind == yaml.MappingNode && i%2 == 1
|
||||
isElement := n.Kind == yaml.SequenceNode
|
||||
|
||||
// run the process callback on the node if it has been set
|
||||
// don't process keys: their format should be fixed
|
||||
if f.process != nil && !isFieldKey {
|
||||
if err := f.process(n.Content[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := f.fmtNode(n.Content[i], p)
|
||||
|
||||
// get the schema for this Node
|
||||
p := path
|
||||
var s *openapi.ResourceSchema
|
||||
switch {
|
||||
case isFieldValue:
|
||||
// if the node is a field, lookup the schema using the field name
|
||||
p = fmt.Sprintf("%s.%s", path, n.Content[i-1].Value)
|
||||
if schema != nil {
|
||||
s = schema.Field(n.Content[i-1].Value)
|
||||
}
|
||||
case isElement:
|
||||
// if the node is a list element, lookup the schema for the array items
|
||||
if schema != nil {
|
||||
s = schema.Elements()
|
||||
}
|
||||
}
|
||||
|
||||
// format the node using the schema
|
||||
err := f.fmtNode(n.Content[i], p, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,6 +187,7 @@ func (s sortedMapContents) Swap(i, j int) {
|
||||
s.Content[iFieldValueIndex], s.Content[jFieldValueIndex] = s.
|
||||
Content[jFieldValueIndex], s.Content[iFieldValueIndex]
|
||||
}
|
||||
|
||||
func (s sortedMapContents) Less(i, j int) bool {
|
||||
iFieldNameIndex := i * 2
|
||||
jFieldNameIndex := j * 2
|
||||
|
||||
@@ -13,10 +13,195 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
. "sigs.k8s.io/kustomize/kyaml/kio/filters"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/filters/testyaml"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestFormatInput_FixYaml1_1Compatibility(t *testing.T) {
|
||||
y := `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
labels:
|
||||
foo: on
|
||||
foo2: hello1
|
||||
annotations:
|
||||
bar: 1
|
||||
bar2: hello2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.0.0
|
||||
args:
|
||||
- on
|
||||
- 1
|
||||
- hello
|
||||
ports:
|
||||
- name: http
|
||||
targetPort: 80
|
||||
containerPort: 80
|
||||
`
|
||||
|
||||
// keep the style on values that parse as non-string types
|
||||
expected := `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
labels:
|
||||
foo: "on"
|
||||
foo2: hello1
|
||||
annotations:
|
||||
bar: "1"
|
||||
bar2: hello2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.0.0
|
||||
args:
|
||||
- "on"
|
||||
- "1"
|
||||
- hello
|
||||
ports:
|
||||
- name: http
|
||||
targetPort: 80
|
||||
containerPort: 80
|
||||
`
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: strings.NewReader(y)}},
|
||||
Filters: []kio.Filter{FormatFilter{}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
|
||||
}.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, buff.String())
|
||||
}
|
||||
|
||||
func TestFormatInput_PostprocessStyle(t *testing.T) {
|
||||
y := `
|
||||
apiVersion: v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: foo
|
||||
spec:
|
||||
notBoolean: "true"
|
||||
notBoolean2: "on"
|
||||
isBoolean: on
|
||||
isBoolean2: true
|
||||
notInt: "12345"
|
||||
isInt: 12345
|
||||
isString1: hello world
|
||||
isString2: "hello world"
|
||||
`
|
||||
|
||||
// keep the style on values that parse as non-string types
|
||||
expected := `apiVersion: v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: foo
|
||||
spec:
|
||||
isBoolean: on
|
||||
isBoolean2: true
|
||||
isInt: 12345
|
||||
isString1: hello world
|
||||
isString2: hello world
|
||||
notBoolean: "true"
|
||||
notBoolean2: "on"
|
||||
notInt: "12345"
|
||||
`
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: strings.NewReader(y)}},
|
||||
Filters: []kio.Filter{FormatFilter{Process: func(n *yaml.Node) error {
|
||||
if yaml.IsYaml1_1NonString(n) {
|
||||
// don't change these styles, they are important for backwards compatibility
|
||||
// e.g. "on" must remain quoted, on must remain unquoted
|
||||
return nil
|
||||
}
|
||||
// style does not have semantic meaning
|
||||
n.Style = 0
|
||||
return nil
|
||||
}}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
|
||||
}.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, buff.String())
|
||||
|
||||
y = `
|
||||
apiVersion: v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: 'foo'
|
||||
spec:
|
||||
notBoolean: "true"
|
||||
notBoolean2: "on"
|
||||
notBoolean3: y is yes
|
||||
isBoolean: on
|
||||
isBoolean2: true
|
||||
isBoolean3: y
|
||||
notInt2: 1234 five
|
||||
notInt3: one 2345
|
||||
notInt: "12345"
|
||||
isInt1: 12345
|
||||
isInt2: -12345
|
||||
isFloat1: 1.1234
|
||||
isFloat2: 1.1234
|
||||
isString1: hello world
|
||||
isString2: "hello world"
|
||||
isString3: 'hello world'
|
||||
`
|
||||
|
||||
// keep the style on values that parse as non-string types
|
||||
expected = `apiVersion: 'v1'
|
||||
kind: 'Foo'
|
||||
metadata:
|
||||
name: 'foo'
|
||||
spec:
|
||||
isBoolean: on
|
||||
isBoolean2: true
|
||||
isBoolean3: y
|
||||
isFloat1: 1.1234
|
||||
isFloat2: 1.1234
|
||||
isInt1: 12345
|
||||
isInt2: -12345
|
||||
isString1: 'hello world'
|
||||
isString2: 'hello world'
|
||||
isString3: 'hello world'
|
||||
notBoolean: "true"
|
||||
notBoolean2: "on"
|
||||
notBoolean3: 'y is yes'
|
||||
notInt: "12345"
|
||||
notInt2: '1234 five'
|
||||
notInt3: 'one 2345'
|
||||
`
|
||||
|
||||
buff = &bytes.Buffer{}
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: strings.NewReader(y)}},
|
||||
Filters: []kio.Filter{FormatFilter{Process: func(n *yaml.Node) error {
|
||||
if yaml.IsYaml1_1NonString(n) {
|
||||
// don't change these styles, they are important for backwards compatibility
|
||||
// e.g. "on" must remain quoted, on must remain unquoted
|
||||
return nil
|
||||
}
|
||||
// style does not have semantic meaning
|
||||
n.Style = yaml.SingleQuotedStyle
|
||||
return nil
|
||||
}}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
|
||||
}.Execute()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, buff.String())
|
||||
}
|
||||
|
||||
func TestFormatInput_Style(t *testing.T) {
|
||||
y := `
|
||||
apiVersion: v1
|
||||
|
||||
75
kyaml/openapi/example_test.go
Normal file
75
kyaml/openapi/example_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openapi_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
s := openapi.SchemaForResourceType(yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
|
||||
|
||||
f := s.Lookup("spec", "replicas")
|
||||
fmt.Println(f.Schema.Description[:70] + "...")
|
||||
fmt.Println(f.Schema.Type)
|
||||
|
||||
// Output:
|
||||
// Number of desired pods. This is a pointer to distinguish between expli...
|
||||
// [integer]
|
||||
}
|
||||
|
||||
func Example_arrayMerge() {
|
||||
s := openapi.SchemaForResourceType(yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
|
||||
|
||||
f := s.Lookup("spec", "template", "spec", "containers")
|
||||
fmt.Println(f.Schema.Description[:70] + "...")
|
||||
fmt.Println(f.Schema.Type)
|
||||
fmt.Println(f.PatchStrategyAndKey()) // merge patch strategy on name
|
||||
|
||||
// Output:
|
||||
// List of containers belonging to the pod. Containers cannot currently b...
|
||||
// [array]
|
||||
// merge name
|
||||
}
|
||||
|
||||
func Example_arrayReplace() {
|
||||
s := openapi.SchemaForResourceType(yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
|
||||
|
||||
f := s.Lookup("spec", "template", "spec", "containers", openapi.Elements, "args")
|
||||
fmt.Println(f.Schema.Description[:70] + "...")
|
||||
fmt.Println(f.Schema.Type)
|
||||
fmt.Println(f.PatchStrategyAndKey()) // no patch strategy or merge key
|
||||
|
||||
// Output:
|
||||
// Arguments to the entrypoint. The docker image's CMD is used if this is...
|
||||
// [array]
|
||||
}
|
||||
|
||||
func Example_arrayElement() {
|
||||
s := openapi.SchemaForResourceType(yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
|
||||
|
||||
f := s.Lookup("spec", "template", "spec", "containers",
|
||||
openapi.Elements, "ports", openapi.Elements, "containerPort")
|
||||
fmt.Println(f.Schema.Description[:70] + "...")
|
||||
fmt.Println(f.Schema.Type)
|
||||
|
||||
// Output:
|
||||
// Number of port to expose on the pod's IP address. This must be a valid...
|
||||
// [integer]
|
||||
}
|
||||
|
||||
func Example_map() {
|
||||
s := openapi.SchemaForResourceType(yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
|
||||
|
||||
f := s.Lookup("metadata", "labels")
|
||||
fmt.Println(f.Schema.Description[:70] + "...")
|
||||
fmt.Println(f.Schema.Type)
|
||||
|
||||
// Output:
|
||||
// Map of string keys and values that can be used to organize and categor...
|
||||
// [object]
|
||||
}
|
||||
200
kyaml/openapi/openapi.go
Normal file
200
kyaml/openapi/openapi.go
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var setup sync.Once
|
||||
var schema spec.Schema
|
||||
var schemaByResourceType map[yaml.TypeMeta]*spec.Schema
|
||||
|
||||
// ResourceSchema wraps the OpenAPI Schema.
|
||||
type ResourceSchema struct {
|
||||
// Schema is the OpenAPI schema for a Resource or field
|
||||
Schema *spec.Schema
|
||||
}
|
||||
|
||||
// SchemaForResourceType returns the Schema for the given Resource
|
||||
// TODO(pwittrock): create a version of this function that will return a schema
|
||||
// which can be used for duck-typed Resources -- e.g. contains common fields such
|
||||
// as metadata, replicas and spec.template.spec
|
||||
func SchemaForResourceType(t yaml.TypeMeta) *ResourceSchema {
|
||||
initSchema()
|
||||
rs, found := schemaByResourceType[t]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
return &ResourceSchema{Schema: rs}
|
||||
}
|
||||
|
||||
// Elements returns the Schema for the elements of an array.
|
||||
func (r *ResourceSchema) Elements() *ResourceSchema {
|
||||
// load the schema from swagger.json
|
||||
initSchema()
|
||||
|
||||
if len(r.Schema.Type) != 1 || r.Schema.Type[0] != "array" {
|
||||
// either not an array, or array has multiple types
|
||||
return nil
|
||||
}
|
||||
s := *r.Schema.Items.Schema
|
||||
for s.Ref.String() != "" {
|
||||
sc, e := spec.ResolveRef(rootSchema(), &s.Ref)
|
||||
if e != nil {
|
||||
return nil
|
||||
}
|
||||
s = *sc
|
||||
}
|
||||
return &ResourceSchema{Schema: &s}
|
||||
}
|
||||
|
||||
const Elements = "[]"
|
||||
|
||||
// Lookup calls either Field or Elements for each item in the path.
|
||||
// If the path item is "[]", then Elements is called, otherwise
|
||||
// Field is called.
|
||||
// If any Field or Elements call returns nil, then Lookup returns
|
||||
// nil immediately.
|
||||
func (r *ResourceSchema) Lookup(path ...string) *ResourceSchema {
|
||||
s := r
|
||||
for _, p := range path {
|
||||
if s == nil {
|
||||
break
|
||||
}
|
||||
if p == Elements {
|
||||
s = s.Elements()
|
||||
continue
|
||||
}
|
||||
s = s.Field(p)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Field returns the Schema for a field.
|
||||
func (r *ResourceSchema) Field(field string) *ResourceSchema {
|
||||
// load the schema from swagger.json
|
||||
initSchema()
|
||||
|
||||
// locate the Schema
|
||||
s, found := r.Schema.Properties[field]
|
||||
switch {
|
||||
case found:
|
||||
// no-op, continue with s as the schema
|
||||
case r.Schema.AdditionalProperties != nil && r.Schema.AdditionalProperties.Schema != nil:
|
||||
// map field type -- use Schema of the value
|
||||
// (the key doesn't matter, they all have the same value type)
|
||||
s = *r.Schema.AdditionalProperties.Schema
|
||||
default:
|
||||
// no Schema found from either swagger.json or line comments
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolve the reference to the Schema if the Schema has one
|
||||
for s.Ref.String() != "" {
|
||||
sc, e := spec.ResolveRef(rootSchema(), &s.Ref)
|
||||
if e != nil {
|
||||
return nil
|
||||
}
|
||||
s = *sc
|
||||
}
|
||||
|
||||
// return the merged Schema
|
||||
return &ResourceSchema{Schema: &s}
|
||||
}
|
||||
|
||||
// PatchStrategyAndKey returns the patch strategy and merge key extensions
|
||||
func (r *ResourceSchema) PatchStrategyAndKey() (string, string) {
|
||||
ps, found := r.Schema.Extensions[kubernetesPatchStrategyExtensionKey]
|
||||
if !found {
|
||||
// merge key and patch strategy must appear together
|
||||
return "", ""
|
||||
}
|
||||
|
||||
mk, found := r.Schema.Extensions[kubernetesMergeKeyExtensionKey]
|
||||
if !found {
|
||||
// merge key and patch strategy must appear together
|
||||
return "", ""
|
||||
}
|
||||
return ps.(string), mk.(string)
|
||||
}
|
||||
|
||||
const (
|
||||
// openAPIAssetName is the name of the asset containing the statically compiled in
|
||||
// OpenAPI definitions for Kubernetes built-in types
|
||||
openAPIAssetName = "openapi/swagger.json"
|
||||
|
||||
// kubernetesGVKExtensionKey is the key to lookup the kubernetes group version kind extension
|
||||
// -- the extension is an array of objects containing a gvk
|
||||
kubernetesGVKExtensionKey = "x-kubernetes-group-version-kind"
|
||||
// kubernetesMergeKeyExtensionKey is the key to lookup the kubernetes merge key extension
|
||||
// -- the extension is a string
|
||||
kubernetesMergeKeyExtensionKey = "x-kubernetes-patch-merge-key"
|
||||
// kubernetesPatchStrategyExtensionKey is the key to lookup the kubernetes patch strategy
|
||||
// extension -- the extension is a string
|
||||
kubernetesPatchStrategyExtensionKey = "x-kubernetes-patch-strategy"
|
||||
|
||||
// groupKey is the key to lookup the group from the GVK extension
|
||||
groupKey = "group"
|
||||
// versionKey is the key to lookup the version from the GVK extension
|
||||
versionKey = "version"
|
||||
// kindKey is the the to lookup the kind from the GVK extension
|
||||
kindKey = "kind"
|
||||
)
|
||||
|
||||
// initSchema parses the json schema
|
||||
func initSchema() {
|
||||
setup.Do(func() {
|
||||
// initialize the map
|
||||
schemaByResourceType = map[yaml.TypeMeta]*spec.Schema{}
|
||||
|
||||
// parse the swagger, this should never fail
|
||||
parse(MustAsset(openAPIAssetName))
|
||||
|
||||
// TODO(pwittrock): add support for parsing additional schemas from
|
||||
// environment variables, files or other sources
|
||||
})
|
||||
}
|
||||
|
||||
// parse parses and indexes a single json schema
|
||||
func parse(b []byte) {
|
||||
if err := schema.UnmarshalJSON(b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// index the schema definitions so we can lookup them up for Resources
|
||||
for k := range schema.Definitions {
|
||||
// index by GVK, if no GVK is found then it is the schema for a subfield
|
||||
// of a Resource
|
||||
d := schema.Definitions[k]
|
||||
gvk, found := d.VendorExtensible.Extensions[kubernetesGVKExtensionKey]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
// cast the extension to a []map[string]string
|
||||
exts, ok := gvk.([]interface{})
|
||||
if !ok || len(exts) != 1 {
|
||||
continue
|
||||
}
|
||||
m, ok := exts[0].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// build the index key and save it
|
||||
g := m[groupKey].(string)
|
||||
apiVersion := m[versionKey].(string)
|
||||
if g != "" {
|
||||
apiVersion = g + "/" + apiVersion
|
||||
}
|
||||
schemaByResourceType[yaml.TypeMeta{Kind: m[kindKey].(string), APIVersion: apiVersion}] = &d
|
||||
}
|
||||
}
|
||||
|
||||
func rootSchema() *spec.Schema {
|
||||
initSchema()
|
||||
return &schema
|
||||
}
|
||||
69
kyaml/openapi/openapi_test.go
Normal file
69
kyaml/openapi/openapi_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package openapi_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestSchemaForResourceType(t *testing.T) {
|
||||
s := openapi.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()
|
||||
}
|
||||
}
|
||||
249
kyaml/openapi/swagger.go
Normal file
249
kyaml/openapi/swagger.go
Normal file
File diff suppressed because one or more lines are too long
20840
kyaml/openapi/swagger.json
Normal file
20840
kyaml/openapi/swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
85
kyaml/yaml/compatibility.go
Normal file
85
kyaml/yaml/compatibility.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
y1_1 "gopkg.in/yaml.v2"
|
||||
y1_2 "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// typeToTag maps OpenAPI schema types to yaml 1.2 tags
|
||||
var typeToTag = map[string]string{
|
||||
"string": "!!str",
|
||||
"integer": "!!int",
|
||||
"boolean": "!!bool",
|
||||
"number": "!!float",
|
||||
}
|
||||
|
||||
// FormatNonStringStyle makes sure that values which parse as non-string values in yaml 1.1
|
||||
// are correctly formatted given the Schema type.
|
||||
func FormatNonStringStyle(node *Node, schema spec.Schema) {
|
||||
if len(schema.Type) != 1 {
|
||||
return
|
||||
}
|
||||
t := schema.Type[0]
|
||||
|
||||
if !IsYaml1_1NonString(node) {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case t == "string" && schema.Format != "int-or-string":
|
||||
if (node.Style&DoubleQuotedStyle == 0) && (node.Style&SingleQuotedStyle == 0) {
|
||||
// must quote values so they are parsed as strings
|
||||
node.Style = DoubleQuotedStyle
|
||||
}
|
||||
case t == "boolean" || t == "integer" || t == "number":
|
||||
if (node.Style&DoubleQuotedStyle != 0) || (node.Style&SingleQuotedStyle != 0) {
|
||||
// must NOT quote the values so they aren't parsed as strings
|
||||
node.Style = 0
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
if tag, found := typeToTag[t]; found {
|
||||
// make sure the right tag is set
|
||||
node.Tag = tag
|
||||
}
|
||||
}
|
||||
|
||||
// IsYaml1_1NonString returns true if the value parses as a non-string value in yaml 1.1
|
||||
// when unquoted.
|
||||
//
|
||||
// Note: yaml 1.2 uses different keywords than yaml 1.1. Example: yaml 1.2 interprets
|
||||
// `field: on` and `field: "on"` as equivalent (both strings). However Yaml 1.1 interprets
|
||||
// `field: on` as on being a bool and `field: "on"` as on being a string.
|
||||
// If an input is read with `field: "on"`, and the style is changed from DoubleQuote to 0,
|
||||
// it will change the type of the field from a string to a bool. For this reason, fields
|
||||
// which are keywords in yaml 1.1 should never have their style changed, as it would break
|
||||
// backwards compatibility with yaml 1.1 -- which is what is used by the Kubernetes apiserver.
|
||||
func IsYaml1_1NonString(node *Node) bool {
|
||||
if node.Kind != y1_2.ScalarNode {
|
||||
// not a keyword
|
||||
return false
|
||||
}
|
||||
if strings.Contains(node.Value, "\n") {
|
||||
// multi-line strings will fail to unmarshal
|
||||
return false
|
||||
}
|
||||
// check if the value will unmarshal into a non-string value using a yaml 1.1 parser
|
||||
var i1 interface{}
|
||||
if err := y1_1.Unmarshal([]byte(node.Value), &i1); err != nil {
|
||||
return false
|
||||
}
|
||||
if reflect.TypeOf(i1) != stringType {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var stringType = reflect.TypeOf("string")
|
||||
134
kyaml/yaml/compatibility_test.go
Normal file
134
kyaml/yaml/compatibility_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package yaml_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestIsYaml1_1NonString(t *testing.T) {
|
||||
type testCase struct {
|
||||
val string
|
||||
expected bool
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
{val: "hello world", expected: false},
|
||||
{val: "2.0", expected: true},
|
||||
{val: "1.0\nhello", expected: false}, // multiline strings should always be false
|
||||
}
|
||||
|
||||
for k := range valueToTagMap {
|
||||
testCases = append(testCases, testCase{val: k, expected: true})
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
assert.Equal(t, test.expected,
|
||||
yaml.IsYaml1_1NonString(&yaml.Node{Kind: yaml.ScalarNode, Value: test.val}), test.val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatNonStringStyle(t *testing.T) {
|
||||
n := yaml.MustParse(`apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo
|
||||
args:
|
||||
- bar
|
||||
- on
|
||||
image: nginx:1.7.9
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: "80"
|
||||
`)
|
||||
s := openapi.SchemaForResourceType(
|
||||
yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
|
||||
|
||||
args, err := n.Pipe(yaml.Lookup(
|
||||
"spec", "template", "spec", "containers", "[name=foo]", "args"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.NotNil(t, args) {
|
||||
t.FailNow()
|
||||
}
|
||||
on := args.YNode().Content[1]
|
||||
onS := s.Lookup(
|
||||
"spec", "template", "spec", "containers", openapi.Elements, "args", openapi.Elements)
|
||||
yaml.FormatNonStringStyle(on, *onS.Schema)
|
||||
|
||||
containerPort, err := n.Pipe(yaml.Lookup(
|
||||
"spec", "template", "spec", "containers", "[name=foo]", "ports",
|
||||
"[name=http]", "containerPort"))
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
if !assert.NotNil(t, containerPort) {
|
||||
t.FailNow()
|
||||
}
|
||||
cpS := s.Lookup("spec", "template", "spec", "containers", openapi.Elements,
|
||||
"ports", openapi.Elements, "containerPort")
|
||||
if !assert.NotNil(t, cpS) {
|
||||
t.FailNow()
|
||||
}
|
||||
yaml.FormatNonStringStyle(containerPort.YNode(), *cpS.Schema)
|
||||
|
||||
actual := n.MustString()
|
||||
expected := `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo
|
||||
args:
|
||||
- bar
|
||||
- "on"
|
||||
image: nginx:1.7.9
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
`
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
// valueToTagMap is a map of values interpreted as non-strings in yaml 1.1 when left
|
||||
// unquoted.
|
||||
// To keep compatibility with the yaml parser used by Kubernetes (yaml 1.1) make sure the values
|
||||
// which are treated as non-string values are kept as non-string values.
|
||||
// https://github.com/go-yaml/yaml/blob/v2/resolve.go
|
||||
var valueToTagMap = func() map[string]string {
|
||||
val := map[string]string{}
|
||||
|
||||
// https://yaml.org/type/null.html
|
||||
values := []string{"", "~", "null", "Null", "NULL"}
|
||||
for i := range values {
|
||||
val[values[i]] = "!!null"
|
||||
}
|
||||
|
||||
// https://yaml.org/type/bool.html
|
||||
values = []string{
|
||||
"y", "Y", "yes", "Yes", "YES", "true", "True", "TRUE", "on", "On", "ON", "n", "N", "no",
|
||||
"No", "NO", "false", "False", "FALSE", "off", "Off", "OFF"}
|
||||
for i := range values {
|
||||
val[values[i]] = "!!bool"
|
||||
}
|
||||
|
||||
// https://yaml.org/type/float.html
|
||||
values = []string{
|
||||
".nan", ".NaN", ".NAN", ".inf", ".Inf", ".INF",
|
||||
"+.inf", "+.Inf", "+.INF", "-.inf", "-.Inf", "-.INF"}
|
||||
for i := range values {
|
||||
val[values[i]] = "!!float"
|
||||
}
|
||||
|
||||
return val
|
||||
}()
|
||||
@@ -223,6 +223,11 @@ func (m MapNodeSlice) Values() []*RNode {
|
||||
return values
|
||||
}
|
||||
|
||||
type TypeMeta struct {
|
||||
Kind string
|
||||
APIVersion string
|
||||
}
|
||||
|
||||
// ResourceMeta contains the metadata for a both Resource Type and Resource.
|
||||
type ResourceMeta struct {
|
||||
// APIVersion is the apiVersion field of a Resource
|
||||
|
||||
Reference in New Issue
Block a user