diff --git a/internal/commands/bake.go b/internal/commands/bake.go index dfafab4e..cd3753ca 100644 --- a/internal/commands/bake.go +++ b/internal/commands/bake.go @@ -142,6 +142,8 @@ func NewBake(fs billy.Filesystem, releasesService baking.ReleasesService, outLog } } +// WithKilnfileFunc overrides the funcion used to parse the Kilnfile. +// It is for setting up tests. func (bake Bake) WithKilnfileFunc(fn func(string) (cargo.Kilnfile, error)) Bake { bake.loadKilnfile = fn return bake @@ -205,6 +207,8 @@ type BakeOptions struct { Version string `short:"v" long:"version" description:"version of the tile"` SkipFetchReleases bool `short:"sfr" long:"skip-fetch" description:"skips the automatic release fetch for all release directories" alias:"skip-fetch-directories"` + TileName string `short:"t" long:"tile-name" description:"select the bake_configuration matching the tile-name from the Kilnfile"` + IsFinal bool `long:"final" description:"this flag causes build metadata to be written to bake_records"` } @@ -440,15 +444,23 @@ func (b Bake) Execute(args []string) error { b.errLogger.Println("warning: --stemcell-tarball is being deprecated in favor of --stemcells-directory") } - templateVariables, err := b.templateVariables.FromPathsAndPairs(b.Options.VariableFiles, b.Options.Variables) - if err != nil { - return fmt.Errorf("failed to parse template variables: %s", err) + var templateVariables map[string]any + if b.Options.TileName != "" { + if templateVariables == nil { + templateVariables = make(map[string]any) + } + templateVariables[builder.TileNameVariable] = b.Options.TileName } if err := BakeArgumentsFromKilnfileConfiguration(&b.Options, templateVariables, b.loadKilnfile); err != nil { return fmt.Errorf("failed to load bake configuration from Kilnfile: %w", err) } + templateVariables, err = b.templateVariables.FromPathsAndPairs(b.Options.VariableFiles, b.Options.Variables) + if err != nil { + return fmt.Errorf("failed to parse template variables: %s", err) + } + releaseManifests, err := b.releases.FromDirectories(b.Options.ReleaseDirectories) if err != nil { return fmt.Errorf("failed to parse releases: %s", err) @@ -609,29 +621,45 @@ func BakeArgumentsFromKilnfileConfiguration(options *BakeOptions, variables map[ if err != nil { return err } - if len(kf.BakeConfigurations) == 0 { - return nil - } - if tileName, ok := variables[builder.TileNameVariable]; ok { - name, ok := tileName.(string) + + if variableValue, ok := variables[builder.TileNameVariable]; ok { + variableString, ok := variableValue.(string) if !ok { - return fmt.Errorf("%s value must be a string got value %#[2]v with type %[2]T", builder.TileNameVariable, tileName) - } - if index := slices.IndexFunc(kf.BakeConfigurations, func(configuration cargo.BakeConfiguration) bool { - return configuration.TileName == name - }); index >= 0 { - fromConfiguration(options, kf.BakeConfigurations[index]) + return fmt.Errorf("%s value must be a string got value %#[2]v with type %[2]T", builder.TileNameVariable, variableValue) } + options.TileName = variableString + } + + if len(kf.BakeConfigurations) == 0 { + return nil } else if len(kf.BakeConfigurations) == 1 { configuration := kf.BakeConfigurations[0] + if options.TileName != "" && options.TileName != configuration.TileName { + return fmt.Errorf("the provided tile_name %q does not match the configuration %q", options.TileName, configuration.TileName) + } fromConfiguration(options, configuration) - if configuration.TileName != "" { - variables[builder.TileNameVariable] = configuration.TileName + } else if _, ok := variables[builder.TileNameVariable]; ok { + index := slices.IndexFunc(kf.BakeConfigurations, func(configuration cargo.BakeConfiguration) bool { + return configuration.TileName == options.TileName + }) + if index < 0 { + return errorBakeConfigurationNotFound(options, kf) } + fromConfiguration(options, kf.BakeConfigurations[index]) } return nil } +func errorBakeConfigurationNotFound(options *BakeOptions, kf cargo.Kilnfile) error { + names := make([]string, 0, len(kf.BakeConfigurations)) + for _, config := range kf.BakeConfigurations { + names = append(names, config.TileName) + } + slices.Sort(names) + names = slices.Compact(names) + return fmt.Errorf("the provided tile_name %q does not match any configuration. The available names are: %v", options.TileName, names) +} + func fromConfiguration(b *BakeOptions, configuration cargo.BakeConfiguration) { if len(configuration.Metadata) > 0 { b.Metadata = configuration.Metadata diff --git a/internal/commands/bake_test.go b/internal/commands/bake_test.go index 75b316a2..9a14d5e4 100644 --- a/internal/commands/bake_test.go +++ b/internal/commands/bake_test.go @@ -415,6 +415,37 @@ var _ = Describe("Bake", func() { }) }) }) + Context("when bake configuration has multiple options", func() { + BeforeEach(func() { + bake = bake.WithKilnfileFunc(func(s string) (cargo.Kilnfile, error) { + return cargo.Kilnfile{ + BakeConfigurations: []cargo.BakeConfiguration{ + { + TileName: "p-each", + Metadata: "peach.yml", + }, + { + TileName: "p-air", + Metadata: "pair.yml", + }, + { + TileName: "p-lum", + Metadata: "plum.yml", + }, + }, + }, nil + }) + }) + When("a the tile flag is passed", func() { + It("it uses the value from the bake configuration with the correct name", func() { + err := bake.Execute([]string{ + "--tile-name=p-each", + }) + Expect(err).To(Not(HaveOccurred())) + Expect(fakeMetadataService.ReadArgsForCall(0)).To(Equal("peach.yml")) + }) + }) + }) Context("when --stub-releases is specified", func() { It("doesn't fetch releases", func() { err := bake.Execute([]string{ @@ -1017,6 +1048,34 @@ var _ = Describe("BakeArgumentsFromKilnfileConfiguration", func() { Expect(kilnfilePathArg).To(Equal(kilnfilePath)) }) + When("there is one tile configuration", func() { + var loadKilnfile func(string) (cargo.Kilnfile, error) + BeforeEach(func() { + loadKilnfile = func(s string) (cargo.Kilnfile, error) { + return cargo.Kilnfile{ + BakeConfigurations: []cargo.BakeConfiguration{ + {TileName: "peach", Metadata: "peach.yml"}, + }, + }, nil + } + }) + When("tile_name is unset", func() { + It("handles getting the first configuration", func() { + variables := make(map[string]any) + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables, loadKilnfile) + Expect(err).NotTo(HaveOccurred()) + Expect(opts.Metadata).To(Equal("peach.yml")) + }) + }) + When("a tile_name is a variable and does not match the bake configuration", func() { + It("handles getting the first configuration", func() { + variables := map[string]any{builder.TileNameVariable: "banana"} + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables, loadKilnfile) + Expect(err).To(HaveOccurred()) + }) + }) + }) + When("there are multiple tile configurations", func() { var loadKilnfile func(string) (cargo.Kilnfile, error) BeforeEach(func() { @@ -1065,6 +1124,17 @@ var _ = Describe("BakeArgumentsFromKilnfileConfiguration", func() { }) Expect(err).To(MatchError("tile_name value must be a string got value 8675309 with type int")) }) + + It("returns an error for unexpected tile_name type", func() { + variables := map[string]any{"tile_name": 8675309} + + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables, func(s string) (cargo.Kilnfile, error) { + return cargo.Kilnfile{ + BakeConfigurations: []cargo.BakeConfiguration{{TileName: "apple"}}, + }, nil + }) + Expect(err).To(MatchError("tile_name value must be a string got value 8675309 with type int")) + }) }) }) diff --git a/internal/commands/rebake.go b/internal/commands/rebake.go index e5aa29ad..3729618f 100644 --- a/internal/commands/rebake.go +++ b/internal/commands/rebake.go @@ -74,7 +74,7 @@ func (cmd ReBake) Execute(args []string) error { } if !record.IsEquivalent(newRecord, log.New(os.Stderr, "bake record diff: ", 0)) { - return fmt.Errorf("expected tile bake records to be equivilant") + return fmt.Errorf("expected tile bake records to be equivalent") } return nil