@@ -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+
303317func (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.
341355func (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.
366410type 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.
375431type 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