Skip to content

Commit 763b5d5

Browse files
authored
add post-renderers (#399)
* prepare for adding post-renderers * go mod tidy * implement post renderers
1 parent 964a17b commit 763b5d5

8 files changed

Lines changed: 293 additions & 12 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.26.2
44

55
require (
66
github.com/Masterminds/sprig/v3 v3.3.0
7+
github.com/drone/envsubst v1.0.3
78
github.com/go-git/go-git v4.7.0+incompatible
89
github.com/go-logr/logr v1.4.3
910
github.com/gobwas/glob v0.2.3

go.sum

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
3131
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3232
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
3333
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34+
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
35+
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
3436
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
3537
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
3638
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
@@ -86,6 +88,7 @@ github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
8688
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
8789
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
8890
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
91+
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
8992
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
9093
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
9194
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
@@ -186,8 +189,6 @@ github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlT
186189
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
187190
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
188191
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
189-
github.com/sap/go-generics v0.2.53 h1:C5MltWIx6MxpgPxQJa2eupGm3Jim4jIfwm/2KOT1BQM=
190-
github.com/sap/go-generics v0.2.53/go.mod h1:xqWmp3jVLGNTmTuPjwdWgNk11pzi0zarA1iqV0om24c=
191192
github.com/sap/go-generics v0.2.54 h1:NbWwKMH4w6Gmts2ilvhmKthwMbzHjJn3zDeh4940T5Q=
192193
github.com/sap/go-generics v0.2.54/go.mod h1:xqWmp3jVLGNTmTuPjwdWgNk11pzi0zarA1iqV0om24c=
193194
github.com/sap/go-generics v0.2.57 h1:BTINpYYC3oNo8rhRCVwmQZ8wmsoLm6JzJqkE6zkQi80=
@@ -382,8 +383,6 @@ sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7
382383
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
383384
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
384385
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
385-
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
386-
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
387386
sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo=
388387
sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
389388
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=

internal/kustomize/kustomization.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"k8s.io/client-go/discovery"
2929
"sigs.k8s.io/controller-runtime/pkg/client"
3030
"sigs.k8s.io/kustomize/api/konfig"
31-
kustypes "sigs.k8s.io/kustomize/api/types"
31+
kusttypes "sigs.k8s.io/kustomize/api/types"
3232
kustfsys "sigs.k8s.io/kustomize/kyaml/filesys"
3333
kyaml "sigs.k8s.io/yaml"
3434

@@ -410,10 +410,10 @@ func generateKustomization(fsys kustfsys.FileSystem, kustomizationPath string) (
410410
}
411411
}
412412

413-
kustomization := kustypes.Kustomization{
414-
TypeMeta: kustypes.TypeMeta{
415-
APIVersion: kustypes.KustomizationVersion,
416-
Kind: kustypes.KustomizationKind,
413+
kustomization := kusttypes.Kustomization{
414+
TypeMeta: kusttypes.TypeMeta{
415+
APIVersion: kusttypes.KustomizationVersion,
416+
Kind: kusttypes.KustomizationKind,
417417
},
418418
Resources: resources,
419419
}

pkg/manifests/kustomize/generator.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
1616
"sigs.k8s.io/controller-runtime/pkg/client"
1717
"sigs.k8s.io/kustomize/api/krusty"
18-
kustypes "sigs.k8s.io/kustomize/api/types"
18+
kusttypes "sigs.k8s.io/kustomize/api/types"
1919
kustfsys "sigs.k8s.io/kustomize/kyaml/filesys"
2020

2121
"github.com/sap/component-operator-runtime/internal/kustomize"
@@ -65,8 +65,8 @@ func NewKustomizeGenerator(fsys fs.FS, kustomizationPath string, _ client.Client
6565
}
6666

6767
kustomizerOptions := &krusty.Options{
68-
LoadRestrictions: kustypes.LoadRestrictionsNone,
69-
PluginConfig: kustypes.DisabledPluginConfig(),
68+
LoadRestrictions: kusttypes.LoadRestrictionsNone,
69+
PluginConfig: kusttypes.DisabledPluginConfig(),
7070
}
7171
kustomizer := krusty.MakeKustomizer(kustomizerOptions)
7272

pkg/manifests/transformer.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,24 @@ package manifests
77

88
import (
99
"bytes"
10+
"io"
1011
"io/fs"
1112
"os"
1213
"path/filepath"
1314
"text/template"
1415

1516
"github.com/Masterminds/sprig/v3"
17+
"github.com/drone/envsubst"
18+
"github.com/sap/go-generics/slices"
1619

20+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
21+
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
"sigs.k8s.io/kustomize/api/konfig"
24+
"sigs.k8s.io/kustomize/api/krusty"
25+
kusttypes "sigs.k8s.io/kustomize/api/types"
26+
kustfsys "sigs.k8s.io/kustomize/kyaml/filesys"
27+
"sigs.k8s.io/kustomize/kyaml/resid"
1728
kyaml "sigs.k8s.io/yaml"
1829

1930
"github.com/sap/component-operator-runtime/internal/templatex"
@@ -70,3 +81,162 @@ func (t *TemplateParameterTransformer) TransformParameters(namespace string, nam
7081
}
7182
return transformedParameters, nil
7283
}
84+
85+
type SubstitutionObjectTransformer struct {
86+
substitutions map[string]string
87+
selector types.Selector[client.Object]
88+
}
89+
90+
var _ ObjectTransformer = &SubstitutionObjectTransformer{}
91+
92+
func NewSubstitutionObjectTransformer(substitutions map[string]string, selector types.Selector[client.Object]) (*SubstitutionObjectTransformer, error) {
93+
return &SubstitutionObjectTransformer{
94+
substitutions: substitutions,
95+
selector: selector,
96+
}, nil
97+
}
98+
99+
func (t *SubstitutionObjectTransformer) TransformObjects(namespace string, name string, objects []client.Object) ([]client.Object, error) {
100+
if len(t.substitutions) == 0 {
101+
return objects, nil
102+
}
103+
104+
var transformedObjects []client.Object
105+
106+
for _, object := range objects {
107+
if !t.selector.Matches(object) {
108+
transformedObjects = append(transformedObjects, object)
109+
continue
110+
}
111+
rawObject, err := kyaml.Marshal(object)
112+
if err != nil {
113+
return nil, err
114+
}
115+
stringObject := string(rawObject)
116+
stringObject, err = envsubst.Eval(stringObject, func(s string) string {
117+
return t.substitutions[s]
118+
})
119+
if err != nil {
120+
return nil, err
121+
}
122+
rawObject = []byte(stringObject)
123+
if err := kyaml.Unmarshal(rawObject, object); err != nil {
124+
return nil, err
125+
}
126+
transformedObjects = append(transformedObjects, object)
127+
}
128+
129+
return transformedObjects, nil
130+
}
131+
132+
type KustomizeObjectTransformer struct {
133+
patches []KustomizePatch
134+
images []KustomizeImage
135+
kustomizer *krusty.Kustomizer
136+
}
137+
138+
var _ ObjectTransformer = &KustomizeObjectTransformer{}
139+
140+
func NewKustomizeObjectTransformer(patches []KustomizePatch, images []KustomizeImage) (*KustomizeObjectTransformer, error) {
141+
kustomizerOptions := &krusty.Options{
142+
LoadRestrictions: kusttypes.LoadRestrictionsNone,
143+
PluginConfig: kusttypes.DisabledPluginConfig(),
144+
}
145+
kustomizer := krusty.MakeKustomizer(kustomizerOptions)
146+
147+
return &KustomizeObjectTransformer{
148+
patches: patches,
149+
images: images,
150+
kustomizer: kustomizer,
151+
}, nil
152+
}
153+
154+
func (t *KustomizeObjectTransformer) TransformObjects(namespace string, name string, objects []client.Object) ([]client.Object, error) {
155+
const resourcePath = "objects.yaml"
156+
157+
fsys := kustfsys.MakeFsInMemory()
158+
159+
var buf bytes.Buffer
160+
for _, object := range objects {
161+
rawObject, err := kyaml.Marshal(object)
162+
if err != nil {
163+
return nil, err
164+
}
165+
// note: no need to handle write errors below, they are always nil (see documentation)
166+
buf.WriteString("---\n")
167+
buf.Write(rawObject)
168+
}
169+
if err := fsys.WriteFile(resourcePath, buf.Bytes()); err != nil {
170+
return nil, err
171+
}
172+
173+
kustomization := kusttypes.Kustomization{
174+
TypeMeta: kusttypes.TypeMeta{
175+
APIVersion: kusttypes.KustomizationVersion,
176+
Kind: kusttypes.KustomizationKind,
177+
},
178+
Resources: []string{resourcePath},
179+
Images: slices.Collect(t.images, func(i KustomizeImage) kusttypes.Image {
180+
return kusttypes.Image{
181+
Name: i.Name,
182+
NewName: i.NewName,
183+
NewTag: i.NewTag,
184+
Digest: i.Digest,
185+
}
186+
}),
187+
Patches: slices.Collect(t.patches, func(p KustomizePatch) kusttypes.Patch {
188+
return kusttypes.Patch{
189+
Patch: p.Patch,
190+
Target: &kusttypes.Selector{
191+
ResId: resid.ResId{
192+
Gvk: resid.Gvk{
193+
Group: p.Target.Group,
194+
Version: p.Target.Version,
195+
Kind: p.Target.Kind,
196+
},
197+
Name: p.Target.Name,
198+
Namespace: p.Target.Namespace,
199+
},
200+
LabelSelector: p.Target.LabelSelector,
201+
AnnotationSelector: p.Target.AnnotationSelector,
202+
},
203+
}
204+
}),
205+
}
206+
rawKustomization, err := kyaml.Marshal(kustomization)
207+
if err != nil {
208+
return nil, err
209+
}
210+
if err := fsys.WriteFile(konfig.DefaultKustomizationFileName(), rawKustomization); err != nil {
211+
return nil, err
212+
}
213+
214+
resmap, err := t.kustomizer.Run(fsys, ".")
215+
if err != nil {
216+
return nil, err
217+
}
218+
219+
raw, err := resmap.AsYaml()
220+
if err != nil {
221+
return nil, err
222+
}
223+
224+
var transformedObjects []client.Object
225+
226+
decoder := utilyaml.NewYAMLToJSONDecoder(bytes.NewBuffer(raw))
227+
for {
228+
object := &unstructured.Unstructured{}
229+
if err := decoder.Decode(&object.Object); err != nil {
230+
if err == io.EOF {
231+
break
232+
}
233+
return nil, err
234+
}
235+
if object.Object == nil {
236+
continue
237+
}
238+
transformedObjects = append(transformedObjects, object)
239+
}
240+
241+
return transformedObjects, nil
242+
}

pkg/manifests/types.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,34 @@ type ObjectTransformer interface {
4747
type Decryptor interface {
4848
Decrypt(input []byte, path string) ([]byte, error)
4949
}
50+
51+
// +kubebuilder:object:generate=true
52+
53+
// Kustomize patch specification, basically a subset of sigs.k8s.io/kustomize/api/types#Patch
54+
type KustomizePatch struct {
55+
Patch string `json:"patch,omitempty"`
56+
Target *KustomizeSelector `json:"target,omitempty"`
57+
}
58+
59+
// +kubebuilder:object:generate=true
60+
61+
// Kustomize object selector; corresponds to sigs.k8s.io/kustomize/api/types#Selector
62+
type KustomizeSelector struct {
63+
Group string `json:"group,omitempty"`
64+
Version string `json:"version,omitempty"`
65+
Kind string `json:"kind,omitempty"`
66+
Name string `json:"name,omitempty"`
67+
Namespace string `json:"namespace,omitempty"`
68+
AnnotationSelector string `json:"annotationSelector,omitempty"`
69+
LabelSelector string `json:"labelSelector,omitempty"`
70+
}
71+
72+
// +kubebuilder:object:generate=true
73+
74+
// Kustomize image modifier specification, basically a subset of sigs.k8s.io/kustomize/api/types#Image
75+
type KustomizeImage struct {
76+
Name string `json:"name"`
77+
NewName string `json:"newName,omitempty"`
78+
NewTag string `json:"newTag,omitempty"`
79+
Digest string `json:"digest,omitempty"`
80+
}

pkg/manifests/zz_generated.deepcopy.go

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/types/selector.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator-runtime contributors
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package types
7+
8+
type Selector[T any] interface {
9+
Matches(object T) bool
10+
}
11+
12+
type SelectorFunc[T any] func(object T) bool
13+
14+
func (s SelectorFunc[T]) Matches(object T) bool {
15+
return s(object)
16+
}
17+
18+
var _ Selector[any] = SelectorFunc[any](nil)

0 commit comments

Comments
 (0)