Skip to content

Commit 7b3b9b6

Browse files
author
Hannes Hörl
committed
Allow data values for specific template steps
Values used in pkgis can be configured to apply to specific template steps of the pkg. The default behaviour stays the same, so that no specific step is defined for data values, they apply to the first one that takes values. Some implementation details: We classify the pkg's template steps, currently supported classes are ytt, helm, cue, and "valueable" which is either of the previously mentioned. When it is time to apply the data values' secrets to the template steps of the reulting app, we either find the template step that is explicitly configured, or we find the first one that takes data values (the first "valueable" step). This flips the the approach around: previously we iterated through all the pkg's template steps and applied the data values, if we have not. Now we iterate over the the data values from the pkgi and apply them to the appropriate template step. If the user configured data values for invalid steps (e.g. a step that does not exist, or a step that does not take any data values) we produce a human readable error and therefore block the reconciliation. Signed-off-by: Hannes Hörl <[email protected]>
1 parent d89101e commit 7b3b9b6

File tree

5 files changed

+409
-223
lines changed

5 files changed

+409
-223
lines changed

config/config/crds.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,10 @@ spec:
15581558
name:
15591559
type: string
15601560
type: object
1561+
templateSteps:
1562+
items:
1563+
type: integer
1564+
type: array
15611565
type: object
15621566
type: array
15631567
type: object

pkg/apis/packaging/v1alpha1/package_install.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ type PackageRef struct {
9191
type PackageInstallValues struct {
9292
// +optional
9393
SecretRef *PackageInstallValuesSecretRef `json:"secretRef,omitempty"`
94+
// +optional
95+
TemplateSteps []int `json:"templateSteps,omitempty"`
9496
}
9597

9698
type PackageInstallValuesSecretRef struct {

pkg/apis/packaging/v1alpha1/zz_generated.deepcopy.go

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

pkg/packageinstall/app.go

Lines changed: 235 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"sort"
99
"strings"
10+
"sync"
1011

1112
"github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1"
1213
kcv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1"
@@ -27,6 +28,7 @@ const (
2728
ExtYttPathsFromSecretNameAnnKey = "ext.packaging.carvel.dev/ytt-paths-from-secret-name"
2829
ExtHelmPathsFromSecretNameAnnKey = "ext.packaging.carvel.dev/helm-template-values-from-secret-name"
2930

31+
// ExtYttDataValuesOverlaysAnnKey if set, adds the pkgi's values secrets as overlays/paths, not as values, to the app
3032
ExtYttDataValuesOverlaysAnnKey = "ext.packaging.carvel.dev/ytt-data-values-overlays"
3133

3234
ExtFetchSecretNameAnnKeyFmt = "ext.packaging.carvel.dev/fetch-%d-secret-name"
@@ -98,88 +100,213 @@ func NewApp(existingApp *v1alpha1.App, pkgInstall *pkgingv1alpha1.PackageInstall
98100
}
99101
}
100102

101-
valuesApplied := false
102-
yttPathsApplied := false
103-
helmPathsApplied := false
104-
105-
for i, templateStep := range desiredApp.Spec.Template {
106-
if templateStep.HelmTemplate != nil {
107-
if !helmPathsApplied {
108-
helmPathsApplied = true
109-
110-
if _, found := pkgInstall.Annotations[HelmTemplateOverlayNameKey]; found {
111-
templateStep.HelmTemplate.Name = pkgInstall.Annotations[HelmTemplateOverlayNameKey]
112-
}
113-
if _, found := pkgInstall.Annotations[HelmTemplateOverlayNameSpaceKey]; found {
114-
templateStep.HelmTemplate.Namespace = pkgInstall.Annotations[HelmTemplateOverlayNameSpaceKey]
115-
}
116-
for _, secretName := range secretNamesFromAnn(pkgInstall, ExtHelmPathsFromSecretNameAnnKey) {
117-
templateStep.HelmTemplate.ValuesFrom = append(templateStep.HelmTemplate.ValuesFrom, kcv1alpha1.AppTemplateValuesSource{
118-
SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{
119-
Name: secretName,
120-
},
121-
})
122-
}
123-
}
124-
if !valuesApplied {
125-
valuesApplied = true
126-
127-
for _, value := range pkgInstall.Spec.Values {
128-
templateStep.HelmTemplate.ValuesFrom = append(templateStep.HelmTemplate.ValuesFrom, kcv1alpha1.AppTemplateValuesSource{
129-
SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{
130-
Name: value.SecretRef.Name,
131-
},
132-
})
133-
}
134-
}
103+
templatesPatcher := templateStepsPatcher{
104+
yttPatcher: &yttStepPatcher{
105+
addValuesAsInlinePaths: pkgiHasAnnotation(pkgInstall, ExtYttDataValuesOverlaysAnnKey),
106+
additionalPaths: secretNamesFromAnn(pkgInstall, ExtYttPathsFromSecretNameAnnKey),
107+
},
108+
helmPatcher: &helmStepPatcher{
109+
additionalPaths: secretNamesFromAnn(pkgInstall, ExtHelmPathsFromSecretNameAnnKey),
110+
name: pkgiAnnotationValue(pkgInstall, HelmTemplateOverlayNameKey),
111+
namespace: pkgiAnnotationValue(pkgInstall, HelmTemplateOverlayNameSpaceKey),
112+
},
113+
cuePatcher: &cueStepPatcher{},
114+
115+
templateSteps: desiredApp.Spec.Template,
116+
values: pkgInstall.Spec.Values,
117+
}
118+
119+
if err := templatesPatcher.patch(); err != nil {
120+
return &v1alpha1.App{}, err
121+
}
122+
123+
return desiredApp, nil
124+
}
125+
126+
type stepClass string
127+
128+
const (
129+
// anything that can take values
130+
stepClassValueable stepClass = "valueable"
131+
// only helm template steps
132+
stepClassHelm stepClass = "helm"
133+
// only ytt template steps
134+
stepClassYtt stepClass = "ytt"
135+
// only cue template steps
136+
stepClassCue stepClass = "cue"
137+
)
138+
139+
type yttStepPatcher struct {
140+
addValuesAsInlinePaths bool // TODO: support multiple ytt steps
141+
additionalPaths []string // TODO: support multiple ytt steps
142+
}
143+
144+
func (yp *yttStepPatcher) addValues(yttStep *kcv1alpha1.AppTemplateYtt, value pkgingv1alpha1.PackageInstallValues) {
145+
if yp.addValuesAsInlinePaths {
146+
addSecretAsInlinePath(&yttStep.Inline, value.SecretRef.Name)
147+
} else {
148+
addSecretAsValueSource(&yttStep.ValuesFrom, value.SecretRef.Name)
149+
}
150+
}
151+
152+
func (yp *yttStepPatcher) addPaths(yttStep *kcv1alpha1.AppTemplateYtt) {
153+
for _, secretName := range yp.additionalPaths {
154+
addSecretAsInlinePath(&yttStep.Inline, secretName)
155+
}
156+
}
157+
158+
type helmStepPatcher struct {
159+
additionalPaths []string
160+
name string // TODO: support multiple helm steps
161+
namespace string // TODO: support multiple helm steps
162+
}
163+
164+
func (hp *helmStepPatcher) addValues(helmStep *kcv1alpha1.AppTemplateHelmTemplate, value pkgingv1alpha1.PackageInstallValues) {
165+
addSecretAsValueSource(&helmStep.ValuesFrom, value.SecretRef.Name)
166+
}
167+
168+
func (hp *helmStepPatcher) addPaths(helmStep *kcv1alpha1.AppTemplateHelmTemplate) {
169+
for _, secretName := range hp.additionalPaths {
170+
addSecretAsValueSource(&helmStep.ValuesFrom, secretName)
171+
}
172+
}
173+
174+
func (hp *helmStepPatcher) setNameAndNamespace(helmStep *kcv1alpha1.AppTemplateHelmTemplate) {
175+
if hp.name != "" {
176+
helmStep.Name = hp.name
177+
}
178+
if hp.namespace != "" {
179+
helmStep.Namespace = hp.namespace
180+
}
181+
}
182+
183+
type cueStepPatcher struct{}
184+
185+
func (cp *cueStepPatcher) addValues(cueStep *kcv1alpha1.AppTemplateCue, value pkgingv1alpha1.PackageInstallValues) {
186+
addSecretAsValueSource(&cueStep.ValuesFrom, value.SecretRef.Name)
187+
}
188+
189+
type templateStepsPatcher struct {
190+
templateSteps []kcv1alpha1.AppTemplate
191+
values []pkgingv1alpha1.PackageInstallValues
192+
193+
yttPatcher *yttStepPatcher
194+
helmPatcher *helmStepPatcher
195+
cuePatcher *cueStepPatcher
196+
197+
classifiedSteps [][]stepClass
198+
once sync.Once
199+
}
200+
201+
func (p *templateStepsPatcher) classifySteps() {
202+
p.classifiedSteps = make([][]stepClass, len(p.templateSteps))
203+
204+
for i, step := range p.templateSteps {
205+
classes := []stepClass{}
206+
207+
if step.HelmTemplate != nil {
208+
classes = append(classes, stepClassHelm, stepClassValueable)
209+
}
210+
if step.Ytt != nil {
211+
classes = append(classes, stepClassYtt, stepClassValueable)
135212
}
213+
if step.Cue != nil {
214+
classes = append(classes, stepClassCue, stepClassValueable)
215+
}
216+
217+
p.classifiedSteps[i] = classes
218+
}
219+
}
220+
221+
func (p *templateStepsPatcher) stepHasClass(stepIdx int, class stepClass) bool {
222+
p.once.Do(p.classifySteps)
223+
224+
for _, stepClass := range p.classifiedSteps[stepIdx] {
225+
if stepClass == class {
226+
return true
227+
}
228+
}
229+
230+
return false
231+
}
232+
233+
func (p *templateStepsPatcher) getClassifiedSteps(class stepClass) []int {
234+
p.once.Do(p.classifySteps)
136235

137-
if templateStep.Ytt != nil {
138-
if !yttPathsApplied {
139-
yttPathsApplied = true
140-
141-
for _, secretName := range secretNamesFromAnn(pkgInstall, ExtYttPathsFromSecretNameAnnKey) {
142-
if templateStep.Ytt.Inline == nil {
143-
templateStep.Ytt.Inline = &kcv1alpha1.AppFetchInline{}
144-
}
145-
templateStep.Ytt.Inline.PathsFrom = append(templateStep.Ytt.Inline.PathsFrom, kcv1alpha1.AppFetchInlineSource{
146-
SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{
147-
Name: secretName,
148-
},
149-
})
150-
}
236+
steps := []int{}
237+
for i := range p.classifiedSteps {
238+
if p.stepHasClass(i, class) {
239+
steps = append(steps, i)
240+
}
241+
}
242+
243+
return steps
244+
}
245+
246+
func (p *templateStepsPatcher) firstOf(class stepClass) (int, bool) {
247+
classifiedSteps := p.getClassifiedSteps(class)
248+
if len(classifiedSteps) < 1 {
249+
return 0, false
250+
}
251+
252+
return classifiedSteps[0], true
253+
}
254+
255+
func (p *templateStepsPatcher) defaultStepIdxs(stepIdxs []int, class stepClass) ([]int, error) {
256+
if len(stepIdxs) > 0 {
257+
return stepIdxs, nil
258+
}
259+
260+
first, ok := p.firstOf(class)
261+
if ok {
262+
return []int{first}, nil
263+
}
264+
265+
return []int{}, fmt.Errorf("no template step of class '%s' found", class)
266+
}
267+
268+
func (p *templateStepsPatcher) patch() error {
269+
for _, values := range p.values {
270+
stepIdxs, err := p.defaultStepIdxs(values.TemplateSteps, stepClassValueable)
271+
if err != nil {
272+
return err
273+
}
274+
275+
for _, stepIdx := range stepIdxs {
276+
if stepIdx < 0 || stepIdx >= len(p.templateSteps) {
277+
return fmt.Errorf("template step %d out of range", stepIdx)
278+
}
279+
if !p.stepHasClass(stepIdx, stepClassValueable) {
280+
return fmt.Errorf("template step %d does not support values", stepIdx)
151281
}
152282

153-
if !valuesApplied {
154-
valuesApplied = true
155-
156-
if _, found := pkgInstall.Annotations[ExtYttDataValuesOverlaysAnnKey]; found {
157-
if templateStep.Ytt.Inline == nil {
158-
templateStep.Ytt.Inline = &kcv1alpha1.AppFetchInline{}
159-
}
160-
for _, value := range pkgInstall.Spec.Values {
161-
templateStep.Ytt.Inline.PathsFrom = append(templateStep.Ytt.Inline.PathsFrom, kcv1alpha1.AppFetchInlineSource{
162-
SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{
163-
Name: value.SecretRef.Name,
164-
},
165-
})
166-
}
167-
} else {
168-
for _, value := range pkgInstall.Spec.Values {
169-
templateStep.Ytt.ValuesFrom = append(templateStep.Ytt.ValuesFrom, kcv1alpha1.AppTemplateValuesSource{
170-
SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{
171-
Name: value.SecretRef.Name,
172-
},
173-
})
174-
}
175-
}
283+
templateStep := p.templateSteps[stepIdx]
284+
285+
switch {
286+
case p.stepHasClass(stepIdx, stepClassYtt):
287+
p.yttPatcher.addValues(templateStep.Ytt, values)
288+
289+
case p.stepHasClass(stepIdx, stepClassHelm):
290+
p.helmPatcher.addValues(templateStep.HelmTemplate, values)
291+
292+
case p.stepHasClass(stepIdx, stepClassCue):
293+
p.cuePatcher.addValues(templateStep.Cue, values)
176294
}
177295
}
296+
}
178297

179-
desiredApp.Spec.Template[i] = templateStep
298+
for _, stepIdx := range p.getClassifiedSteps(stepClassYtt) {
299+
p.yttPatcher.addPaths(p.templateSteps[stepIdx].Ytt)
300+
break // TODO: support multiple ytt steps
301+
}
302+
for _, stepIdx := range p.getClassifiedSteps(stepClassHelm) {
303+
ts := p.templateSteps[stepIdx].HelmTemplate
304+
p.helmPatcher.addPaths(ts)
305+
p.helmPatcher.setNameAndNamespace(ts)
306+
break // TODO: support multiple helm steps
180307
}
181308

182-
return desiredApp, nil
309+
return nil
183310
}
184311

185312
func secretNamesFromAnn(installedPkg *pkgingv1alpha1.PackageInstall, annKey string) []string {
@@ -206,3 +333,38 @@ func secretNamesFromAnn(installedPkg *pkgingv1alpha1.PackageInstall, annKey stri
206333
}
207334
return result
208335
}
336+
337+
func pkgiAnnotationValue(pkgi *pkgingv1alpha1.PackageInstall, key string) string {
338+
if anno, found := pkgi.Annotations[key]; found {
339+
return anno
340+
}
341+
return ""
342+
}
343+
344+
func pkgiHasAnnotation(pkgi *pkgingv1alpha1.PackageInstall, key string) bool {
345+
_, found := pkgi.Annotations[key]
346+
return found
347+
}
348+
349+
// addSecretAsInlinePath adds a secret as an inline path to the provided inline
350+
// fetches. If the inline fetch is nil, it is initialized.
351+
func addSecretAsInlinePath(inline **kcv1alpha1.AppFetchInline, secretName string) {
352+
if *inline == nil {
353+
*inline = &kcv1alpha1.AppFetchInline{}
354+
}
355+
(*inline).PathsFrom = append((*inline).PathsFrom, kcv1alpha1.AppFetchInlineSource{
356+
SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{
357+
Name: secretName,
358+
},
359+
})
360+
}
361+
362+
// addSecretAsValueSource adds a secret as a value source to the provided
363+
// template values sources.
364+
func addSecretAsValueSource(values *[]kcv1alpha1.AppTemplateValuesSource, secretName string) {
365+
*values = append(*values, kcv1alpha1.AppTemplateValuesSource{
366+
SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{
367+
Name: secretName,
368+
},
369+
})
370+
}

0 commit comments

Comments
 (0)