mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
yaml formatter improvements
- identify and fix yaml 1.1 compatibility issues in configuration - support providing function for performing custom formatting
This commit is contained in:
@@ -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,17 @@ 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 {
|
||||
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 +116,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.SchemaForField(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.SchemaForElements()
|
||||
}
|
||||
}
|
||||
|
||||
// format the node using the schema
|
||||
err := f.fmtNode(n.Content[i], p, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -143,6 +186,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
|
||||
|
||||
Reference in New Issue
Block a user