Skip to content

Commit 0e447d7

Browse files
kubeadm Json6902 Patches
1 parent 103e926 commit 0e447d7

File tree

6 files changed

+425
-176
lines changed

6 files changed

+425
-176
lines changed

cmd/kubeadm/app/util/kustomize/BUILD

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
33
go_library(
44
name = "go_default_library",
55
srcs = [
6+
"json6902.go",
67
"kustomize.go",
7-
"unstructured.go",
8+
"strategicmerge.go",
89
],
910
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/kustomize",
1011
visibility = ["//visibility:public"],
1112
deps = [
1213
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
1314
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
14-
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
1515
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
1616
"//staging/src/k8s.io/cli-runtime/pkg/kustomize:go_default_library",
1717
"//vendor/github.com/pkg/errors:go_default_library",
18+
"//vendor/sigs.k8s.io/kustomize/pkg/constants:go_default_library",
1819
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
1920
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
2021
"//vendor/sigs.k8s.io/kustomize/pkg/loader:go_default_library",
22+
"//vendor/sigs.k8s.io/kustomize/pkg/patch:go_default_library",
23+
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
24+
"//vendor/sigs.k8s.io/yaml:go_default_library",
2125
],
2226
)
2327

@@ -39,10 +43,11 @@ go_test(
3943
name = "go_default_test",
4044
srcs = [
4145
"kustomize_test.go",
42-
"unstructured_test.go",
46+
"strategicmerge_test.go",
4347
],
4448
embed = [":go_default_library"],
4549
deps = [
50+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
4651
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
4752
"//vendor/github.com/lithammer/dedent:go_default_library",
4853
],
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package kustomize contains helpers for working with embedded kustomize commands
18+
package kustomize
19+
20+
import (
21+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
22+
"sigs.k8s.io/kustomize/pkg/ifc"
23+
"sigs.k8s.io/kustomize/pkg/patch"
24+
)
25+
26+
// json6902 represents a json6902 patch
27+
type json6902 struct {
28+
// Target refers to a Kubernetes object that the json patch will be applied to
29+
*patch.Target
30+
31+
// Patch contain the json patch as a string
32+
Patch string
33+
}
34+
35+
// json6902Slice is a slice of json6902 patches.
36+
type json6902Slice []*json6902
37+
38+
// newJSON6902FromFile returns a json6902 patch from a file
39+
func newJSON6902FromFile(f patch.Json6902, ldr ifc.Loader, file string) (*json6902, error) {
40+
patch, err := ldr.Load(file)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
return &json6902{
46+
Target: f.Target,
47+
Patch: string(patch),
48+
}, nil
49+
}
50+
51+
// filterByResource returns all the json6902 patches in the json6902Slice corresponding to a given resource
52+
func (s *json6902Slice) filterByResource(r *unstructured.Unstructured) json6902Slice {
53+
var result json6902Slice
54+
for _, p := range *s {
55+
if p.Group == r.GroupVersionKind().Group &&
56+
p.Version == r.GroupVersionKind().Version &&
57+
p.Kind == r.GroupVersionKind().Kind &&
58+
p.Namespace == r.GetNamespace() &&
59+
p.Name == r.GetName() {
60+
result = append(result, p)
61+
}
62+
}
63+
return result
64+
}

cmd/kubeadm/app/util/kustomize/kustomize.go

Lines changed: 137 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,25 @@ import (
2525
"runtime"
2626
"sync"
2727

28+
"github.com/pkg/errors"
29+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30+
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
2831
"k8s.io/cli-runtime/pkg/kustomize"
32+
"sigs.k8s.io/kustomize/pkg/constants"
2933
"sigs.k8s.io/kustomize/pkg/fs"
34+
"sigs.k8s.io/kustomize/pkg/ifc"
3035
"sigs.k8s.io/kustomize/pkg/loader"
36+
"sigs.k8s.io/kustomize/pkg/patch"
37+
"sigs.k8s.io/kustomize/pkg/types"
38+
"sigs.k8s.io/yaml"
3139
)
3240

3341
// Manager define a manager that allow access to kustomize capabilities
3442
type Manager struct {
35-
kustomizeDir string
36-
us UnstructuredSlice
43+
kustomizeDir string
44+
kustomizationFile *types.Kustomization
45+
strategicMergePatches strategicMergeSlice
46+
json6902Patches json6902Slice
3747
}
3848

3949
var (
@@ -42,6 +52,8 @@ var (
4252
)
4353

4454
// GetManager return the KustomizeManager singleton instance
55+
// NB. this is done at singleton instance level because kubeadm has a unique pool
56+
// of patches that are applied to different content, at different time
4557
func GetManager(kustomizeDir string) (*Manager, error) {
4658
lock.Lock()
4759
defer lock.Unlock()
@@ -52,91 +64,135 @@ func GetManager(kustomizeDir string) (*Manager, error) {
5264
kustomizeDir: kustomizeDir,
5365
}
5466

55-
// loads the UnstructuredSlice with all the patches into the Manager
56-
// NB. this is done at singleton instance level because kubeadm has a unique pool
57-
// of patches that are applied to different content, at different time
58-
if err := km.getUnstructuredSlice(); err != nil {
67+
// Create a loader that mimics the behavior of kubectl kustomize, including support for reading from
68+
// a local folder or git repository like [email protected]:someOrg/someRepo.git or https://github.com/someOrg/someRepo?ref=someHash
69+
// in order to do so you must use ldr.Root() instead of km.kustomizeDir and ldr.Load instead of other ways to read files
70+
fSys := fs.MakeRealFS()
71+
ldr, err := loader.NewLoader(km.kustomizeDir, fSys)
72+
if err != nil {
73+
return nil, err
74+
}
75+
defer ldr.Cleanup()
76+
77+
// read the Kustomization file and all the patches it is
78+
// referencing (either stategicMerge or json6902 patches)
79+
if err := km.loadFromKustomizationFile(ldr); err != nil {
5980
return nil, err
6081
}
6182

83+
// if a Kustomization file was not found, kubeadm creates
84+
// one using all the patches in the folder; however in this
85+
// case only stategicMerge patches are supported
86+
if km.kustomizationFile == nil {
87+
km.kustomizationFile = &types.Kustomization{}
88+
if err := km.loadFromFolder(ldr); err != nil {
89+
return nil, err
90+
}
91+
}
92+
6293
instances[kustomizeDir] = km
6394
}
6495

6596
return instances[kustomizeDir], nil
6697
}
6798

68-
// getUnstructuredSlice returns a UnstructuredSlice with all the patches.
69-
func (km *Manager) getUnstructuredSlice() error {
70-
// kubeadm does not require a kustomization.yaml file listing all the resources/patches, so it is necessary
71-
// to rebuild the list of patches manually
72-
// TODO: make this git friendly - currently this works only for patches in local folders -
73-
files, err := ioutil.ReadDir(km.kustomizeDir)
74-
if err != nil {
75-
return err
99+
// loadFromKustomizationFile reads a Kustomization file and all the patches it is
100+
// referencing (either stategicMerge or json6902 patches)
101+
func (km *Manager) loadFromKustomizationFile(ldr ifc.Loader) error {
102+
// Kustomize support different KustomizationFileNames, so we try to read all
103+
var content []byte
104+
match := 0
105+
for _, kf := range constants.KustomizationFileNames {
106+
c, err := ldr.Load(kf)
107+
if err == nil {
108+
match++
109+
content = c
110+
}
76111
}
77112

78-
var paths = []string{}
79-
for _, file := range files {
80-
if file.IsDir() {
81-
continue
113+
// if no kustomization file is found return
114+
if match == 0 {
115+
return nil
116+
}
117+
118+
// if more that one kustomization file is found, return error
119+
if match > 1 {
120+
return errors.Errorf("Found multiple kustomization files under: %s\n", ldr.Root())
121+
}
122+
123+
// Decode the kustomization file
124+
decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(content), 1024)
125+
var k = &types.Kustomization{}
126+
if err := decoder.Decode(k); err != nil {
127+
return errors.Wrap(err, "Error decoding kustomization file")
128+
}
129+
km.kustomizationFile = k
130+
131+
// gets all the strategic merge patches
132+
for _, f := range km.kustomizationFile.PatchesStrategicMerge {
133+
smp, err := newStrategicMergeSliceFromFile(ldr, string(f))
134+
if err != nil {
135+
return err
82136
}
83-
paths = append(paths, file.Name())
137+
km.strategicMergePatches = append(km.strategicMergePatches, smp...)
84138
}
85139

86-
// Create a loader that mimics the behavior of kubectl kustomize, including support for reading from
87-
// a local git repository like [email protected]:someOrg/someRepo.git or https://github.com/someOrg/someRepo?ref=someHash
88-
fSys := fs.MakeRealFS()
89-
ldr, err := loader.NewLoader(km.kustomizeDir, fSys)
90-
if err != nil {
91-
return err
140+
// gets all the json6902 patches
141+
for _, f := range km.kustomizationFile.PatchesJson6902 {
142+
jp, err := newJSON6902FromFile(f, ldr, f.Path)
143+
if err != nil {
144+
return err
145+
}
146+
km.json6902Patches = append(km.json6902Patches, jp)
92147
}
93-
defer ldr.Cleanup()
94148

95-
// read all the kustomizations and build the UnstructuredSlice
96-
us, err := NewUnstructuredSliceFromFiles(ldr, paths)
149+
return nil
150+
}
151+
152+
// loadFromFolder returns all the stategicMerge patches in a folder
153+
func (km *Manager) loadFromFolder(ldr ifc.Loader) error {
154+
files, err := ioutil.ReadDir(ldr.Root())
97155
if err != nil {
98156
return err
99157
}
158+
for _, fileInfo := range files {
159+
if fileInfo.IsDir() {
160+
continue
161+
}
100162

101-
km.us = us
163+
smp, err := newStrategicMergeSliceFromFile(ldr, fileInfo.Name())
164+
if err != nil {
165+
return err
166+
}
167+
km.strategicMergePatches = append(km.strategicMergePatches, smp...)
168+
}
102169
return nil
103170
}
104171

105172
// Kustomize apply a set of patches to a resource.
106173
// Portions of the kustomize logic in this function are taken from the kubernetes-sigs/kind project
107-
func (km *Manager) Kustomize(res []byte) ([]byte, error) {
108-
// create a loader that mimics the behavior of kubectl kustomize
109-
// and converts the resource into a UnstructuredSlice
110-
// Nb. in kubeadm we are controlling resource generation, and so we
111-
// we are expecting 1 object into each resource, eg. the static pod.
112-
// Nevertheless, this code is ready for more than one object per resource
113-
resList, err := NewUnstructuredSliceFromBytes(res)
114-
if err != nil {
174+
func (km *Manager) Kustomize(data []byte) ([]byte, error) {
175+
// parse the resource to kustomize
176+
decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader(data), 1024)
177+
var resource *unstructured.Unstructured
178+
if err := decoder.Decode(&resource); err != nil {
115179
return nil, err
116180
}
117181

118-
// create a list of resource and corresponding patches
119-
var resources, patches UnstructuredSlice
120-
for _, r := range resList {
121-
resources = append(resources, r)
122-
123-
resourcePatches := km.us.FilterResource(r.GroupVersionKind(), r.GetNamespace(), r.GetName())
124-
125-
if len(resourcePatches) > 0 {
126-
fmt.Printf("[kustomize] Applying %d patches to %s Resource=%s/%s\n", len(resourcePatches), r.GroupVersionKind(), r.GetNamespace(), r.GetName())
127-
patches = append(patches, resourcePatches...)
128-
}
129-
}
182+
// get patches corresponding to this resource
183+
strategicMerge := km.strategicMergePatches.filterByResource(resource)
184+
json6902 := km.json6902Patches.filterByResource(resource)
130185

131186
// if there are no patches, for the target resources, exit
132-
if len(patches) == 0 {
133-
return res, nil
187+
if len(strategicMerge)+len(json6902) == 0 {
188+
return data, nil
134189
}
135190

191+
fmt.Printf("[kustomize] Applying %d patches to %s Resource=%s/%s\n", len(strategicMerge)+len(json6902), resource.GroupVersionKind(), resource.GetNamespace(), resource.GetName())
192+
136193
// create an in memory fs to use for the kustomization
137194
memFS := fs.MakeFakeFS()
138195

139-
var kustomization bytes.Buffer
140196
fakeDir := "/"
141197
// for Windows we need this to be a drive because kustomize uses filepath.Abs()
142198
// which will add a drive letter if there is none. which drive letter is
@@ -145,33 +201,44 @@ func (km *Manager) Kustomize(res []byte) ([]byte, error) {
145201
fakeDir = `C:\`
146202
}
147203

148-
// write resources and patches to the in memory fs, generate the kustomization.yaml
149-
// that ties everything together
150-
kustomization.WriteString("resources:\n")
151-
for i, r := range resources {
152-
b, err := r.MarshalJSON()
204+
// writes the resource to a file in the temp file system
205+
b, err := yaml.Marshal(resource)
206+
if err != nil {
207+
return nil, err
208+
}
209+
name := "resource.yaml"
210+
_ = memFS.WriteFile(filepath.Join(fakeDir, name), b)
211+
212+
km.kustomizationFile.Resources = []string{name}
213+
214+
// writes strategic merge patches to files in the temp file system
215+
km.kustomizationFile.PatchesStrategicMerge = []patch.StrategicMerge{}
216+
for i, p := range strategicMerge {
217+
b, err := yaml.Marshal(p)
153218
if err != nil {
154219
return nil, err
155220
}
156-
157-
name := fmt.Sprintf("resource-%d.json", i)
221+
name := fmt.Sprintf("patch-%d.yaml", i)
158222
_ = memFS.WriteFile(filepath.Join(fakeDir, name), b)
159-
fmt.Fprintf(&kustomization, " - %s\n", name)
223+
224+
km.kustomizationFile.PatchesStrategicMerge = append(km.kustomizationFile.PatchesStrategicMerge, patch.StrategicMerge(name))
160225
}
161226

162-
kustomization.WriteString("patches:\n")
163-
for i, p := range patches {
164-
b, err := p.MarshalJSON()
165-
if err != nil {
166-
return nil, err
167-
}
227+
// writes json6902 patches to files in the temp file system
228+
km.kustomizationFile.PatchesJson6902 = []patch.Json6902{}
229+
for i, p := range json6902 {
230+
name := fmt.Sprintf("patchjson-%d.yaml", i)
231+
_ = memFS.WriteFile(filepath.Join(fakeDir, name), []byte(p.Patch))
168232

169-
name := fmt.Sprintf("patch-%d.json", i)
170-
_ = memFS.WriteFile(filepath.Join(fakeDir, name), b)
171-
fmt.Fprintf(&kustomization, " - %s\n", name)
233+
km.kustomizationFile.PatchesJson6902 = append(km.kustomizationFile.PatchesJson6902, patch.Json6902{Target: p.Target, Path: name})
172234
}
173235

174-
memFS.WriteFile(filepath.Join(fakeDir, "kustomization.yaml"), kustomization.Bytes())
236+
// writes the kustomization file to the temp file system
237+
kbytes, err := yaml.Marshal(km.kustomizationFile)
238+
if err != nil {
239+
return nil, err
240+
}
241+
memFS.WriteFile(filepath.Join(fakeDir, "kustomization.yaml"), kbytes)
175242

176243
// Finally customize the target resource
177244
var out bytes.Buffer

0 commit comments

Comments
 (0)