-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
visitor.go
172 lines (148 loc) · 4.87 KB
/
visitor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package merge3
import (
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/walk"
)
type ConflictStrategy uint
const (
// TODO: Support more strategies
TakeUpdate ConflictStrategy = 1 + iota
)
type Visitor struct{}
func (m Visitor) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) {
if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() {
// explicitly cleared from either dest or update
return walk.ClearNode, nil
}
if nodes.Dest() == nil && nodes.Updated() == nil {
// implicitly cleared missing from both dest and update
return walk.ClearNode, nil
}
if nodes.Dest() == nil {
// not cleared, but missing from the dest
// initialize a new value that can be recursively merged
return yaml.NewRNode(&yaml.Node{Kind: yaml.MappingNode}), nil
}
// recursively merge the dest with the original and updated
return nodes.Dest(), nil
}
func (m Visitor) visitAList(nodes walk.Sources, _ *openapi.ResourceSchema) (*yaml.RNode, error) {
if yaml.IsMissingOrNull(nodes.Updated()) && !yaml.IsMissingOrNull(nodes.Origin()) {
// implicitly cleared from update -- element was deleted
return walk.ClearNode, nil
}
if yaml.IsMissingOrNull(nodes.Dest()) {
// not cleared, but missing from the dest
// initialize a new value that can be recursively merged
return yaml.NewRNode(&yaml.Node{Kind: yaml.SequenceNode}), nil
}
// recursively merge the dest with the original and updated
return nodes.Dest(), nil
}
func (m Visitor) VisitScalar(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) {
if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() {
// explicitly cleared from either dest or update
return nil, nil
}
if yaml.IsMissingOrNull(nodes.Updated()) != yaml.IsMissingOrNull(nodes.Origin()) {
// value added or removed in update
return nodes.Updated(), nil
}
if yaml.IsMissingOrNull(nodes.Updated()) && yaml.IsMissingOrNull(nodes.Origin()) {
// value added or removed in update
return nodes.Dest(), nil
}
values, err := m.getStrValues(nodes)
if err != nil {
return nil, err
}
if (values.Dest == "" || values.Dest == values.Origin) && values.Origin != values.Update {
// if local is nil or is unchanged but there is new update
return nodes.Updated(), nil
}
if nodes.Updated().YNode().Value != nodes.Origin().YNode().Value {
// value changed in update
return nodes.Updated(), nil
}
// unchanged between origin and update, keep the dest
return nodes.Dest(), nil
}
func (m Visitor) visitNAList(nodes walk.Sources) (*yaml.RNode, error) {
if nodes.Updated().IsTaggedNull() || nodes.Dest().IsTaggedNull() {
// explicitly cleared from either dest or update
return walk.ClearNode, nil
}
if yaml.IsMissingOrNull(nodes.Updated()) != yaml.IsMissingOrNull(nodes.Origin()) {
// value added or removed in update
return nodes.Updated(), nil
}
if yaml.IsMissingOrNull(nodes.Updated()) && yaml.IsMissingOrNull(nodes.Origin()) {
// value not present in source or dest
return nodes.Dest(), nil
}
// compare origin and update values to see if they have changed
values, err := m.getStrValues(nodes)
if err != nil {
return nil, err
}
if values.Update != values.Origin {
// value changed in update
return nodes.Updated(), nil
}
// unchanged between origin and update, keep the dest
return nodes.Dest(), nil
}
func (m Visitor) VisitList(nodes walk.Sources, s *openapi.ResourceSchema, kind walk.ListKind) (*yaml.RNode, error) {
if kind == walk.AssociativeList {
return m.visitAList(nodes, s)
}
// non-associative list
return m.visitNAList(nodes)
}
func (m Visitor) getStrValues(nodes walk.Sources) (strValues, error) {
var uStr, oStr, dStr string
var err error
if nodes.Updated() != nil && nodes.Updated().YNode() != nil {
s := nodes.Updated().YNode().Style
defer func() {
nodes.Updated().YNode().Style = s
}()
nodes.Updated().YNode().Style = yaml.FlowStyle | yaml.SingleQuotedStyle
uStr, err = nodes.Updated().String()
if err != nil {
return strValues{}, err
}
}
if nodes.Origin() != nil && nodes.Origin().YNode() != nil {
s := nodes.Origin().YNode().Style
defer func() {
nodes.Origin().YNode().Style = s
}()
nodes.Origin().YNode().Style = yaml.FlowStyle | yaml.SingleQuotedStyle
oStr, err = nodes.Origin().String()
if err != nil {
return strValues{}, err
}
}
if nodes.Dest() != nil && nodes.Dest().YNode() != nil {
s := nodes.Dest().YNode().Style
defer func() {
nodes.Dest().YNode().Style = s
}()
nodes.Dest().YNode().Style = yaml.FlowStyle | yaml.SingleQuotedStyle
dStr, err = nodes.Dest().String()
if err != nil {
return strValues{}, err
}
}
return strValues{Origin: oStr, Update: uStr, Dest: dStr}, nil
}
type strValues struct {
Origin string
Update string
Dest string
}
var _ walk.Visitor = Visitor{}