diff --git a/config/config/crds.yml b/config/config/crds.yml index 62ee23a10..fe11e94dc 100644 --- a/config/config/crds.yml +++ b/config/config/crds.yml @@ -1558,6 +1558,10 @@ spec: name: type: string type: object + templateSteps: + items: + type: integer + type: array type: object type: array type: object diff --git a/examples/pkgi-with-config-and-values-per-step.yaml b/examples/pkgi-with-config-and-values-per-step.yaml new file mode 100644 index 000000000..6227f0c3f --- /dev/null +++ b/examples/pkgi-with-config-and-values-per-step.yaml @@ -0,0 +1,108 @@ +--- +apiVersion: data.packaging.carvel.dev/v1alpha1 +kind: Package +metadata: + name: thingamajig.acme.corp.0.0.1 +spec: + refName: thingamajig.acme.corp + version: 0.0.1 + template: + spec: + fetch: + - path: helm-chart + helmChart: + name: nginx + repository: + url: oci://registry-1.docker.io/bitnamicharts + version: 15.12.2 + - path: patcher + inline: + paths: + schema.yaml: | + #@data/values-schema + --- + #! These validations would fail if we were not able to add data + #! values to the ytt step + + #@schema/validation min_len=1 + existingServerBlockConfigmap: "" + #@schema/validation min_len=1 + customServerBlock: "" + server-block-cm.yaml: | + #@ load("@ytt:data", "data") + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: #@ data.values.existingServerBlockConfigmap + data: + custom-server-block.conf: #@ data.values.customServerBlock + template: + - helmTemplate: + path: helm-chart + - ytt: + paths: + - "-" + - patcher + deploy: + - kapp: {} + +--- +apiVersion: packaging.carvel.dev/v1alpha1 +kind: PackageInstall +metadata: + name: thingamajig + annotations: + ext.packaging.carvel.dev/helm-0-template-name: foobar +spec: + serviceAccountName: default-ns-sa + packageRef: + refName: thingamajig.acme.corp + versionSelection: + constraints: 0.0.1 + values: + - secretRef: + name: thingamajig-shared-values + templateSteps: [ 0 , 1 ] + - secretRef: + name: thingamajig-helm-values + templateSteps: [ 0 ] + - secretRef: + name: thingamajig-ytt-values + templateSteps: [ 1 ] + +--- +apiVersion: v1 +kind: Secret +metadata: + name: thingamajig-shared-values +stringData: + values.yaml: | + existingServerBlockConfigmap: custom-server-block + +--- +apiVersion: v1 +kind: Secret +metadata: + name: thingamajig-helm-values +stringData: + values.yaml: | + service: + type: ClusterIP + replicaCount: 2 + +--- +apiVersion: v1 +kind: Secret +metadata: + name: thingamajig-ytt-values +stringData: + values.yaml: | + customServerBlock: | + server { + listen 0.0.0.0:8080; + location / { + return 200 "hello from kapp-controller, helm, ytt and friends!"; + add_header Content-Type text/plain; + } + } diff --git a/hack/test-examples.sh b/hack/test-examples.sh index 8485477db..dec929d2a 100755 --- a/hack/test-examples.sh +++ b/hack/test-examples.sh @@ -33,6 +33,9 @@ time kapp delete -y -a simple-app-http time kapp deploy -y -a cue -f examples/cue.yml time kapp delete -y -a cue +time kapp deploy -y -a step-values-and-config -f examples/pkgi-with-config-and-values-per-step.yaml +time kapp delete -y -a step-values-and-config + kapp delete -y -a rbac echo EXTERNAL SUCCESS diff --git a/pkg/apis/packaging/v1alpha1/package_install.go b/pkg/apis/packaging/v1alpha1/package_install.go index e18419486..564f72573 100644 --- a/pkg/apis/packaging/v1alpha1/package_install.go +++ b/pkg/apis/packaging/v1alpha1/package_install.go @@ -91,6 +91,8 @@ type PackageRef struct { type PackageInstallValues struct { // +optional SecretRef *PackageInstallValuesSecretRef `json:"secretRef,omitempty"` + // +optional + TemplateSteps []int `json:"templateSteps,omitempty"` } type PackageInstallValuesSecretRef struct { diff --git a/pkg/apis/packaging/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/packaging/v1alpha1/zz_generated.deepcopy.go index 405ddc200..47811876b 100644 --- a/pkg/apis/packaging/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/packaging/v1alpha1/zz_generated.deepcopy.go @@ -136,6 +136,11 @@ func (in *PackageInstallValues) DeepCopyInto(out *PackageInstallValues) { *out = new(PackageInstallValuesSecretRef) **out = **in } + if in.TemplateSteps != nil { + in, out := &in.TemplateSteps, &out.TemplateSteps + *out = make([]int, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/packageinstall/app.go b/pkg/packageinstall/app.go index 23ce547d7..1a3aa77f8 100644 --- a/pkg/packageinstall/app.go +++ b/pkg/packageinstall/app.go @@ -7,6 +7,7 @@ import ( "fmt" "sort" "strings" + "sync" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" kcv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" @@ -14,20 +15,27 @@ import ( datapkgingv1alpha1 "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apiserver/apis/datapackaging/v1alpha1" "github.com/vmware-tanzu/carvel-kapp-controller/pkg/client/clientset/versioned/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) const ( ManuallyControlledAnnKey = "ext.packaging.carvel.dev/manually-controlled" - HelmTemplateOverlayNameKey = "ext.packaging.carvel.dev/helm-template-name" - HelmTemplateOverlayNameSpaceKey = "ext.packaging.carvel.dev/helm-template-namespace" + HelmTemplateOverlayNameKey = "ext.packaging.carvel.dev/helm-template-name" + HelmTemplateOverlayNameKeyFmt = "ext.packaging.carvel.dev/helm-%d-template-name" + HelmTemplateOverlayNameSpaceKey = "ext.packaging.carvel.dev/helm-template-namespace" + HelmTemplateOverlayNameSpaceKeyFmt = "ext.packaging.carvel.dev/helm-%d-template-namespace" // Resulting secret names are sorted deterministically by suffix - ExtYttPathsFromSecretNameAnnKey = "ext.packaging.carvel.dev/ytt-paths-from-secret-name" - ExtHelmPathsFromSecretNameAnnKey = "ext.packaging.carvel.dev/helm-template-values-from-secret-name" + ExtYttPathsFromSecretNameAnnKey = "ext.packaging.carvel.dev/ytt-paths-from-secret-name" + ExtYttPathsFromSecretNameAnnKeyFmt = "ext.packaging.carvel.dev/ytt-%d-paths-from-secret-name" + ExtHelmPathsFromSecretNameAnnKey = "ext.packaging.carvel.dev/helm-template-values-from-secret-name" + ExtHelmPathsFromSecretNameAnnKeyFmt = "ext.packaging.carvel.dev/helm-%d-template-values-from-secret-name" - ExtYttDataValuesOverlaysAnnKey = "ext.packaging.carvel.dev/ytt-data-values-overlays" + // ExtYttDataValuesOverlaysAnnKey if set, adds the pkgi's values secrets as overlays/paths, not as values, to the app + ExtYttDataValuesOverlaysAnnKey = "ext.packaging.carvel.dev/ytt-data-values-overlays" + ExtYttDataValuesOverlaysAnnKeyFmt = "ext.packaging.carvel.dev/ytt-%d-data-values-overlays" ExtFetchSecretNameAnnKeyFmt = "ext.packaging.carvel.dev/fetch-%d-secret-name" ) @@ -98,95 +106,236 @@ func NewApp(existingApp *v1alpha1.App, pkgInstall *pkgingv1alpha1.PackageInstall } } - valuesApplied := false - yttPathsApplied := false - helmPathsApplied := false + templatesPatcher := templateStepsPatcher{ + templateSteps: desiredApp.Spec.Template, + values: pkgInstall.Spec.Values, + annotations: pkgInstall.Annotations, + } + if err := templatesPatcher.patch(); err != nil { + return &v1alpha1.App{}, err + } - for i, templateStep := range desiredApp.Spec.Template { - if templateStep.HelmTemplate != nil { - if !helmPathsApplied { - helmPathsApplied = true + return desiredApp, nil +} - if _, found := pkgInstall.Annotations[HelmTemplateOverlayNameKey]; found { - templateStep.HelmTemplate.Name = pkgInstall.Annotations[HelmTemplateOverlayNameKey] - } - if _, found := pkgInstall.Annotations[HelmTemplateOverlayNameSpaceKey]; found { - templateStep.HelmTemplate.Namespace = pkgInstall.Annotations[HelmTemplateOverlayNameSpaceKey] - } - for _, secretName := range secretNamesFromAnn(pkgInstall, ExtHelmPathsFromSecretNameAnnKey) { - templateStep.HelmTemplate.ValuesFrom = append(templateStep.HelmTemplate.ValuesFrom, kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: secretName, - }, - }) - } - } - if !valuesApplied { - valuesApplied = true - - for _, value := range pkgInstall.Spec.Values { - templateStep.HelmTemplate.ValuesFrom = append(templateStep.HelmTemplate.ValuesFrom, kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: value.SecretRef.Name, - }, - }) - } - } +type ( + stepClass string + stepClasses = sets.Set[stepClass] +) + +const ( + // anything that can take values + stepClassTakesValues stepClass = "takesValues" + // only helm template steps + stepClassHelm stepClass = "helm" + // only ytt template steps + stepClassYtt stepClass = "ytt" + // only cue template steps + stepClassCue stepClass = "cue" +) + +type templateStepsPatcher struct { + templateSteps []kcv1alpha1.AppTemplate + values []pkgingv1alpha1.PackageInstallValues + annotations map[string]string + + classifiedSteps []stepClasses + once sync.Once +} + +func (p *templateStepsPatcher) classifySteps() { + p.classifiedSteps = make([]stepClasses, len(p.templateSteps)) + + for i, step := range p.templateSteps { + classes := stepClasses{} + + if step.HelmTemplate != nil { + classes.Insert(stepClassHelm, stepClassTakesValues) + } + if step.Ytt != nil { + classes.Insert(stepClassYtt, stepClassTakesValues) + } + if step.Cue != nil { + classes.Insert(stepClassCue, stepClassTakesValues) } - if templateStep.Ytt != nil { - if !yttPathsApplied { - yttPathsApplied = true + p.classifiedSteps[i] = classes + } +} - for _, secretName := range secretNamesFromAnn(pkgInstall, ExtYttPathsFromSecretNameAnnKey) { - if templateStep.Ytt.Inline == nil { - templateStep.Ytt.Inline = &kcv1alpha1.AppFetchInline{} - } - templateStep.Ytt.Inline.PathsFrom = append(templateStep.Ytt.Inline.PathsFrom, kcv1alpha1.AppFetchInlineSource{ - SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ - Name: secretName, - }, - }) - } +func (p *templateStepsPatcher) stepHasClass(stepIdx int, class stepClass) bool { + p.once.Do(p.classifySteps) + return p.classifiedSteps[stepIdx].Has(class) +} + +func (p *templateStepsPatcher) getClassifiedSteps(class stepClass) []int { + p.once.Do(p.classifySteps) + + steps := []int{} + for i := range p.classifiedSteps { + if p.stepHasClass(i, class) { + steps = append(steps, i) + } + } + + return steps +} + +func (p *templateStepsPatcher) firstOf(class stepClass) (int, bool) { + classifiedSteps := p.getClassifiedSteps(class) + if len(classifiedSteps) < 1 { + return 0, false + } + + return classifiedSteps[0], true +} + +func (p *templateStepsPatcher) defaultStepIdxs(stepIdxs []int, class stepClass) ([]int, error) { + if len(stepIdxs) > 0 { + return stepIdxs, nil + } + + first, ok := p.firstOf(class) + if ok { + return []int{first}, nil + } + + return []int{}, fmt.Errorf("no template step of class '%s' found", class) +} + +func (p *templateStepsPatcher) patch() error { + if err := p.patchFromValues(); err != nil { + return err + } + p.patchFromYttAnnotations() + p.patchFromHelmAnnotations() + + return nil +} + +// patchFromValues patches all template steps that take values with values from +// the packageInstall +func (p *templateStepsPatcher) patchFromValues() error { + for _, values := range p.values { + stepIdxs, err := p.defaultStepIdxs(values.TemplateSteps, stepClassTakesValues) + if err != nil { + return err + } + + for _, stepIdx := range stepIdxs { + if stepIdx < 0 || stepIdx >= len(p.templateSteps) { + return fmt.Errorf("template step %d out of range", stepIdx) + } + if !p.stepHasClass(stepIdx, stepClassTakesValues) { + return fmt.Errorf("template step %d does not support values", stepIdx) } - if !valuesApplied { - valuesApplied = true + templateStep := p.templateSteps[stepIdx] - if _, found := pkgInstall.Annotations[ExtYttDataValuesOverlaysAnnKey]; found { - if templateStep.Ytt.Inline == nil { - templateStep.Ytt.Inline = &kcv1alpha1.AppFetchInline{} - } - for _, value := range pkgInstall.Spec.Values { - templateStep.Ytt.Inline.PathsFrom = append(templateStep.Ytt.Inline.PathsFrom, kcv1alpha1.AppFetchInlineSource{ - SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ - Name: value.SecretRef.Name, - }, - }) + switch { + case p.stepHasClass(stepIdx, stepClassYtt): + // ytt is a bit special: when we find the indexed annotation (or the + // naked one for the first ytt step) to apply the values as inline + // paths on the app, we do so; else, by default, we apply the pkgi's + // values as values on the app. + valuesAsPath := false + if firstYttStepIdx, ok := p.firstOf(stepClassYtt); ok && stepIdx == firstYttStepIdx { + if _, ok := p.annotations[ExtYttDataValuesOverlaysAnnKey]; ok { + valuesAsPath = true } + } + if _, ok := p.annotations[fmt.Sprintf(ExtYttDataValuesOverlaysAnnKeyFmt, stepIdx)]; ok { + valuesAsPath = true + } + if valuesAsPath { + addSecretAsInlinePath(&templateStep.Ytt.Inline, values.SecretRef.Name) } else { - for _, value := range pkgInstall.Spec.Values { - templateStep.Ytt.ValuesFrom = append(templateStep.Ytt.ValuesFrom, kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: value.SecretRef.Name, - }, - }) - } + addSecretAsValueSource(&templateStep.Ytt.ValuesFrom, values.SecretRef.Name) } + + case p.stepHasClass(stepIdx, stepClassHelm): + addSecretAsValueSource(&templateStep.HelmTemplate.ValuesFrom, values.SecretRef.Name) + + case p.stepHasClass(stepIdx, stepClassCue): + addSecretAsValueSource(&templateStep.Cue.ValuesFrom, values.SecretRef.Name) } } + } + + return nil +} + +// patchFromYttAnnotations patches ytt template steps with values from +// annotations from the packageInstall +func (p *templateStepsPatcher) patchFromYttAnnotations() { + firstYttIdx, hasYtt := p.firstOf(stepClassYtt) - desiredApp.Spec.Template[i] = templateStep + if !hasYtt { + return } - return desiredApp, nil + patcher := func(ts *kcv1alpha1.AppTemplateYtt, pathsAnno string) { + for _, secretName := range secretNamesFromAnn(p.annotations, pathsAnno) { + addSecretAsInlinePath(&ts.Inline, secretName) + } + } + + for _, stepIdx := range p.getClassifiedSteps(stepClassYtt) { + ts := p.templateSteps[stepIdx].Ytt + + if stepIdx == firstYttIdx { + // annotations that are not indexed are only applied to the first ytt + // step, so that we are backwards compatible + patcher(ts, ExtYttPathsFromSecretNameAnnKey) + } + + patcher(ts, fmt.Sprintf(ExtYttPathsFromSecretNameAnnKeyFmt, stepIdx)) + } +} + +// patchFromHelmAnnotations patches helm template steps with values from +// annotations from the packageInstall +func (p *templateStepsPatcher) patchFromHelmAnnotations() { + firstHelmIdx, hasHelm := p.firstOf(stepClassHelm) + + if !hasHelm { + return + } + + patcher := func(ts *kcv1alpha1.AppTemplateHelmTemplate, nameAnno, namespaceAnno, pathsAnno string) { + if name, ok := p.annotations[nameAnno]; ok && name != "" { + ts.Name = name + } + if namespace, ok := p.annotations[namespaceAnno]; ok && namespace != "" { + ts.Namespace = namespace + } + for _, secretName := range secretNamesFromAnn(p.annotations, pathsAnno) { + addSecretAsValueSource(&ts.ValuesFrom, secretName) + } + } + + for _, stepIdx := range p.getClassifiedSteps(stepClassHelm) { + ts := p.templateSteps[stepIdx].HelmTemplate + + if stepIdx == firstHelmIdx { + // annotations that are not indexed are only applied to the first helm + // step, so that we are backwards compatible + patcher(ts, HelmTemplateOverlayNameKey, HelmTemplateOverlayNameSpaceKey, ExtHelmPathsFromSecretNameAnnKey) + } + + patcher(ts, + fmt.Sprintf(HelmTemplateOverlayNameKeyFmt, stepIdx), + fmt.Sprintf(HelmTemplateOverlayNameSpaceKeyFmt, stepIdx), + fmt.Sprintf(ExtHelmPathsFromSecretNameAnnKeyFmt, stepIdx), + ) + } } -func secretNamesFromAnn(installedPkg *pkgingv1alpha1.PackageInstall, annKey string) []string { +func secretNamesFromAnn(annotations map[string]string, annKey string) []string { var suffixes []string suffixToSecretName := map[string]string{} - for ann, secretName := range installedPkg.Annotations { + for ann, secretName := range annotations { if ann == annKey { suffix := "" suffixToSecretName[suffix] = secretName @@ -206,3 +355,26 @@ func secretNamesFromAnn(installedPkg *pkgingv1alpha1.PackageInstall, annKey stri } return result } + +// addSecretAsInlinePath adds a secret as an inline path to the provided inline +// fetches. If the inline fetch is nil, it is initialized. +func addSecretAsInlinePath(inline **kcv1alpha1.AppFetchInline, secretName string) { + if *inline == nil { + *inline = &kcv1alpha1.AppFetchInline{} + } + (*inline).PathsFrom = append((*inline).PathsFrom, kcv1alpha1.AppFetchInlineSource{ + SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ + Name: secretName, + }, + }) +} + +// addSecretAsValueSource adds a secret as a value source to the provided +// template values sources. +func addSecretAsValueSource(values *[]kcv1alpha1.AppTemplateValuesSource, secretName string) { + *values = append(*values, kcv1alpha1.AppTemplateValuesSource{ + SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ + Name: secretName, + }, + }) +} diff --git a/pkg/packageinstall/app_test.go b/pkg/packageinstall/app_test.go index f1c985e18..93927bd30 100644 --- a/pkg/packageinstall/app_test.go +++ b/pkg/packageinstall/app_test.go @@ -20,404 +20,6 @@ import ( // several tests below have no SyncPeriod set so they'll all use the same default. var defaultSyncPeriod metav1.Duration = metav1.Duration{Duration: 10 * time.Minute} -func TestAppExtPathsFromSecretNameAnn(t *testing.T) { - ipkg := &pkgingv1alpha1.PackageInstall{ - ObjectMeta: metav1.ObjectMeta{ - Name: "app", - Namespace: "default", - Annotations: map[string]string{ - "ext.packaging.carvel.dev/ytt-paths-from-secret-name": "ytt-no-suffix", - "ext.packaging.carvel.dev/ytt-paths-from-secret-name.4": "ytt-suffix-4", - "ext.packaging.carvel.dev/ytt-paths-from-secret-name.2": "ytt-suffix-2", - "ext.packaging.carvel.dev/ytt-paths-from-secret-name.text": "ytt-suffix-text", - "ext.packaging.carvel.dev/helm-template-values-from-secret-name": "helm-no-suffix", - "ext.packaging.carvel.dev/helm-template-values-from-secret-name.4": "helm-suffix-4", - "ext.packaging.carvel.dev/helm-template-values-from-secret-name.2": "helm-suffix-2", - "ext.packaging.carvel.dev/helm-template-values-from-secret-name.text": "helm-suffix-text", - }, - }, - } - - pkgVersion := datapkgingv1alpha1.Package{ - Spec: datapkgingv1alpha1.PackageSpec{ - RefName: "expec-pkg", - Version: "1.5.0", - Template: datapkgingv1alpha1.AppTemplateSpec{ - Spec: &kcv1alpha1.AppSpec{ - Template: []kcv1alpha1.AppTemplate{ - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - }, - }, - }, - }, - } - - app, err := packageinstall.NewApp(&kcv1alpha1.App{}, ipkg, pkgVersion, packageinstall.Opts{DefaultSyncPeriod: 10 * time.Minute}) - if err != nil { - t.Fatalf("Expected no err, but was: %s", err) - } - - expectedApp := &kcv1alpha1.App{ - Spec: kcv1alpha1.AppSpec{ - SyncPeriod: &defaultSyncPeriod, - Template: []kcv1alpha1.AppTemplate{ - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{ - ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "helm-no-suffix", - }, - }, - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "helm-suffix-2", - }, - }, - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "helm-suffix-4", - }, - }, - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "helm-suffix-text", - }, - }, - }, - }}, - // Second Helm template step is untouched - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, - - {Ytt: &kcv1alpha1.AppTemplateYtt{ - Inline: &kcv1alpha1.AppFetchInline{ - PathsFrom: []kcv1alpha1.AppFetchInlineSource{ - kcv1alpha1.AppFetchInlineSource{ - SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ - Name: "ytt-no-suffix", - }, - }, - kcv1alpha1.AppFetchInlineSource{ - SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ - Name: "ytt-suffix-2", - }, - }, - kcv1alpha1.AppFetchInlineSource{ - SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ - Name: "ytt-suffix-4", - }, - }, - kcv1alpha1.AppFetchInlineSource{ - SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ - Name: "ytt-suffix-text", - }, - }, - }, - }, - }}, - // Second ytt templating step is untouched - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - }, - }, - } - - // Not interesting in metadata in this test - app.ObjectMeta = metav1.ObjectMeta{} - - if !reflect.DeepEqual(expectedApp, app) { - bs, _ := yaml.Marshal(app) - t.Fatalf("App does not match expected app: (actual)\n%s", bs) - } -} -func TestAppHelmOverlaysFromAnn(t *testing.T) { - ipkg := &pkgingv1alpha1.PackageInstall{ - ObjectMeta: metav1.ObjectMeta{ - Name: "app", - Namespace: "default", - Annotations: map[string]string{ - "ext.packaging.carvel.dev/helm-template-name": "helm-new-name", - "ext.packaging.carvel.dev/helm-template-namespace": "helm-new-namespace", - }, - }, - Spec: pkgingv1alpha1.PackageInstallSpec{ - Values: []pkgingv1alpha1.PackageInstallValues{ - {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values1"}}, - {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values2"}}, - }, - }, - } - - pkgVersion := datapkgingv1alpha1.Package{ - Spec: datapkgingv1alpha1.PackageSpec{ - RefName: "expec-pkg", - Version: "1.5.0", - Template: datapkgingv1alpha1.AppTemplateSpec{ - Spec: &kcv1alpha1.AppSpec{ - Template: []kcv1alpha1.AppTemplate{ - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{ - Name: "helm-default-name", - Namespace: "helm-default-namespace", - }}, - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - }, - }, - }, - }, - } - - app, err := packageinstall.NewApp(&kcv1alpha1.App{}, ipkg, pkgVersion, packageinstall.Opts{DefaultSyncPeriod: 10 * time.Minute}) - if err != nil { - t.Fatalf("Expected no err, but was: %s", err) - } - - expectedApp := &kcv1alpha1.App{ - Spec: kcv1alpha1.AppSpec{ - SyncPeriod: &defaultSyncPeriod, - Template: []kcv1alpha1.AppTemplate{ - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{ - Name: "helm-new-name", - Namespace: "helm-new-namespace", - ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "values1", - }, - }, - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "values2", - }, - }, - }, - }}, - // Ytt template step is untouched - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - }, - }, - } - - // Not interesting in metadata in this test - app.ObjectMeta = metav1.ObjectMeta{} - - if !reflect.DeepEqual(expectedApp, app) { - bs, _ := yaml.Marshal(app) - t.Fatalf("App does not match expected app: (actual)\n%s", bs) - } -} - -func TestAppExtYttDataValuesOverlaysAnn(t *testing.T) { - ipkg := &pkgingv1alpha1.PackageInstall{ - ObjectMeta: metav1.ObjectMeta{ - Name: "app", - Namespace: "default", - Annotations: map[string]string{ - "ext.packaging.carvel.dev/ytt-data-values-overlays": "", - }, - }, - Spec: pkgingv1alpha1.PackageInstallSpec{ - Values: []pkgingv1alpha1.PackageInstallValues{ - {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values1"}}, - {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values2"}}, - }, - }, - } - - pkgVersion := datapkgingv1alpha1.Package{ - Spec: datapkgingv1alpha1.PackageSpec{ - RefName: "expec-pkg", - Version: "1.5.0", - Template: datapkgingv1alpha1.AppTemplateSpec{ - Spec: &kcv1alpha1.AppSpec{ - Template: []kcv1alpha1.AppTemplate{ - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - }, - }, - }, - }, - } - - app, err := packageinstall.NewApp(&kcv1alpha1.App{}, ipkg, pkgVersion, packageinstall.Opts{DefaultSyncPeriod: 10 * time.Minute}) - if err != nil { - t.Fatalf("Expected no err, but was: %s", err) - } - - expectedApp := &kcv1alpha1.App{ - Spec: kcv1alpha1.AppSpec{ - SyncPeriod: &defaultSyncPeriod, - Template: []kcv1alpha1.AppTemplate{ - {Ytt: &kcv1alpha1.AppTemplateYtt{ - Inline: &kcv1alpha1.AppFetchInline{ - PathsFrom: []kcv1alpha1.AppFetchInlineSource{ - kcv1alpha1.AppFetchInlineSource{ - SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ - Name: "values1", - }, - }, - kcv1alpha1.AppFetchInlineSource{ - SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{ - Name: "values2", - }, - }, - }, - }, - }}, - // Second ytt templating step is untouched - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - }, - }, - } - - // Not interesting in metadata in this test - app.ObjectMeta = metav1.ObjectMeta{} - - if !reflect.DeepEqual(expectedApp, app) { - bs, _ := yaml.Marshal(app) - t.Fatalf("App does not match expected app: (actual)\n%s", bs) - } -} - -func TestAppYttValues(t *testing.T) { - ipkg := &pkgingv1alpha1.PackageInstall{ - ObjectMeta: metav1.ObjectMeta{ - Name: "app", - Namespace: "default", - }, - Spec: pkgingv1alpha1.PackageInstallSpec{ - Values: []pkgingv1alpha1.PackageInstallValues{ - {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values1"}}, - {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values2"}}, - }, - }, - } - - pkgVersion := datapkgingv1alpha1.Package{ - Spec: datapkgingv1alpha1.PackageSpec{ - RefName: "expec-pkg", - Version: "1.5.0", - Template: datapkgingv1alpha1.AppTemplateSpec{ - Spec: &kcv1alpha1.AppSpec{ - Template: []kcv1alpha1.AppTemplate{ - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, - }, - }, - }, - }, - } - - app, err := packageinstall.NewApp(&kcv1alpha1.App{}, ipkg, pkgVersion, packageinstall.Opts{DefaultSyncPeriod: 10 * time.Minute}) - if err != nil { - t.Fatalf("Expected no err, but was: %s", err) - } - - expectedApp := &kcv1alpha1.App{ - Spec: kcv1alpha1.AppSpec{ - SyncPeriod: &defaultSyncPeriod, - Template: []kcv1alpha1.AppTemplate{ - {Ytt: &kcv1alpha1.AppTemplateYtt{ - ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "values1", - }, - }, - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "values2", - }, - }, - }, - }}, - // Second ytt templating step is untouched - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, - }, - }, - } - - // Not interesting in metadata in this test - app.ObjectMeta = metav1.ObjectMeta{} - - if !reflect.DeepEqual(expectedApp, app) { - bs, _ := yaml.Marshal(app) - t.Fatalf("App does not match expected app: (actual)\n%s", bs) - } -} - -func TestAppHelmTemplateValues(t *testing.T) { - ipkg := &pkgingv1alpha1.PackageInstall{ - ObjectMeta: metav1.ObjectMeta{ - Name: "app", - Namespace: "default", - }, - Spec: pkgingv1alpha1.PackageInstallSpec{ - Values: []pkgingv1alpha1.PackageInstallValues{ - {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values1"}}, - {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values2"}}, - }, - }, - } - - pkgVersion := datapkgingv1alpha1.Package{ - Spec: datapkgingv1alpha1.PackageSpec{ - RefName: "expec-pkg", - Version: "1.5.0", - Template: datapkgingv1alpha1.AppTemplateSpec{ - Spec: &kcv1alpha1.AppSpec{ - Template: []kcv1alpha1.AppTemplate{ - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - }, - }, - }, - }, - } - - app, err := packageinstall.NewApp(&kcv1alpha1.App{}, ipkg, pkgVersion, packageinstall.Opts{DefaultSyncPeriod: 10 * time.Minute}) - if err != nil { - t.Fatalf("Expected no err, but was: %s", err) - } - - expectedApp := &kcv1alpha1.App{ - Spec: kcv1alpha1.AppSpec{ - SyncPeriod: &defaultSyncPeriod, - Template: []kcv1alpha1.AppTemplate{ - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{ - ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "values1", - }, - }, - kcv1alpha1.AppTemplateValuesSource{ - SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{ - Name: "values2", - }, - }, - }, - }}, - // Second helm templating step is untouched - {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, - // Second ytt templating step is untouched - {Ytt: &kcv1alpha1.AppTemplateYtt{}}, - }, - }, - } - - // Not interesting in metadata in this test - app.ObjectMeta = metav1.ObjectMeta{} - - if !reflect.DeepEqual(expectedApp, app) { - bs, _ := yaml.Marshal(app) - t.Fatalf("App does not match expected app: (actual)\n%s", bs) - } -} - func TestAppManuallyControlled(t *testing.T) { existingApp := &kcv1alpha1.App{ ObjectMeta: metav1.ObjectMeta{ @@ -716,3 +318,331 @@ func TestAppPackageIntallDefaultNamespace(t *testing.T) { require.Equal(t, expectedApp, app, "App does not match expected app") } + +func TestAppPackageIntallValuesForTemplateSteps(t *testing.T) { + pkgi := func(values []pkgingv1alpha1.PackageInstallValues) *pkgingv1alpha1.PackageInstall { + return &pkgingv1alpha1.PackageInstall{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app", + Namespace: "default", + }, + Spec: pkgingv1alpha1.PackageInstallSpec{ + Values: values, + }, + } + } + + pkg := func() datapkgingv1alpha1.Package { + return datapkgingv1alpha1.Package{ + Spec: datapkgingv1alpha1.PackageSpec{ + RefName: "expec-pkg", + Version: "1.5.0", + Template: datapkgingv1alpha1.AppTemplateSpec{ + Spec: &kcv1alpha1.AppSpec{ + Template: []kcv1alpha1.AppTemplate{ + {Sops: &kcv1alpha1.AppTemplateSops{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + {Kbld: &kcv1alpha1.AppTemplateKbld{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {Cue: &kcv1alpha1.AppTemplateCue{}}, + }, + }, + }, + }, + } + } + + testCases := map[string]struct { + values []pkgingv1alpha1.PackageInstallValues + patchPkg func(*datapkgingv1alpha1.Package) + patchPkgi func(*pkgingv1alpha1.PackageInstall) + expectedTemplate []kcv1alpha1.AppTemplate + exepectedErrMsg string + }{ + "no values": { + expectedTemplate: []kcv1alpha1.AppTemplate{ + {Sops: &kcv1alpha1.AppTemplateSops{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + {Kbld: &kcv1alpha1.AppTemplateKbld{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {Cue: &kcv1alpha1.AppTemplateCue{}}, + }, + }, + "only add to first step": { + values: []pkgingv1alpha1.PackageInstallValues{ + {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values-for-first"}}, + }, + expectedTemplate: []kcv1alpha1.AppTemplate{ + {Sops: &kcv1alpha1.AppTemplateSops{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{ + ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "values-for-first"}}, + }, + }}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + {Kbld: &kcv1alpha1.AppTemplateKbld{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {Cue: &kcv1alpha1.AppTemplateCue{}}, + }, + }, + "invalid step index": { + values: []pkgingv1alpha1.PackageInstallValues{ + {TemplateSteps: []int{100}}, + }, + exepectedErrMsg: "out of range", + }, + "specific values, but step does not support values": { + values: []pkgingv1alpha1.PackageInstallValues{ + {TemplateSteps: []int{0}, SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "foo-bar"}}, + }, + exepectedErrMsg: "does not support values", + }, + "some values, but no steps which takes values": { + values: []pkgingv1alpha1.PackageInstallValues{ + {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "foo-bar"}}, + }, + patchPkg: func(pkg *datapkgingv1alpha1.Package) { + // pkg does define any template steps that support values + pkg.Spec.Template.Spec.Template = []kcv1alpha1.AppTemplate{ + {Sops: &kcv1alpha1.AppTemplateSops{}}, + } + }, + exepectedErrMsg: "no template step of class 'takesValues' found", + }, + "values for specific steps": { + values: []pkgingv1alpha1.PackageInstallValues{ + {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values-for-first"}}, + {TemplateSteps: []int{6}, SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values-for-specific-cue"}}, + {TemplateSteps: []int{3, 6}, SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values-for-multiple-steps"}}, + {TemplateSteps: []int{5}, SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values-for-specific-ytt"}}, + }, + expectedTemplate: []kcv1alpha1.AppTemplate{ + {Sops: &kcv1alpha1.AppTemplateSops{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{ + ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "values-for-first"}}, + }, + }}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{ + ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "values-for-multiple-steps"}}, + }, + }}, + {Kbld: &kcv1alpha1.AppTemplateKbld{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{ + ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "values-for-specific-ytt"}}, + }, + }}, + {Cue: &kcv1alpha1.AppTemplateCue{ + ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "values-for-specific-cue"}}, + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "values-for-multiple-steps"}}, + }, + }}, + }, + }, + "values for ytt, as inline paths": { + patchPkgi: func(pkgi *pkgingv1alpha1.PackageInstall) { + pkgi.ObjectMeta.SetAnnotations(map[string]string{ + "ext.packaging.carvel.dev/ytt-data-values-overlays": "", + "ext.packaging.carvel.dev/ytt-5-data-values-overlays": "", + }) + }, + values: []pkgingv1alpha1.PackageInstallValues{ + {SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values-for-first"}}, + {TemplateSteps: []int{2}, SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values-for-specific-ytt"}}, + {TemplateSteps: []int{5}, SecretRef: &pkgingv1alpha1.PackageInstallValuesSecretRef{Name: "values-for-another-ytt"}}, + }, + expectedTemplate: []kcv1alpha1.AppTemplate{ + {Sops: &kcv1alpha1.AppTemplateSops{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{ + Inline: &kcv1alpha1.AppFetchInline{ + PathsFrom: []kcv1alpha1.AppFetchInlineSource{ + {SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{Name: "values-for-first"}}, + }, + }, + }}, + {Ytt: &kcv1alpha1.AppTemplateYtt{ + ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "values-for-specific-ytt"}}, + }, + }}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + {Kbld: &kcv1alpha1.AppTemplateKbld{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{ + Inline: &kcv1alpha1.AppFetchInline{ + PathsFrom: []kcv1alpha1.AppFetchInlineSource{ + {SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{Name: "values-for-another-ytt"}}, + }, + }, + }}, + {Cue: &kcv1alpha1.AppTemplateCue{}}, + }, + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + pkg := pkg() + if tc.patchPkg != nil { + tc.patchPkg(&pkg) + } + + pkgi := pkgi(tc.values) + if tc.patchPkgi != nil { + tc.patchPkgi(pkgi) + } + + app, err := packageinstall.NewApp(&kcv1alpha1.App{}, pkgi, pkg, packageinstall.Opts{}) + + if errMsg := tc.exepectedErrMsg; errMsg != "" { + require.ErrorContains(t, err, errMsg) + } else { + require.NoError(t, err) + } + + require.Equal(t, tc.expectedTemplate, app.Spec.Template, "App template does not match expected template") + }) + } +} + +func TestAppPackageIntallAnnotationsForTemplateSteps(t *testing.T) { + pkgi := func(annotations map[string]string) *pkgingv1alpha1.PackageInstall { + pkgi := &pkgingv1alpha1.PackageInstall{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app", + Namespace: "default", + }, + } + + pkgi.ObjectMeta.SetAnnotations(annotations) + + return pkgi + } + + pkg := func(templateSteps []kcv1alpha1.AppTemplate) datapkgingv1alpha1.Package { + return datapkgingv1alpha1.Package{ + Spec: datapkgingv1alpha1.PackageSpec{ + RefName: "expec-pkg", + Version: "1.5.0", + Template: datapkgingv1alpha1.AppTemplateSpec{ + Spec: &kcv1alpha1.AppSpec{ + Template: templateSteps, + }, + }, + }, + } + } + + someHelmTemplateSteps := func() []kcv1alpha1.AppTemplate { + return []kcv1alpha1.AppTemplate{ + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + } + } + someYttTemplateSteps := func() []kcv1alpha1.AppTemplate { + return []kcv1alpha1.AppTemplate{ + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + } + } + + testCases := map[string]struct { + pkgiAnnotations map[string]string + pkgTemplateSteps []kcv1alpha1.AppTemplate + expectedTemplate []kcv1alpha1.AppTemplate + }{ + "no annotations": { + pkgTemplateSteps: someHelmTemplateSteps(), + expectedTemplate: []kcv1alpha1.AppTemplate{ + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{}}, + }, + }, + "helm annotations": { + pkgTemplateSteps: someHelmTemplateSteps(), + pkgiAnnotations: map[string]string{ + "ext.packaging.carvel.dev/helm-template-name": "some-default-helm-name", + "ext.packaging.carvel.dev/helm-1-template-name": "some-specific-helm-name", + "ext.packaging.carvel.dev/helm-2-template-namespace": "some-specific-helm-namespace", + "ext.packaging.carvel.dev/helm-100-template-namespace": "no-such-step", + + "ext.packaging.carvel.dev/helm-template-values-from-secret-name": "some-helm-secret", + "ext.packaging.carvel.dev/helm-template-values-from-secret-name.blipp": "some-helm-secret.blipp", + "ext.packaging.carvel.dev/helm-1-template-values-from-secret-name.foo": "some-helm-secret.foo", + "ext.packaging.carvel.dev/helm-1-template-values-from-secret-name.bar": "some-helm-secret.bar", + }, + expectedTemplate: []kcv1alpha1.AppTemplate{ + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{ + Name: "some-default-helm-name", + ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "some-helm-secret"}}, + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "some-helm-secret.blipp"}}, + }, + }}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{ + Name: "some-specific-helm-name", + ValuesFrom: []kcv1alpha1.AppTemplateValuesSource{ + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "some-helm-secret.bar"}}, + {SecretRef: &kcv1alpha1.AppTemplateValuesSourceRef{Name: "some-helm-secret.foo"}}, + }, + }}, + {HelmTemplate: &kcv1alpha1.AppTemplateHelmTemplate{ + Namespace: "some-specific-helm-namespace", + }}, + }, + }, + "ytt annotations": { + pkgTemplateSteps: someYttTemplateSteps(), + pkgiAnnotations: map[string]string{ + "ext.packaging.carvel.dev/ytt-paths-from-secret-name": "some-ytt-secret", + "ext.packaging.carvel.dev/ytt-paths-from-secret-name.1": "some-other-ytt-secret", + "ext.packaging.carvel.dev/ytt-0-paths-from-secret-name.foobar": "some-third-ytt-secret", + "ext.packaging.carvel.dev/ytt-2-paths-from-secret-name": "some-specific-ytt-secret", + "ext.packaging.carvel.dev/ytt-100-paths-from-secret-name": "no such step", + }, + expectedTemplate: []kcv1alpha1.AppTemplate{ + {Ytt: &kcv1alpha1.AppTemplateYtt{ + Inline: &kcv1alpha1.AppFetchInline{ + PathsFrom: []kcv1alpha1.AppFetchInlineSource{ + {SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{Name: "some-ytt-secret"}}, + {SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{Name: "some-other-ytt-secret"}}, + {SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{Name: "some-third-ytt-secret"}}, + }, + }, + }}, + {Ytt: &kcv1alpha1.AppTemplateYtt{}}, + {Ytt: &kcv1alpha1.AppTemplateYtt{ + Inline: &kcv1alpha1.AppFetchInline{ + PathsFrom: []kcv1alpha1.AppFetchInlineSource{ + {SecretRef: &kcv1alpha1.AppFetchInlineSourceRef{Name: "some-specific-ytt-secret"}}, + }, + }, + }}, + }, + }, + } + + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + app, err := packageinstall.NewApp( + &kcv1alpha1.App{}, + pkgi(tc.pkgiAnnotations), + pkg(tc.pkgTemplateSteps), + packageinstall.Opts{}, + ) + require.NoError(t, err) + require.Equal(t, tc.expectedTemplate, app.Spec.Template, "App template does not match expected template") + }) + } +}