strategicmerge.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /*
  2. Copyright 2019 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 kustomize
  14. import (
  15. "bytes"
  16. "encoding/json"
  17. "io"
  18. "strings"
  19. "github.com/pkg/errors"
  20. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/util/yaml"
  23. "sigs.k8s.io/kustomize/pkg/ifc"
  24. )
  25. // strategicMergeSlice is a slice of strategic merge patches.
  26. // Unstructured objects are used to represent strategic merge patches of any group/version/kind.
  27. type strategicMergeSlice []*unstructured.Unstructured
  28. // newStrategicMergeSliceFromFile returns a slice of strategic merge patches from a file
  29. func newStrategicMergeSliceFromFile(loader ifc.Loader, path string) (strategicMergeSlice, error) {
  30. content, err := loader.Load(path)
  31. if err != nil {
  32. return nil, errors.Wrapf(err, "load from path %q failed", path)
  33. }
  34. res, err := newStrategicMergeSliceFromBytes(content)
  35. if err != nil {
  36. return nil, errors.Wrapf(err, "convert %q to Unstructured failed", path)
  37. }
  38. return res, nil
  39. }
  40. // newStrategicMergeSliceFromBytes returns a strategic merge patches contained in a []byte.
  41. // This functions handles all the nuances of Kubernetes yaml (e.g. many yaml
  42. // documents in one file, List of objects)
  43. func newStrategicMergeSliceFromBytes(in []byte) (strategicMergeSlice, error) {
  44. decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
  45. var result strategicMergeSlice
  46. var err error
  47. // Parse all the yaml documents in the file
  48. for err == nil || isEmptyYamlError(err) {
  49. var u unstructured.Unstructured
  50. err = decoder.Decode(&u)
  51. // if the yaml document is a valid unstructured object
  52. if err == nil {
  53. // it the unstructured object is empty, move to the next
  54. if len(u.Object) == 0 {
  55. continue
  56. }
  57. // validate the object has kind, metadata.name as required by Kustomize
  58. if err := validate(u); err != nil {
  59. return nil, err
  60. }
  61. // if the document is a list of objects
  62. if strings.HasSuffix(u.GetKind(), "List") {
  63. // for each item in the list of objects
  64. if err := u.EachListItem(func(item runtime.Object) error {
  65. // Marshal the object
  66. itemJSON, err := json.Marshal(item)
  67. if err != nil {
  68. return err
  69. }
  70. // Get the strategicMergeSlice for the item
  71. itemU, err := newStrategicMergeSliceFromBytes(itemJSON)
  72. if err != nil {
  73. return err
  74. }
  75. // append the strategicMergeSlice for the item to the strategicMergeSlice
  76. result = append(result, itemU...)
  77. return nil
  78. }); err != nil {
  79. return nil, err
  80. }
  81. continue
  82. }
  83. // append the object to the stategicMergeSlice
  84. result = append(result, &u)
  85. }
  86. }
  87. if err != io.EOF {
  88. return nil, err
  89. }
  90. return result, nil
  91. }
  92. // filterByResource returns all the strategic merge patches in the strategicMergeSlice corresponding to a given resource
  93. func (s *strategicMergeSlice) filterByResource(r *unstructured.Unstructured) strategicMergeSlice {
  94. var result strategicMergeSlice
  95. for _, p := range *s {
  96. if p.GroupVersionKind() == r.GroupVersionKind() &&
  97. p.GetNamespace() == r.GetNamespace() &&
  98. p.GetName() == r.GetName() {
  99. result = append(result, p)
  100. }
  101. }
  102. return result
  103. }
  104. // validate validates that u has kind and name
  105. // except for kind `List`, which doesn't require a name
  106. func validate(u unstructured.Unstructured) error {
  107. kind := u.GetKind()
  108. if kind == "" {
  109. return errors.New("missing kind in object")
  110. } else if strings.HasSuffix(kind, "List") {
  111. return nil
  112. }
  113. if u.GetName() == "" {
  114. return errors.New("missing metadata.name in object")
  115. }
  116. return nil
  117. }
  118. func isEmptyYamlError(err error) bool {
  119. return strings.Contains(err.Error(), "is missing in 'null'")
  120. }