Skip to content

Commit 30c8052

Browse files
Merge upstream/main
Co-authored-by: Cursor <cursoragent@cursor.com>
2 parents 00923a9 + 167cb53 commit 30c8052

17 files changed

Lines changed: 368 additions & 95 deletions

File tree

HomebrewFormula/kiln.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@
55
class Kiln < Formula
66
desc ""
77
homepage ""
8-
version "0.116.0"
8+
version "0.117.0"
99

1010
on_macos do
1111
if Hardware::CPU.intel?
12-
url "https://github.com/pivotal-cf/kiln/releases/download/v0.116.0/kiln-darwin-amd64-0.116.0.tar.gz"
13-
sha256 "f9214c2c517a7cc32341dc34384ecb37e9aba5129c37179e6f9e3443a218a4e6"
12+
url "https://github.com/pivotal-cf/kiln/releases/download/v0.117.0/kiln-darwin-amd64-0.117.0.tar.gz"
13+
sha256 "f04adb2dc4cd9e0ba04b5c7ac2b697aaf378f0a8db90f985b46fc7a9653d9468"
1414

1515
define_method(:install) do
1616
bin.install "kiln"
1717
end
1818
end
1919
if Hardware::CPU.arm?
20-
url "https://github.com/pivotal-cf/kiln/releases/download/v0.116.0/kiln-darwin-arm64-0.116.0.tar.gz"
21-
sha256 "5f0e27d727a2bbd145a743830da4739dd15ed309968349e0a396c8d1db43366c"
20+
url "https://github.com/pivotal-cf/kiln/releases/download/v0.117.0/kiln-darwin-arm64-0.117.0.tar.gz"
21+
sha256 "2e671466ff82ca66fc5b37578a417e65415e348aad5331158ccc923cadb1d5ec"
2222

2323
define_method(:install) do
2424
bin.install "kiln"
@@ -28,8 +28,8 @@ class Kiln < Formula
2828

2929
on_linux do
3030
if Hardware::CPU.intel? && Hardware::CPU.is_64_bit?
31-
url "https://github.com/pivotal-cf/kiln/releases/download/v0.116.0/kiln-linux-amd64-0.116.0.tar.gz"
32-
sha256 "3ba6966934bf6c25f2adbcde1907884a5505319dd1f3018c356275f5ccbc3cec"
31+
url "https://github.com/pivotal-cf/kiln/releases/download/v0.117.0/kiln-linux-amd64-0.117.0.tar.gz"
32+
sha256 "e2e4f08b07155819e4577d2ff552c126a9190243891a9d496c80f6187ac5f7c6"
3333
define_method(:install) do
3434
bin.install "kiln"
3535
end

TILE_AUTHOR_GUIDE.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,43 @@ The property definition in [releen/hello-tile/properties/hello.yml](https://gith
129129
in referenced in `base.yml` using `$( property "port" )`.
130130
Most other product template part functions behave similarly.
131131

132+
### Carvel / Kubernetes Tile Metadata (`base.yml`)
133+
134+
When building Carvel/Kubernetes tiles using `kiln carvel bake`, the `base.yml` metadata file supports the following additional top-level fields:
135+
136+
- `supports_parallel_deploys` (boolean): Indicates whether the tile supports parallel deployments in Ops Manager.
137+
- `requires_product_versions` (list): Specifies product version dependencies required by the tile.
138+
- `name`: Product name.
139+
- `version`: Version requirement constraint (e.g. `>=1.0.0`).
140+
- `optional` (boolean): Whether the dependency is optional.
141+
- `compatible_kubernetes_distributions` (list): Supported Kubernetes distribution criteria.
142+
- `name`: Distribution name (e.g. `k0s`).
143+
- `version`: Version requirement constraint (e.g. `>0.0.0`).
144+
- `package_installs` (list): References to Carvel package install templates.
145+
146+
Example Carvel tile `base.yml`:
147+
148+
```yaml
149+
name: k8s-tile-name
150+
label: "My K8s Tile"
151+
icon_image: $( icon )
152+
metadata_version: "3.2.0"
153+
minimum_version_for_upgrade: 0.0.0
154+
product_version: $( version )
155+
rank: 1
156+
serial: false
157+
supports_parallel_deploys: true
158+
requires_product_versions:
159+
- name: some-other-product
160+
version: ">=1.0.0"
161+
optional: true
162+
compatible_kubernetes_distributions:
163+
- name: k0s
164+
version: ">0.0.0"
165+
package_installs:
166+
- $( package "my-package" )
167+
```
168+
132169
## <a id="bosh-release-tarballs"></a> Managing BOSH Release Tarballs
133170
134171
`kiln fetch` downloads BOSH Release Tarballs from any of the following "sources"

internal/acceptance/carvel/carvel_workflow_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,12 @@ var _ = Describe("carvel full workflow", Ordered, func() {
287287

288288
Expect(lock.Releases).To(HaveLen(1))
289289
rel := lock.Releases[0]
290-
Expect(rel.Name).To(Equal("k8s-tile-test"))
290+
Expect(rel.Name).To(Equal("k8s-tile-test-pkg"))
291291
Expect(rel.Version).To(HavePrefix("0.1.1+"))
292292
Expect(rel.Version).To(MatchRegexp(`^0\.1\.1\+[0-9a-f]{12}$`))
293293
Expect(rel.SHA1).NotTo(BeEmpty(), "lock must contain SHA1 of uploaded tarball")
294294
Expect(rel.RemoteSource).To(Equal("artifactory"))
295-
Expect(rel.RemotePath).To(Equal("bosh-releases/k8s-tile-test/k8s-tile-test-" + rel.Version + ".tgz"))
295+
Expect(rel.RemotePath).To(Equal("bosh-releases/k8s-tile-test-pkg/k8s-tile-test-pkg-" + rel.Version + ".tgz"))
296296

297297
gitInTile("add", "Kilnfile.lock")
298298
gitInTile("commit", "-m", "add Kilnfile.lock from upload")

internal/acceptance/carvel/fixtures/sample-tile/base.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ package_installs:
1717
compatible_kubernetes_distributions:
1818
- name: k0s
1919
version: '>0.0.0'
20+
supports_parallel_deploys: true
21+
requires_product_versions:
22+
- name: some-other-product
23+
version: '>=1.0.0'
24+
optional: true

internal/carvel/baker.go

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Baker interface {
3838
KilnBake(destination string) error
3939
ParseMetadata(source string) error
4040
GetName() string
41+
GetBoshReleaseName() string
4142
// GetVersion returns the product version from base.yml or the version file.
4243
GetVersion() (string, error)
4344
// GetReleaseVersion returns the BOSH release version, which includes a
@@ -98,6 +99,9 @@ func (b *baker) Bake(source string, kilnfile cargo.Kilnfile, kilnfileLock cargo.
9899
if err != nil {
99100
return err
100101
}
102+
if b.metadata.Name == "" {
103+
return errors.New("missing required field 'name' in tile metadata (base.yml)")
104+
}
101105
if err := validateVariables(b.metadata.Variables); err != nil {
102106
return err
103107
}
@@ -149,6 +153,9 @@ func (b *baker) BakeFromLockfile(source string, kilnfile cargo.Kilnfile, kilnfil
149153
if err != nil {
150154
return err
151155
}
156+
if b.metadata.Name == "" {
157+
return errors.New("missing required field 'name' in tile metadata (base.yml)")
158+
}
152159
if err := validateVariables(b.metadata.Variables); err != nil {
153160
return err
154161
}
@@ -159,8 +166,8 @@ func (b *baker) BakeFromLockfile(source string, kilnfile cargo.Kilnfile, kilnfil
159166
}
160167
b.progress(fmt.Sprintf("Tile: %s version %s (metadata_version %s)", b.metadata.Name, ver, b.metadata.MetadataVersion))
161168

162-
if releaseLock.Name != b.metadata.Name {
163-
return fmt.Errorf("lockfile release name %q does not match tile name %q", releaseLock.Name, b.metadata.Name)
169+
if releaseLock.Name != b.GetBoshReleaseName() {
170+
return fmt.Errorf("lockfile release name %q does not match tile-derived name %q", releaseLock.Name, b.GetBoshReleaseName())
164171
}
165172

166173
b.releaseVersion = releaseLock.Version
@@ -202,7 +209,7 @@ func (b *baker) BakeFromLockfile(source string, kilnfile cargo.Kilnfile, kilnfil
202209
return err
203210
}
204211

205-
destTarball := path.Join(releasesDir, b.metadata.Name+"-"+releaseLock.Version+".tgz")
212+
destTarball := path.Join(releasesDir, b.GetBoshReleaseName()+"-"+releaseLock.Version+".tgz")
206213

207214
b.progress("Copying cached BOSH release from " + localTarball)
208215
b.log("copying cached BOSH release from " + localTarball)
@@ -228,7 +235,7 @@ func (b *baker) GetReleaseTarball() (string, error) {
228235
if b.releaseVersion == "" {
229236
return "", fmt.Errorf("release version not set -- call Bake() or BakeFromLockfile() first")
230237
}
231-
tarball := path.Join(b.destination, "releases", b.metadata.Name+"-"+b.releaseVersion+".tgz")
238+
tarball := path.Join(b.destination, "releases", b.GetBoshReleaseName()+"-"+b.releaseVersion+".tgz")
232239
if _, err := os.Stat(tarball); err != nil {
233240
return "", fmt.Errorf("release tarball not found at %s: %w", tarball, err)
234241
}
@@ -300,6 +307,13 @@ func (b *baker) hookModeGroups() []hookModeGroup {
300307
}
301308
}
302309

310+
func (b *baker) GetBoshReleaseName() string {
311+
if b.metadata.Name == "" {
312+
return ""
313+
}
314+
return b.metadata.Name + "-pkg"
315+
}
316+
303317
func (b *baker) GetReleaseVersion() string {
304318
return b.releaseVersion
305319
}
@@ -336,7 +350,7 @@ func (b *baker) progress(message string) {
336350

337351
// deduplicateConsumes removes duplicate BOSH link consumer entries by name.
338352
// Identical duplicates are dropped silently. If two entries share a name but
339-
// differ in type or optional, the first is kept and a WARNING is emitted —
353+
// differ in any field, the first is kept and a WARNING is emitted —
340354
// BOSH rejects duplicate link names in job.MF, so the second is always ignored.
341355
func (b *baker) deduplicateConsumes(consumes []boshLinkConsumer) []boshLinkConsumer {
342356
seen := make(map[string]boshLinkConsumer)
@@ -351,19 +365,59 @@ func (b *baker) deduplicateConsumes(consumes []boshLinkConsumer) []boshLinkConsu
351365
if existing != c {
352366
b.progress(fmt.Sprintf(
353367
"WARNING: duplicate BOSH link consumer name %q found across packageinstalls.\n"+
354-
" Keeping: {type: %s, optional: %v}\n"+
355-
" Ignoring: {type: %s, optional: %v}\n"+
368+
" Keeping: {type: %s, optional: %v, from: %s, deployment: %s}\n"+
369+
" Ignoring: {type: %s, optional: %v, from: %s, deployment: %s}\n"+
356370
" Ensure all packageinstalls agree on the link definition.",
357-
c.Name, existing.Type, existing.Optional, c.Type, c.Optional,
371+
c.Name,
372+
existing.Type, existing.Optional, existing.From, existing.Deployment,
373+
c.Type, c.Optional, c.From, c.Deployment,
358374
))
359375
}
360376
}
361377
return deduped
362378
}
363379

380+
// readJobSpecOverlays reads all *.job-spec-overlay.yml sidecars for the tile's
381+
// package installs and returns the merged slice of boshLinkConsumer entries.
382+
func (b *baker) readJobSpecOverlays() ([]boshLinkConsumer, error) {
383+
var all []boshLinkConsumer
384+
for _, entry := range b.metadata.PackageInstalls {
385+
entry = strings.Trim(entry, "$() ")
386+
entry = strings.TrimPrefix(entry, "package")
387+
entry = strings.Trim(entry, `"' `)
388+
389+
overlayPath := path.Join(b.source, "packageinstalls", entry+".job-spec-overlay.yml")
390+
data, err := os.ReadFile(overlayPath)
391+
if errors.Is(err, os.ErrNotExist) {
392+
continue
393+
}
394+
if err != nil {
395+
return nil, fmt.Errorf("reading %s: %w", overlayPath, err)
396+
}
397+
var overlay jobSpecOverlay
398+
if err := yaml.Unmarshal(data, &overlay); err != nil {
399+
return nil, fmt.Errorf("parsing %s: %w", overlayPath, err)
400+
}
401+
all = append(all, overlay.Consumes...)
402+
}
403+
return all, nil
404+
}
405+
364406
// boshLinkConsumer declares a BOSH link the registry-data job should consume.
365407
// Populated from per-packageinstall *.job-spec-overlay.yml sidecar files.
408+
// When From or Deployment is set, kiln also emits a cross-deployment consumes
409+
// entry for the link in the runtime config addon job.
366410
type boshLinkConsumer struct {
411+
Name string `yaml:"name"`
412+
Type string `yaml:"type"`
413+
Optional bool `yaml:"optional"`
414+
From string `yaml:"from,omitempty"`
415+
Deployment string `yaml:"deployment,omitempty"`
416+
}
417+
418+
// boshConsumes is the BOSH job spec consumes schema: name, type, and optional only.
419+
// From/Deployment are runtime-config-only and must not appear in the job spec.
420+
type boshConsumes struct {
367421
Name string `yaml:"name"`
368422
Type string `yaml:"type"`
369423
Optional bool `yaml:"optional"`
@@ -372,6 +426,8 @@ type boshLinkConsumer struct {
372426
// jobSpecOverlay is the schema for <entry>.job-spec-overlay.yml sidecar files.
373427
// kiln reads these from packageinstalls/ and merges the consumes entries into
374428
// the generated registry-data job.MF alongside the hardcoded cluster-info link.
429+
// Entries that set from or deployment are also emitted as cross-deployment
430+
// consumes on the runtime config addon job.
375431
type jobSpecOverlay struct {
376432
Consumes []boshLinkConsumer `yaml:"consumes"`
377433
}
@@ -434,7 +490,6 @@ files:
434490

435491
registryDataTemplates := ""
436492
registryDataProperties := ""
437-
var allConsumes []boshLinkConsumer
438493

439494
b.progress(" Configuring package installs")
440495
for _, entry := range b.metadata.PackageInstalls {
@@ -495,21 +550,6 @@ files:
495550
overlayContent = string(overlayData)
496551
}
497552

498-
// Read optional job-spec-overlay sidecar to discover additional BOSH link consumptions.
499-
jobSpecOverlayPath := path.Join(b.source, "packageinstalls", entry+".job-spec-overlay.yml")
500-
overlayData, overlayErr = os.ReadFile(jobSpecOverlayPath)
501-
if overlayErr != nil {
502-
if !errors.Is(overlayErr, os.ErrNotExist) {
503-
return overlayErr
504-
}
505-
} else {
506-
var overlay jobSpecOverlay
507-
if parseErr := yaml.Unmarshal(overlayData, &overlay); parseErr != nil {
508-
return fmt.Errorf("parsing %s: %w", jobSpecOverlayPath, parseErr)
509-
}
510-
allConsumes = append(allConsumes, overlay.Consumes...)
511-
}
512-
513553
manifestTemplate := generateManifestTemplate(entry, overlayContent)
514554

515555
err = os.WriteFile(
@@ -522,9 +562,17 @@ files:
522562
}
523563
}
524564

565+
allConsumes, err := b.readJobSpecOverlays()
566+
if err != nil {
567+
return err
568+
}
525569
deduped := b.deduplicateConsumes(allConsumes)
526570

527-
registryDataSpec, err := buildRegistryDataSpec(registryDataTemplates, registryDataProperties, deduped)
571+
boshLinks := make([]boshConsumes, len(deduped))
572+
for i, c := range deduped {
573+
boshLinks[i] = boshConsumes{Name: c.Name, Type: c.Type, Optional: c.Optional}
574+
}
575+
registryDataSpec, err := buildRegistryDataSpec(registryDataTemplates, registryDataProperties, boshLinks)
528576
if err != nil {
529577
return err
530578
}
@@ -583,7 +631,7 @@ properties: {}
583631
// buildRegistryDataSpec constructs the job.MF content for the registry-data BOSH job.
584632
// It always includes the hardcoded cluster-info link and appends any additional links
585633
// collected from *.job-spec-overlay.yml sidecars in the packageinstalls/ directory.
586-
func buildRegistryDataSpec(templates, properties string, additionalLinks []boshLinkConsumer) (string, error) {
634+
func buildRegistryDataSpec(templates, properties string, additionalLinks []boshConsumes) (string, error) {
587635
extraLinks := ""
588636
if len(additionalLinks) > 0 {
589637
data, err := yaml.Marshal(additionalLinks)
@@ -741,6 +789,8 @@ func (b *baker) generateBaseYaml() error {
741789
meta.Rank = b.metadata.Rank
742790
meta.Serial = b.metadata.Serial
743791
meta.CompatibleKubernetesDistributions = b.metadata.CompatibleKubernetesDistributions
792+
meta.SupportsParallelDeploys = b.metadata.SupportsParallelDeploys
793+
meta.RequiresProductVersions = b.metadata.RequiresProductVersions
744794
meta.FormTypes = b.metadata.FormTypes
745795
meta.PropertyBlueprints = b.metadata.PropertyBlueprints
746796
meta.Variables = b.metadata.Variables
@@ -756,7 +806,7 @@ func (b *baker) generateBaseYaml() error {
756806
}
757807

758808
meta.Releases = []string{
759-
`$( release "` + b.metadata.Name + `" )`,
809+
`$( release "` + b.GetBoshReleaseName() + `" )`,
760810
}
761811
for _, ar := range b.metadata.AdditionalReleases {
762812
meta.Releases = append(meta.Releases, `$( release "`+ar.Name+`" )`)
@@ -849,11 +899,30 @@ func (b *baker) generateRuntimeConfigs() error {
849899
}
850900
}
851901

902+
allConsumes, err := b.readJobSpecOverlays()
903+
if err != nil {
904+
return err
905+
}
906+
deduped := b.deduplicateConsumes(allConsumes)
907+
908+
consumesMap := make(map[string]models.JobConsumes)
909+
for _, c := range deduped {
910+
if c.From != "" || c.Deployment != "" {
911+
consumesMap[c.Name] = models.JobConsumes{
912+
From: c.From,
913+
Deployment: c.Deployment,
914+
}
915+
}
916+
}
917+
852918
registryDataJob := models.Job{
853919
Name: "registry-data",
854-
Release: b.metadata.Name,
920+
Release: b.GetBoshReleaseName(),
855921
Properties: registryDataProps,
856922
}
923+
if len(consumesMap) > 0 {
924+
registryDataJob.Consumes = consumesMap
925+
}
857926

858927
releases := []string{`$( release "` + b.metadata.Name + `" )`}
859928
addonJobs := []models.Job{registryDataJob}
@@ -1050,12 +1119,12 @@ func (b *baker) createBoshRelease() error {
10501119
releaseVersion := buildReleaseVersion(productVersion, fingerprint)
10511120
b.releaseVersion = releaseVersion
10521121

1053-
finalTarball := path.Join(b.destination, "releases", b.metadata.Name+"-"+releaseVersion+".tgz")
1122+
finalTarball := path.Join(b.destination, "releases", b.GetBoshReleaseName()+"-"+releaseVersion+".tgz")
10541123
cmd := exec.Command("bosh",
10551124
"create-release",
10561125
"--dir="+dirName,
10571126
"--force",
1058-
"--name", b.metadata.Name,
1127+
"--name", b.GetBoshReleaseName(),
10591128
"--version", releaseVersion,
10601129
"--tarball", finalTarball)
10611130
b.log("executing " + cmd.String())

0 commit comments

Comments
 (0)