Skip to content

Commit 8dc0354

Browse files
authored
feat(carvel): support structured BOSH variable declarations in base.yml (#664)
## Summary - Changes `Variables []string` → `Variables []proofing.Variable` in `internal/carvel/models/metadata.go` and `metadata_out.go` - Adds `validateVariables()` to `baker.go` — returns a clear error if a variable entry is missing `name` or `type` - Covers with 4 new Ginkgo unit specs + updates `testdata/sample-tile/base.yml` for integration round-trip coverage ## Motivation Carvel tile authors need to declare BOSH CredHub variable definitions (e.g., certificate CA hierarchies) inline in `base.yml`. The previous `[]string` type rejected structured map entries with a YAML unmarshal error: ``` yaml: unmarshal errors: line 34: cannot unmarshal !!map into string ``` Discovered while building the EAR-K8s tile's Diego Instance Identity CA support. The EAR-K8s tile mirrors the TAS IST tile's pattern of declaring `diego-instance-identity-intermediate-ca-2-7` as a BOSH CredHub certificate variable so BOSH manages the CA lifecycle automatically. ## Why `proofing.Variable`? `pkg/proofing` already models this shape (`name`, `type`, `options any`). Reusing it avoids duplicating the struct. The import direction (`internal/carvel` → `pkg/proofing`) is valid — no circular dependency. ## Test Plan - [x] `go test ./internal/carvel/... --ginkgo.focus="validateVariables"` — 4 new unit specs pass - [x] `go test ./internal/carvel/...` — 36/36 specs pass, including integration round-trip for structured variables - [x] `go build ./internal/carvel/...` — clean build ## Related TNZ-112157 Made with [Cursor](https://cursor.com)
2 parents 20910ea + be63033 commit 8dc0354

5 files changed

Lines changed: 111 additions & 39 deletions

File tree

internal/carvel/baker.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/pivotal-cf/kiln/internal/carvel/models"
1919
"github.com/pivotal-cf/kiln/pkg/cargo"
20+
"github.com/pivotal-cf/kiln/pkg/proofing"
2021

2122
"github.com/hashicorp/go-version"
2223
"gopkg.in/yaml.v3"
@@ -89,6 +90,9 @@ func (b *baker) Bake(source string) error {
8990
if err != nil {
9091
return err
9192
}
93+
if err := validateVariables(b.metadata.Variables); err != nil {
94+
return err
95+
}
9296

9397
ver, err := b.GetVersion()
9498
if err != nil {
@@ -137,6 +141,9 @@ func (b *baker) BakeFromLockfile(source string, releaseLock cargo.BOSHReleaseTar
137141
if err != nil {
138142
return err
139143
}
144+
if err := validateVariables(b.metadata.Variables); err != nil {
145+
return err
146+
}
140147

141148
ver, err := b.GetVersion()
142149
if err != nil {
@@ -248,6 +255,18 @@ func (b *baker) progress(message string) {
248255
_, _ = fmt.Fprintln(b.progressWriter, message)
249256
}
250257

258+
func validateVariables(vars []proofing.Variable) error {
259+
var errs []error
260+
for i, v := range vars {
261+
if v.Name == "" {
262+
errs = append(errs, fmt.Errorf("variables[%d]: missing required field 'name'", i))
263+
} else if v.Type == "" {
264+
errs = append(errs, fmt.Errorf("variables[%d] (%q): missing required field 'type'", i, v.Name))
265+
}
266+
}
267+
return errors.Join(errs...)
268+
}
269+
251270
func (b *baker) generateBoshReleaseDir() error {
252271
dirName := path.Join(b.source, ".boshrelease")
253272
err := os.RemoveAll(dirName)

internal/carvel/baker_test.go

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
. "github.com/onsi/gomega"
1515
"github.com/pivotal-cf/kiln/internal/carvel/models"
1616
"github.com/pivotal-cf/kiln/pkg/cargo"
17+
"github.com/pivotal-cf/kiln/pkg/proofing"
1718
"gopkg.in/yaml.v3"
1819
)
1920

@@ -140,12 +141,12 @@ var _ = Describe("Carvel Baker", func() {
140141
subject = NewBaker()
141142
subject.SetWriter(GinkgoWriter)
142143
})
143-
AfterEach(func() {
144-
// Clean up the temp directory
145-
if inputPath != "" {
146-
_ = os.RemoveAll(filepath.Dir(inputPath))
147-
}
148-
})
144+
AfterEach(func() {
145+
// Clean up the temp directory
146+
if inputPath != "" {
147+
_ = os.RemoveAll(filepath.Dir(inputPath))
148+
}
149+
})
149150
JustBeforeEach(func() {
150151
err = subject.Bake(inputPath)
151152
})
@@ -169,7 +170,11 @@ var _ = Describe("Carvel Baker", func() {
169170
Expect(outMeta.Serial).To(BeFalse())
170171
Expect(outMeta.PropertyBlueprints).To(HaveLen(2))
171172
Expect(outMeta.FormTypes).To(HaveLen(1))
172-
Expect(outMeta.Variables).To(BeEmpty())
173+
Expect(outMeta.Variables).To(HaveLen(1))
174+
Expect(outMeta.Variables[0].Name).To(Equal("sample-tile-ca"))
175+
Expect(outMeta.Variables[0].Type).To(Equal("certificate"))
176+
Expect(outMeta.Variables[0].Options).To(HaveKeyWithValue("common_name", "Sample Tile CA"))
177+
Expect(outMeta.Variables[0].Options).To(HaveKeyWithValue("is_ca", true))
173178
Expect(outMeta.Releases).To(HaveLen(1))
174179
Expect(outMeta.Releases[0]).To(ContainSubstring("k8s-tile-test"))
175180
Expect(outMeta.InstanceGroups).To(HaveLen(0))
@@ -297,7 +302,7 @@ var _ = Describe("Carvel Baker", func() {
297302
`$( property "admin_password" )`,
298303
},
299304
FormTypes: []string{`$( form "db_props" )`},
300-
Variables: []string{},
305+
Variables: []proofing.Variable{},
301306
PackageInstalls: []string{`$( package "test-install" )`},
302307
}
303308
yamlData, err := yaml.Marshal(&m)
@@ -591,4 +596,42 @@ var _ = Describe("Carvel Baker", func() {
591596
Expect(nonEmpty).To(Equal(5))
592597
})
593598
})
599+
600+
Context("validateVariables", func() {
601+
It("passes for an empty list", func() {
602+
err := validateVariables([]proofing.Variable{})
603+
Expect(err).NotTo(HaveOccurred())
604+
})
605+
606+
It("passes for a valid certificate variable", func() {
607+
err := validateVariables([]proofing.Variable{
608+
{
609+
Name: "/cf/diego-instance-identity-root-ca-2-6",
610+
Type: "certificate",
611+
Options: map[string]any{
612+
"common_name": "Diego Instance Identity Root CA",
613+
"is_ca": true,
614+
"duration": 1095,
615+
},
616+
},
617+
})
618+
Expect(err).NotTo(HaveOccurred())
619+
})
620+
621+
It("errors when name is empty", func() {
622+
err := validateVariables([]proofing.Variable{
623+
{Name: "", Type: "certificate"},
624+
})
625+
Expect(err).To(HaveOccurred())
626+
Expect(err.Error()).To(ContainSubstring("missing required field 'name'"))
627+
})
628+
629+
It("errors when type is empty", func() {
630+
err := validateVariables([]proofing.Variable{
631+
{Name: "my-var", Type: ""},
632+
})
633+
Expect(err).To(HaveOccurred())
634+
Expect(err.Error()).To(ContainSubstring("missing required field 'type'"))
635+
})
636+
})
594637
})

internal/carvel/models/metadata.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package models
22

3+
import "github.com/pivotal-cf/kiln/pkg/proofing"
4+
35
type Metadata struct {
4-
Name string `yaml:"name"`
5-
ProductVersion string `yaml:"product_version"`
6-
IconImage string `yaml:"icon_image"`
7-
Label string `yaml:"label"`
8-
MetadataVersion string `yaml:"metadata_version"`
9-
MinimumVersionForUpgrade string `yaml:"minimum_version_for_upgrade"`
10-
Rank int `yaml:"rank"`
11-
Serial bool `yaml:"serial"`
12-
PropertyBlueprints []string `yaml:"property_blueprints"`
13-
FormTypes []string `yaml:"form_types"`
14-
Variables []string `yaml:"variables"`
15-
PackageInstalls []string `yaml:"package_installs"`
16-
CompatibleKubernetesDistributions []ProductVersion `yaml:"compatible_kubernetes_distributions,omitempty"`
6+
Name string `yaml:"name"`
7+
ProductVersion string `yaml:"product_version"`
8+
IconImage string `yaml:"icon_image"`
9+
Label string `yaml:"label"`
10+
MetadataVersion string `yaml:"metadata_version"`
11+
MinimumVersionForUpgrade string `yaml:"minimum_version_for_upgrade"`
12+
Rank int `yaml:"rank"`
13+
Serial bool `yaml:"serial"`
14+
PropertyBlueprints []string `yaml:"property_blueprints"`
15+
FormTypes []string `yaml:"form_types"`
16+
Variables []proofing.Variable `yaml:"variables"`
17+
PackageInstalls []string `yaml:"package_installs"`
18+
CompatibleKubernetesDistributions []ProductVersion `yaml:"compatible_kubernetes_distributions,omitempty"`
1719
}

internal/carvel/models/metadata_out.go

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
package models
22

3+
import "github.com/pivotal-cf/kiln/pkg/proofing"
4+
35
type MetadataOut struct {
4-
Name string `yaml:"name"`
5-
ProductVersion string `yaml:"product_version"`
6-
IconImage string `yaml:"icon_image"`
7-
Label string `yaml:"label"`
8-
MetadataVersion string `yaml:"metadata_version"`
9-
MinimumVersionForUpgrade string `yaml:"minimum_version_for_upgrade"`
10-
Rank int `yaml:"rank"`
11-
Serial bool `yaml:"serial"`
12-
PropertyBlueprints []string `yaml:"property_blueprints"`
13-
FormTypes []string `yaml:"form_types"`
14-
Variables []string `yaml:"variables"`
15-
InstanceGroups []string `yaml:"job_types"`
16-
StemcellCriteria StemcellCriteria `yaml:"stemcell_criteria"`
17-
Releases []string `yaml:"releases"`
18-
RuntimeConfigs []string `yaml:"runtime_configs"`
19-
RequiresKubernetes bool `yaml:"requires_kubernetes"`
20-
CompatibleKubernetesDistributions []ProductVersion `yaml:"compatible_kubernetes_distributions"`
6+
Name string `yaml:"name"`
7+
ProductVersion string `yaml:"product_version"`
8+
IconImage string `yaml:"icon_image"`
9+
Label string `yaml:"label"`
10+
MetadataVersion string `yaml:"metadata_version"`
11+
MinimumVersionForUpgrade string `yaml:"minimum_version_for_upgrade"`
12+
Rank int `yaml:"rank"`
13+
Serial bool `yaml:"serial"`
14+
PropertyBlueprints []string `yaml:"property_blueprints"`
15+
FormTypes []string `yaml:"form_types"`
16+
Variables []proofing.Variable `yaml:"variables"`
17+
InstanceGroups []string `yaml:"job_types"`
18+
StemcellCriteria StemcellCriteria `yaml:"stemcell_criteria"`
19+
Releases []string `yaml:"releases"`
20+
RuntimeConfigs []string `yaml:"runtime_configs"`
21+
RequiresKubernetes bool `yaml:"requires_kubernetes"`
22+
CompatibleKubernetesDistributions []ProductVersion `yaml:"compatible_kubernetes_distributions"`
2123
}
2224

2325
type StemcellCriteria struct {

internal/carvel/testdata/sample-tile/base.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ property_blueprints:
1111
- $( property "admin_password" )
1212
form_types:
1313
- $( form "db_props" )
14-
variables: []
14+
variables:
15+
- name: sample-tile-ca
16+
type: certificate
17+
options:
18+
common_name: Sample Tile CA
19+
is_ca: true
20+
duration: 730
1521
package_installs:
1622
- $( package "test-install" )
1723
compatible_kubernetes_distributions:

0 commit comments

Comments
 (0)