mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-07-19 02:28:30 +00:00
Merge pull request #4114 from monopole/anchorsAweigh
Add YAML anchor/alias expansion.
This commit is contained in:
@@ -214,6 +214,35 @@ type ResMap interface {
|
|||||||
// namespaces. Cluster wide objects are never excluded.
|
// namespaces. Cluster wide objects are never excluded.
|
||||||
SubsetThatCouldBeReferencedByResource(*resource.Resource) ResMap
|
SubsetThatCouldBeReferencedByResource(*resource.Resource) ResMap
|
||||||
|
|
||||||
|
// DeAnchor replaces YAML aliases with structured data copied from anchors.
|
||||||
|
// This cannot be undone; if desired, call DeepCopy first.
|
||||||
|
// Subsequent marshalling to YAML will no longer have anchor
|
||||||
|
// definitions ('&') or aliases ('*').
|
||||||
|
//
|
||||||
|
// Anchors are not expected to work across YAML 'documents'.
|
||||||
|
// If three resources are loaded from one file containing three YAML docs:
|
||||||
|
//
|
||||||
|
// {resourceA}
|
||||||
|
// ---
|
||||||
|
// {resourceB}
|
||||||
|
// ---
|
||||||
|
// {resourceC}
|
||||||
|
//
|
||||||
|
// then anchors defined in A cannot be seen from B and C and vice versa.
|
||||||
|
// OTOH, cross-resource links (a field in B referencing fields in A) will
|
||||||
|
// work if the resources are gathered in a ResourceList:
|
||||||
|
//
|
||||||
|
// apiVersion: config.kubernetes.io/v1
|
||||||
|
// kind: ResourceList
|
||||||
|
// metadata:
|
||||||
|
// name: someList
|
||||||
|
// items:
|
||||||
|
// - {resourceA}
|
||||||
|
// - {resourceB}
|
||||||
|
// - {resourceC}
|
||||||
|
//
|
||||||
|
DeAnchor() error
|
||||||
|
|
||||||
// DeepCopy copies the ResMap and underlying resources.
|
// DeepCopy copies the ResMap and underlying resources.
|
||||||
DeepCopy() ResMap
|
DeepCopy() ResMap
|
||||||
|
|
||||||
|
|||||||
@@ -607,6 +607,16 @@ func (m *resWrangler) ToRNodeSlice() []*kyaml.RNode {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeAnchor implements ResMap.
|
||||||
|
func (m *resWrangler) DeAnchor() (err error) {
|
||||||
|
for i := range m.rList {
|
||||||
|
if err = m.rList[i].DeAnchor(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ApplySmPatch applies the patch, and errors on Id collisions.
|
// ApplySmPatch applies the patch, and errors on Id collisions.
|
||||||
func (m *resWrangler) ApplySmPatch(
|
func (m *resWrangler) ApplySmPatch(
|
||||||
selectedSet *resource.IdSet, patch *resource.Resource) error {
|
selectedSet *resource.IdSet, patch *resource.Resource) error {
|
||||||
|
|||||||
@@ -902,6 +902,100 @@ rules:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeAnchorSingleDoc(t *testing.T) {
|
||||||
|
input := `apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: wildcard
|
||||||
|
data:
|
||||||
|
color: &color-used blue
|
||||||
|
feeling: *color-used
|
||||||
|
`
|
||||||
|
rm, err := rmF.NewResMapFromBytes([]byte(input))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, rm.DeAnchor())
|
||||||
|
yaml, err := rm.AsYaml()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, strings.TrimSpace(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
color: blue
|
||||||
|
feeling: blue
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: wildcard
|
||||||
|
`), strings.TrimSpace(string(yaml)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anchor references don't cross YAML document boundaries.
|
||||||
|
func TestDeAnchorMultiDoc(t *testing.T) {
|
||||||
|
input := `apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: betty
|
||||||
|
data:
|
||||||
|
color: &color-used blue
|
||||||
|
feeling: *color-used
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: bob
|
||||||
|
data:
|
||||||
|
color: red
|
||||||
|
feeling: *color-used
|
||||||
|
`
|
||||||
|
_, err := rmF.NewResMapFromBytes([]byte(input))
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "unknown anchor 'color-used' referenced")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anchor references cross list elements in a ResourceList.
|
||||||
|
func TestDeAnchorResourceList(t *testing.T) {
|
||||||
|
input := `apiVersion: config.kubernetes.io/v1
|
||||||
|
kind: ResourceList
|
||||||
|
metadata:
|
||||||
|
name: aShortList
|
||||||
|
items:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: betty
|
||||||
|
data:
|
||||||
|
color: &color-used blue
|
||||||
|
feeling: *color-used
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: bob
|
||||||
|
data:
|
||||||
|
color: red
|
||||||
|
feeling: *color-used
|
||||||
|
`
|
||||||
|
rm, err := rmF.NewResMapFromBytes([]byte(input))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, rm.DeAnchor())
|
||||||
|
yaml, err := rm.AsYaml()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, strings.TrimSpace(`
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
color: blue
|
||||||
|
feeling: blue
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: betty
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
color: red
|
||||||
|
feeling: blue
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: bob
|
||||||
|
`), strings.TrimSpace(string(yaml)))
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplySmPatch_General(t *testing.T) {
|
func TestApplySmPatch_General(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
myDeployment = "Deployment"
|
myDeployment = "Deployment"
|
||||||
|
|||||||
@@ -808,15 +808,13 @@ items:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test is just an exploration of the low level (go-yaml)
|
// This test shows the lower level (go-yaml) representation of a small doc
|
||||||
// representation of a small doc with an anchor. The anchor
|
// with an anchor. The anchor structure is there, in the sense that an
|
||||||
// structure is there, in the sense that an alias pointer is
|
// alias pointer is readily available when a node's kind is an AliasNode.
|
||||||
// readily available when a node's kind is an AliasNode.
|
// I.e. the anchor mapping name -> object was noted during unmarshalling.
|
||||||
// That is, the anchor mapping has already been recognized.
|
// However, at the time of writing github.com/go-yaml/yaml/encoder.go
|
||||||
// However, the github.com/go-yaml/yaml/encoder.go code doesn't
|
// doesn't appear to have an option to perform anchor replacements when
|
||||||
// appear to have an option to perform anchor replacements when
|
// encoding. It emits anchor definitions and references (aliases) intact.
|
||||||
// encoding (instead it emits the anchor definitions and
|
|
||||||
// references, which is not a bad thing but not desired here).
|
|
||||||
func TestByteReader_AnchorBehavior(t *testing.T) {
|
func TestByteReader_AnchorBehavior(t *testing.T) {
|
||||||
const input = `
|
const input = `
|
||||||
data:
|
data:
|
||||||
|
|||||||
@@ -903,6 +903,48 @@ func (rn *RNode) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeAnchor inflates all YAML aliases with their anchor values.
|
||||||
|
// All YAML anchor data is permanently removed (feel free to call Copy first).
|
||||||
|
func (rn *RNode) DeAnchor() (err error) {
|
||||||
|
rn.value, err = deAnchor(rn.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// deAnchor removes all AliasNodes from the yaml.Node's tree, replacing
|
||||||
|
// them with what they point to. All Anchor fields (these are used to mark
|
||||||
|
// anchor definitions) are cleared.
|
||||||
|
func deAnchor(yn *yaml.Node) (res *yaml.Node, err error) {
|
||||||
|
if yn == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if yn.Anchor != "" {
|
||||||
|
// This node defines an anchor. Clear the field so that it
|
||||||
|
// doesn't show up when marshalling.
|
||||||
|
if yn.Kind == yaml.AliasNode {
|
||||||
|
// Maybe this is OK, but for now treating it as a bug.
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"anchor %q defined using alias %v", yn.Anchor, yn.Alias)
|
||||||
|
}
|
||||||
|
yn.Anchor = ""
|
||||||
|
}
|
||||||
|
switch yn.Kind {
|
||||||
|
case yaml.ScalarNode:
|
||||||
|
return yn, nil
|
||||||
|
case yaml.AliasNode:
|
||||||
|
return deAnchor(yn.Alias)
|
||||||
|
case yaml.DocumentNode, yaml.MappingNode, yaml.SequenceNode:
|
||||||
|
for i := range yn.Content {
|
||||||
|
yn.Content[i], err = deAnchor(yn.Content[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return yn, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cannot deAnchor kind %q", yn.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetValidatedMetadata returns metadata after subjecting it to some tests.
|
// GetValidatedMetadata returns metadata after subjecting it to some tests.
|
||||||
func (rn *RNode) GetValidatedMetadata() (ResourceMeta, error) {
|
func (rn *RNode) GetValidatedMetadata() (ResourceMeta, error) {
|
||||||
m, err := rn.GetMeta()
|
m, err := rn.GetMeta()
|
||||||
|
|||||||
@@ -696,6 +696,31 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeAnchor(t *testing.T) {
|
||||||
|
rn, err := Parse(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: wildcard
|
||||||
|
data:
|
||||||
|
color: &color-used blue
|
||||||
|
feeling: *color-used
|
||||||
|
`)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, rn.DeAnchor())
|
||||||
|
actual, err := rn.String()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, strings.TrimSpace(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: wildcard
|
||||||
|
data:
|
||||||
|
color: blue
|
||||||
|
feeling: blue
|
||||||
|
`), strings.TrimSpace(actual))
|
||||||
|
}
|
||||||
|
|
||||||
func TestRNode_UnmarshalJSON(t *testing.T) {
|
func TestRNode_UnmarshalJSON(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
testName string
|
testName string
|
||||||
|
|||||||
Reference in New Issue
Block a user