Skip to content

Commit 73eea6f

Browse files
authored
Merge pull request #15 from onmetal/feature/kustomizeutils
Implement kustomizeutils
2 parents 2fa6f77 + fcf672e commit 73eea6f

File tree

8 files changed

+365
-1
lines changed

8 files changed

+365
-1
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ checklicense: ## Check that every file has a license header present.
3939
go run github.com/google/addlicense -check -c 'OnMetal authors' **/*.go
4040

4141
.PHONY: check
42-
check: generate test lint checklicense ## Execute multiple checks.
42+
check: generate lint addlicense test ## Execute multiple checks.
4343

go.mod

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,41 @@ require (
1111
k8s.io/apimachinery v0.22.3
1212
k8s.io/client-go v0.22.3
1313
sigs.k8s.io/controller-runtime v0.10.2
14+
sigs.k8s.io/kustomize/api v0.10.0
15+
sigs.k8s.io/kustomize/kyaml v0.12.0
1416
)
1517

1618
require (
19+
github.com/PuerkitoBio/purell v1.1.1 // indirect
20+
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
1721
github.com/bmatcuk/doublestar/v4 v4.0.2 // indirect
1822
github.com/davecgh/go-spew v1.1.1 // indirect
1923
github.com/evanphx/json-patch v4.11.0+incompatible // indirect
2024
github.com/fsnotify/fsnotify v1.4.9 // indirect
25+
github.com/go-errors/errors v1.0.1 // indirect
2126
github.com/go-logr/logr v0.4.0 // indirect
27+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
28+
github.com/go-openapi/jsonreference v0.19.5 // indirect
29+
github.com/go-openapi/swag v0.19.14 // indirect
2230
github.com/gogo/protobuf v1.3.2 // indirect
2331
github.com/golang/protobuf v1.5.2 // indirect
2432
github.com/google/go-cmp v0.5.5 // indirect
2533
github.com/google/gofuzz v1.1.0 // indirect
34+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
2635
github.com/googleapis/gnostic v0.5.5 // indirect
36+
github.com/imdario/mergo v0.3.12 // indirect
37+
github.com/josharian/intern v1.0.0 // indirect
2738
github.com/json-iterator/go v1.1.11 // indirect
39+
github.com/mailru/easyjson v0.7.6 // indirect
2840
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2941
github.com/modern-go/reflect2 v1.0.1 // indirect
42+
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
3043
github.com/nxadm/tail v1.4.8 // indirect
3144
github.com/pkg/errors v0.9.1 // indirect
45+
github.com/pmezard/go-difflib v1.0.0 // indirect
46+
github.com/stretchr/testify v1.7.0 // indirect
47+
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
48+
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
3249
golang.org/x/mod v0.5.0 // indirect
3350
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
3451
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect

go.sum

Lines changed: 38 additions & 0 deletions
Large diffs are not rendered by default.

kustomizeutils/kustomizeutils.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2021 OnMetal authors
2+
//
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+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package kustomizeutils
16+
17+
import (
18+
"fmt"
19+
20+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
21+
22+
"k8s.io/apimachinery/pkg/api/meta"
23+
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/runtime/schema"
25+
"sigs.k8s.io/kustomize/api/krusty"
26+
"sigs.k8s.io/kustomize/api/resmap"
27+
"sigs.k8s.io/kustomize/api/resource"
28+
"sigs.k8s.io/kustomize/api/types"
29+
"sigs.k8s.io/kustomize/kyaml/filesys"
30+
"sigs.k8s.io/kustomize/kyaml/yaml"
31+
)
32+
33+
// BuildKustomization is a shorthand for creating an in-memory directory, creating 'kustomization.yaml' with the
34+
// yaml-encoded contents of the given types.Kustomization in it and running a krusty.Kustomizer on the directory.
35+
// This is useful for quickly using types from remote repositories via Kustomize.
36+
func BuildKustomization(kustomization types.Kustomization) (resmap.ResMap, error) {
37+
fs := filesys.MakeEmptyDirInMemory()
38+
data, err := yaml.Marshal(kustomization)
39+
if err != nil {
40+
return nil, fmt.Errorf("could not marshal kustomization: %w", err)
41+
}
42+
43+
if err := fs.WriteFile("kustomization.yaml", data); err != nil {
44+
return nil, fmt.Errorf("could not write kustomization: %w", err)
45+
}
46+
47+
kustomizer := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
48+
return kustomizer.Run(fs, ".")
49+
}
50+
51+
// BuildKustomizationIntoList is a shorthand for BuildKustomization + DecodeResMapIntoList.
52+
func BuildKustomizationIntoList(decoder runtime.Decoder, kustomization types.Kustomization, into runtime.Object) error {
53+
resMap, err := BuildKustomization(kustomization)
54+
if err != nil {
55+
return fmt.Errorf("error building kustomization: %w", err)
56+
}
57+
58+
if err := DecodeResMapIntoList(decoder, resMap, into); err != nil {
59+
return fmt.Errorf("error decoding resmap into list: %w", err)
60+
}
61+
return nil
62+
}
63+
64+
// DecodeResource decodes a resource.Resource into a given runtime.Object, if given.
65+
// Shorthand for resource.Resource.MarshalJSON + runtime.Codec.Decode.
66+
func DecodeResource(decoder runtime.Decoder, res *resource.Resource, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
67+
data, err := res.MarshalJSON()
68+
if err != nil {
69+
return nil, nil, fmt.Errorf("could not marshal resource: %w", err)
70+
}
71+
72+
return decoder.Decode(data, defaults, into)
73+
}
74+
75+
// DecodeResMapObjects decodes the resmap.ResMap objects into a slice of runtime.Object.
76+
func DecodeResMapObjects(deccoder runtime.Decoder, resMap resmap.ResMap) ([]runtime.Object, error) {
77+
resources := resMap.Resources()
78+
res := make([]runtime.Object, 0, len(resources))
79+
for _, rsc := range resMap.Resources() {
80+
obj, _, err := DecodeResource(deccoder, rsc, nil, nil)
81+
if err != nil {
82+
return nil, fmt.Errorf("error decoding object: %w", err)
83+
}
84+
85+
res = append(res, obj)
86+
}
87+
return res, nil
88+
}
89+
90+
// DecodeResMapIntoList decodes a resmap.ResMap into a list represented by runtime.Object.
91+
func DecodeResMapIntoList(decoder runtime.Decoder, resMap resmap.ResMap, into runtime.Object) error {
92+
objs, err := DecodeResMapObjects(decoder, resMap)
93+
if err != nil {
94+
return fmt.Errorf("error decoding objects: %w", err)
95+
}
96+
97+
if err := meta.SetList(into, objs); err != nil {
98+
return fmt.Errorf("error setting list: %w", err)
99+
}
100+
return nil
101+
}
102+
103+
// DecodeResMapUnstructureds decodes a resmap.ResMap into a slice of unstructured.Unstructured.
104+
func DecodeResMapUnstructureds(resMap resmap.ResMap) ([]unstructured.Unstructured, error) {
105+
res := make([]unstructured.Unstructured, 0, resMap.Size())
106+
for _, rsc := range resMap.Resources() {
107+
data, err := rsc.MarshalJSON()
108+
if err != nil {
109+
return nil, fmt.Errorf("error marshaling resource to json: %w", err)
110+
}
111+
112+
obj := &unstructured.Unstructured{}
113+
if _, _, err := unstructured.UnstructuredJSONScheme.Decode(data, nil, obj); err != nil {
114+
return nil, fmt.Errorf("error decoding unstructured: %w", err)
115+
}
116+
res = append(res, *obj)
117+
}
118+
return res, nil
119+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2021 OnMetal authors
2+
//
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+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package kustomizeutils
16+
17+
import (
18+
"testing"
19+
20+
. "github.com/onsi/ginkgo"
21+
. "github.com/onsi/gomega"
22+
)
23+
24+
func TestKustomizeutils(t *testing.T) {
25+
RegisterFailHandler(Fail)
26+
RunSpecs(t, "Kustomizeutils Suite")
27+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright 2021 OnMetal authors
2+
//
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+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package kustomizeutils
16+
17+
import (
18+
"github.com/onmetal/controller-utils/testdata"
19+
. "github.com/onsi/ginkgo"
20+
. "github.com/onsi/gomega"
21+
corev1 "k8s.io/api/core/v1"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
24+
"k8s.io/client-go/kubernetes/scheme"
25+
"sigs.k8s.io/kustomize/api/hasher"
26+
"sigs.k8s.io/kustomize/api/resmap"
27+
"sigs.k8s.io/kustomize/api/resource"
28+
"sigs.k8s.io/kustomize/api/types"
29+
)
30+
31+
var _ = Describe("Kustomizeutils", func() {
32+
Describe("BuildKustomization", func() {
33+
It("should build the kustomization", func() {
34+
resMap, err := BuildKustomization(types.Kustomization{
35+
ConfigMapGenerator: []types.ConfigMapArgs{
36+
{
37+
GeneratorArgs: types.GeneratorArgs{
38+
Name: "my-config",
39+
KvPairSources: types.KvPairSources{
40+
LiteralSources: []string{"foo=bar"},
41+
},
42+
Options: &types.GeneratorOptions{
43+
DisableNameSuffixHash: true,
44+
},
45+
},
46+
},
47+
},
48+
})
49+
Expect(err).NotTo(HaveOccurred())
50+
Expect(resMap.Size()).To(Equal(1))
51+
resources := resMap.Resources()
52+
Expect(resources).To(HaveLen(1))
53+
resource := resources[0]
54+
data, err := resource.AsYAML()
55+
Expect(err).NotTo(HaveOccurred())
56+
Expect(string(data)).To(Equal(testdata.ConfigMapYAML))
57+
})
58+
})
59+
60+
Describe("BuildKustomizationIntoList", func() {
61+
It("should build the kustomization into a list", func() {
62+
list := &corev1.ConfigMapList{}
63+
Expect(BuildKustomizationIntoList(scheme.Codecs.UniversalDeserializer(), types.Kustomization{
64+
ConfigMapGenerator: []types.ConfigMapArgs{
65+
{
66+
GeneratorArgs: types.GeneratorArgs{
67+
Name: "my-config",
68+
KvPairSources: types.KvPairSources{
69+
LiteralSources: []string{"foo=bar"},
70+
},
71+
Options: &types.GeneratorOptions{
72+
DisableNameSuffixHash: true,
73+
},
74+
},
75+
},
76+
},
77+
}, list)).To(Succeed())
78+
Expect(list.Items).To(ConsistOf(corev1.ConfigMap{
79+
TypeMeta: metav1.TypeMeta{
80+
Kind: "ConfigMap",
81+
APIVersion: "v1",
82+
},
83+
ObjectMeta: metav1.ObjectMeta{
84+
Name: "my-config",
85+
},
86+
Data: map[string]string{"foo": "bar"},
87+
}))
88+
})
89+
})
90+
91+
Describe("DecodeResource", func() {
92+
It("should decode the resource into the object", func() {
93+
res, err := resource.NewFactory(&hasher.Hasher{}).FromBytes([]byte(testdata.ConfigMapYAML))
94+
Expect(err).NotTo(HaveOccurred())
95+
96+
cm := &corev1.ConfigMap{}
97+
_, _, err = DecodeResource(scheme.Codecs.UniversalDeserializer(), res, nil, cm)
98+
Expect(err).NotTo(HaveOccurred())
99+
Expect(cm).To(Equal(&corev1.ConfigMap{
100+
TypeMeta: metav1.TypeMeta{
101+
Kind: "ConfigMap",
102+
APIVersion: "v1",
103+
},
104+
ObjectMeta: metav1.ObjectMeta{
105+
Name: "my-config",
106+
},
107+
Data: map[string]string{"foo": "bar"},
108+
}))
109+
})
110+
})
111+
112+
Describe("DecodeResMapIntoList", func() {
113+
It("should decode the resmap into a list", func() {
114+
res, err := resource.NewFactory(&hasher.Hasher{}).FromBytes([]byte(testdata.ConfigMapYAML))
115+
Expect(err).NotTo(HaveOccurred())
116+
resMap := resmap.New()
117+
Expect(resMap.Append(res))
118+
119+
list := &corev1.ConfigMapList{}
120+
Expect(DecodeResMapIntoList(scheme.Codecs.UniversalDeserializer(), resMap, list)).To(Succeed())
121+
Expect(list.Items).To(ConsistOf(corev1.ConfigMap{
122+
TypeMeta: metav1.TypeMeta{
123+
Kind: "ConfigMap",
124+
APIVersion: "v1",
125+
},
126+
ObjectMeta: metav1.ObjectMeta{
127+
Name: "my-config",
128+
},
129+
Data: map[string]string{"foo": "bar"},
130+
}))
131+
})
132+
})
133+
134+
Describe("DecodeResMapUnstructureds", func() {
135+
res, err := resource.NewFactory(&hasher.Hasher{}).FromBytes([]byte(testdata.ConfigMapYAML))
136+
Expect(err).NotTo(HaveOccurred())
137+
resMap := resmap.New()
138+
Expect(resMap.Append(res))
139+
140+
unstructureds, err := DecodeResMapUnstructureds(resMap)
141+
Expect(err).NotTo(HaveOccurred())
142+
Expect(unstructureds).To(ConsistOf(unstructured.Unstructured{Object: map[string]interface{}{
143+
"kind": "ConfigMap",
144+
"apiVersion": "v1",
145+
"metadata": map[string]interface{}{
146+
"name": "my-config",
147+
},
148+
"data": map[string]interface{}{
149+
"foo": "bar",
150+
},
151+
}}))
152+
})
153+
})

testdata/cm.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: v1
2+
data:
3+
foo: bar
4+
kind: ConfigMap
5+
metadata:
6+
name: my-config

testdata/testdata.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ var (
2828
// ObjectsYAML is a yaml file containing multiple objects and empty documents.
2929
//go:embed objects.yaml
3030
ObjectsYAML []byte
31+
32+
// ConfigMapYAML is a yaml file containing a config map.
33+
//go:embed cm.yaml
34+
ConfigMapYAML string
3135
)
3236

3337
func Secret() *corev1.Secret {

0 commit comments

Comments
 (0)