resmap.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package resmap implements a map from ResId to Resource that tracks all resources in a kustomization.
  14. package resmap
  15. import (
  16. "bytes"
  17. "fmt"
  18. "reflect"
  19. "sort"
  20. "github.com/ghodss/yaml"
  21. "sigs.k8s.io/kustomize/pkg/resid"
  22. "sigs.k8s.io/kustomize/pkg/resource"
  23. "sigs.k8s.io/kustomize/pkg/types"
  24. )
  25. // ResMap is a map from ResId to Resource.
  26. type ResMap map[resid.ResId]*resource.Resource
  27. type IdMatcher func(resid.ResId) bool
  28. // GetMatchingIds returns a slice of ResId keys from the map
  29. // that all satisfy the given matcher function.
  30. func (m ResMap) GetMatchingIds(matches IdMatcher) []resid.ResId {
  31. var result []resid.ResId
  32. for id := range m {
  33. if matches(id) {
  34. result = append(result, id)
  35. }
  36. }
  37. return result
  38. }
  39. // EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
  40. func (m ResMap) EncodeAsYaml() ([]byte, error) {
  41. var ids []resid.ResId
  42. for id := range m {
  43. ids = append(ids, id)
  44. }
  45. sort.Sort(IdSlice(ids))
  46. firstObj := true
  47. var b []byte
  48. buf := bytes.NewBuffer(b)
  49. for _, id := range ids {
  50. obj := m[id]
  51. out, err := yaml.Marshal(obj.Map())
  52. if err != nil {
  53. return nil, err
  54. }
  55. if firstObj {
  56. firstObj = false
  57. } else {
  58. _, err = buf.WriteString("---\n")
  59. if err != nil {
  60. return nil, err
  61. }
  62. }
  63. _, err = buf.Write(out)
  64. if err != nil {
  65. return nil, err
  66. }
  67. }
  68. return buf.Bytes(), nil
  69. }
  70. // ErrorIfNotEqual returns error if maps are not equal.
  71. func (m ResMap) ErrorIfNotEqual(m2 ResMap) error {
  72. if len(m) != len(m2) {
  73. var keySet1 []resid.ResId
  74. var keySet2 []resid.ResId
  75. for id := range m {
  76. keySet1 = append(keySet1, id)
  77. }
  78. for id := range m2 {
  79. keySet2 = append(keySet2, id)
  80. }
  81. return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
  82. }
  83. for id, obj1 := range m {
  84. obj2, found := m2[id]
  85. if !found {
  86. return fmt.Errorf("%#v doesn't exist in %#v", id, m2)
  87. }
  88. if !reflect.DeepEqual(obj1, obj2) {
  89. return fmt.Errorf("%#v doesn't deep equal %#v", obj1, obj2)
  90. }
  91. }
  92. return nil
  93. }
  94. // DeepCopy clone the resmap into a new one
  95. func (m ResMap) DeepCopy(rf *resource.Factory) ResMap {
  96. mcopy := make(ResMap)
  97. for id, obj := range m {
  98. mcopy[id] = obj.DeepCopy()
  99. }
  100. return mcopy
  101. }
  102. // FilterBy returns a subset ResMap containing ResIds with
  103. // the same namespace and leftmost name prefix and rightmost name
  104. // as the inputId. If inputId is a cluster level resource, this
  105. // returns the original ResMap.
  106. func (m ResMap) FilterBy(inputId resid.ResId) ResMap {
  107. if inputId.Gvk().IsClusterKind() {
  108. return m
  109. }
  110. result := ResMap{}
  111. for id, res := range m {
  112. if id.Gvk().IsClusterKind() || id.Namespace() == inputId.Namespace() &&
  113. id.HasSameLeftmostPrefix(inputId) &&
  114. id.HasSameRightmostSuffix(inputId) {
  115. result[id] = res
  116. }
  117. }
  118. return result
  119. }
  120. // MergeWithErrorOnIdCollision combines multiple ResMap instances, failing on
  121. // key collision and skipping nil maps.
  122. // If all of the maps are nil, an empty ResMap is returned.
  123. func MergeWithErrorOnIdCollision(maps ...ResMap) (ResMap, error) {
  124. result := ResMap{}
  125. for _, m := range maps {
  126. if m == nil {
  127. continue
  128. }
  129. for id, res := range m {
  130. if _, found := result[id]; found {
  131. return nil, fmt.Errorf("id '%q' already used", id)
  132. }
  133. result[id] = res
  134. }
  135. }
  136. return result, nil
  137. }
  138. // MergeWithOverride combines multiple ResMap instances, allowing and sometimes
  139. // demanding certain collisions and skipping nil maps.
  140. // A collision would be demanded, say, when a generated ConfigMap has the
  141. // "replace" option in its generation instructions, meaning it is supposed
  142. // to replace something from the raw resources list.
  143. // If all of the maps are nil, an empty ResMap is returned.
  144. // When looping over the instances to combine them, if a resource id for
  145. // resource X is found to be already in the combined map, then the behavior
  146. // field for X must be BehaviorMerge or BehaviorReplace. If X is not in the
  147. // map, then it's behavior cannot be merge or replace.
  148. func MergeWithOverride(maps ...ResMap) (ResMap, error) {
  149. result := maps[0]
  150. if result == nil {
  151. result = ResMap{}
  152. }
  153. for _, m := range maps[1:] {
  154. if m == nil {
  155. continue
  156. }
  157. for id, r := range m {
  158. matchedId := result.GetMatchingIds(id.GvknEquals)
  159. if len(matchedId) == 1 {
  160. id = matchedId[0]
  161. switch r.Behavior() {
  162. case types.BehaviorReplace:
  163. r.Replace(result[id])
  164. result[id] = r
  165. case types.BehaviorMerge:
  166. r.Merge(result[id])
  167. result[id] = r
  168. default:
  169. return nil, fmt.Errorf("id %#v exists; must merge or replace", id)
  170. }
  171. } else if len(matchedId) == 0 {
  172. switch r.Behavior() {
  173. case types.BehaviorMerge, types.BehaviorReplace:
  174. return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id)
  175. default:
  176. result[id] = r
  177. }
  178. } else {
  179. return nil, fmt.Errorf("merge conflict, found multiple objects %v the Resmap %v can merge into", matchedId, id)
  180. }
  181. }
  182. }
  183. return result, nil
  184. }