kustomize.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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 contains helpers for working with embedded kustomize commands
  14. package kustomize
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io/ioutil"
  19. "path/filepath"
  20. "runtime"
  21. "sync"
  22. "github.com/pkg/errors"
  23. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  24. yamlutil "k8s.io/apimachinery/pkg/util/yaml"
  25. "k8s.io/cli-runtime/pkg/kustomize"
  26. "sigs.k8s.io/kustomize/pkg/constants"
  27. "sigs.k8s.io/kustomize/pkg/fs"
  28. "sigs.k8s.io/kustomize/pkg/ifc"
  29. "sigs.k8s.io/kustomize/pkg/loader"
  30. "sigs.k8s.io/kustomize/pkg/patch"
  31. "sigs.k8s.io/kustomize/pkg/types"
  32. "sigs.k8s.io/yaml"
  33. )
  34. // Manager define a manager that allow access to kustomize capabilities
  35. type Manager struct {
  36. kustomizeDir string
  37. kustomizationFile *types.Kustomization
  38. strategicMergePatches strategicMergeSlice
  39. json6902Patches json6902Slice
  40. }
  41. var (
  42. lock = &sync.Mutex{}
  43. instances = map[string]*Manager{}
  44. )
  45. // GetManager return the KustomizeManager singleton instance
  46. // NB. this is done at singleton instance level because kubeadm has a unique pool
  47. // of patches that are applied to different content, at different time
  48. func GetManager(kustomizeDir string) (*Manager, error) {
  49. lock.Lock()
  50. defer lock.Unlock()
  51. // if the instance does not exists, create it
  52. if _, ok := instances[kustomizeDir]; !ok {
  53. km := &Manager{
  54. kustomizeDir: kustomizeDir,
  55. }
  56. // Create a loader that mimics the behavior of kubectl kustomize, including support for reading from
  57. // a local folder or git repository like git@github.com:someOrg/someRepo.git or https://github.com/someOrg/someRepo?ref=someHash
  58. // in order to do so you must use ldr.Root() instead of km.kustomizeDir and ldr.Load instead of other ways to read files
  59. fSys := fs.MakeRealFS()
  60. ldr, err := loader.NewLoader(km.kustomizeDir, fSys)
  61. if err != nil {
  62. return nil, err
  63. }
  64. defer ldr.Cleanup()
  65. // read the Kustomization file and all the patches it is
  66. // referencing (either stategicMerge or json6902 patches)
  67. if err := km.loadFromKustomizationFile(ldr); err != nil {
  68. return nil, err
  69. }
  70. // if a Kustomization file was not found, kubeadm creates
  71. // one using all the patches in the folder; however in this
  72. // case only stategicMerge patches are supported
  73. if km.kustomizationFile == nil {
  74. km.kustomizationFile = &types.Kustomization{}
  75. if err := km.loadFromFolder(ldr); err != nil {
  76. return nil, err
  77. }
  78. }
  79. instances[kustomizeDir] = km
  80. }
  81. return instances[kustomizeDir], nil
  82. }
  83. // loadFromKustomizationFile reads a Kustomization file and all the patches it is
  84. // referencing (either stategicMerge or json6902 patches)
  85. func (km *Manager) loadFromKustomizationFile(ldr ifc.Loader) error {
  86. // Kustomize support different KustomizationFileNames, so we try to read all
  87. var content []byte
  88. match := 0
  89. for _, kf := range constants.KustomizationFileNames {
  90. c, err := ldr.Load(kf)
  91. if err == nil {
  92. match++
  93. content = c
  94. }
  95. }
  96. // if no kustomization file is found return
  97. if match == 0 {
  98. return nil
  99. }
  100. // if more that one kustomization file is found, return error
  101. if match > 1 {
  102. return errors.Errorf("Found multiple kustomization files under: %s\n", ldr.Root())
  103. }
  104. // Decode the kustomization file
  105. decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(content), 1024)
  106. var k = &types.Kustomization{}
  107. if err := decoder.Decode(k); err != nil {
  108. return errors.Wrap(err, "Error decoding kustomization file")
  109. }
  110. km.kustomizationFile = k
  111. // gets all the strategic merge patches
  112. for _, f := range km.kustomizationFile.PatchesStrategicMerge {
  113. smp, err := newStrategicMergeSliceFromFile(ldr, string(f))
  114. if err != nil {
  115. return err
  116. }
  117. km.strategicMergePatches = append(km.strategicMergePatches, smp...)
  118. }
  119. // gets all the json6902 patches
  120. for _, f := range km.kustomizationFile.PatchesJson6902 {
  121. jp, err := newJSON6902FromFile(f, ldr, f.Path)
  122. if err != nil {
  123. return err
  124. }
  125. km.json6902Patches = append(km.json6902Patches, jp)
  126. }
  127. return nil
  128. }
  129. // loadFromFolder returns all the stategicMerge patches in a folder
  130. func (km *Manager) loadFromFolder(ldr ifc.Loader) error {
  131. files, err := ioutil.ReadDir(ldr.Root())
  132. if err != nil {
  133. return err
  134. }
  135. for _, fileInfo := range files {
  136. if fileInfo.IsDir() {
  137. continue
  138. }
  139. smp, err := newStrategicMergeSliceFromFile(ldr, fileInfo.Name())
  140. if err != nil {
  141. return err
  142. }
  143. km.strategicMergePatches = append(km.strategicMergePatches, smp...)
  144. }
  145. return nil
  146. }
  147. // Kustomize apply a set of patches to a resource.
  148. // Portions of the kustomize logic in this function are taken from the kubernetes-sigs/kind project
  149. func (km *Manager) Kustomize(data []byte) ([]byte, error) {
  150. // parse the resource to kustomize
  151. decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(data), 1024)
  152. var resource *unstructured.Unstructured
  153. if err := decoder.Decode(&resource); err != nil {
  154. return nil, err
  155. }
  156. // get patches corresponding to this resource
  157. strategicMerge := km.strategicMergePatches.filterByResource(resource)
  158. json6902 := km.json6902Patches.filterByResource(resource)
  159. // if there are no patches, for the target resources, exit
  160. patchesCnt := len(strategicMerge) + len(json6902)
  161. if patchesCnt == 0 {
  162. return data, nil
  163. }
  164. fmt.Printf("[kustomize] Applying %d patches to %s Resource=%s/%s\n", patchesCnt, resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName())
  165. // create an in memory fs to use for the kustomization
  166. memFS := fs.MakeFakeFS()
  167. fakeDir := "/"
  168. // for Windows we need this to be a drive because kustomize uses filepath.Abs()
  169. // which will add a drive letter if there is none. which drive letter is
  170. // unimportant as the path is on the fake filesystem anyhow
  171. if runtime.GOOS == "windows" {
  172. fakeDir = `C:\`
  173. }
  174. // writes the resource to a file in the temp file system
  175. b, err := yaml.Marshal(resource)
  176. if err != nil {
  177. return nil, err
  178. }
  179. name := "resource.yaml"
  180. memFS.WriteFile(filepath.Join(fakeDir, name), b)
  181. km.kustomizationFile.Resources = []string{name}
  182. // writes strategic merge patches to files in the temp file system
  183. km.kustomizationFile.PatchesStrategicMerge = []patch.StrategicMerge{}
  184. for i, p := range strategicMerge {
  185. b, err := yaml.Marshal(p)
  186. if err != nil {
  187. return nil, err
  188. }
  189. name := fmt.Sprintf("patch-%d.yaml", i)
  190. memFS.WriteFile(filepath.Join(fakeDir, name), b)
  191. km.kustomizationFile.PatchesStrategicMerge = append(km.kustomizationFile.PatchesStrategicMerge, patch.StrategicMerge(name))
  192. }
  193. // writes json6902 patches to files in the temp file system
  194. km.kustomizationFile.PatchesJson6902 = []patch.Json6902{}
  195. for i, p := range json6902 {
  196. name := fmt.Sprintf("patchjson-%d.yaml", i)
  197. memFS.WriteFile(filepath.Join(fakeDir, name), []byte(p.Patch))
  198. km.kustomizationFile.PatchesJson6902 = append(km.kustomizationFile.PatchesJson6902, patch.Json6902{Target: p.Target, Path: name})
  199. }
  200. // writes the kustomization file to the temp file system
  201. kbytes, err := yaml.Marshal(km.kustomizationFile)
  202. if err != nil {
  203. return nil, err
  204. }
  205. memFS.WriteFile(filepath.Join(fakeDir, "kustomization.yaml"), kbytes)
  206. // Finally customize the target resource
  207. var out bytes.Buffer
  208. if err := kustomize.RunKustomizeBuild(&out, memFS, fakeDir); err != nil {
  209. return nil, err
  210. }
  211. return out.Bytes(), nil
  212. }