diff --git a/README.md b/README.md index 2070c2e0..984215e9 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ Commands: glaze Pin versions in Kilnfile to match lock. help prints this usage information publish publish tile on Pivnet + re-bake re-bake constructs a tile from a bake record release-notes generates release notes from bosh-release release notes sync-with-local update the Kilnfile.lock based on local releases test Test manifest for a product @@ -224,6 +225,13 @@ tile. There are very few reasons a tile developer should want to do this, but if you do, you can include these extra files here. The flag can be specified multiple times to embed multiple files or directories. +##### `--final` + +The `--final` flag is to bake a final release tile. When passing the --final flag, +Kiln creates a baked record file with metadata like source revision SHA, tile version, kiln version and +file checksums. This bake record file will be created under bake_records folder. This +bake record file can later be used to re-bake the tile. + ##### `--forms-directory` The `--forms-directory` flag takes a path to a directory that contains one @@ -519,7 +527,16 @@ provides_product_versions: ``` +### `re-bake` +It constructs a tile from a given bake record file. + +To run the command, you simply need to be within a tile directory and execute the following command: +``` +$ kiln re-bake --output-file tile.pivotal bake_records/1.0.0.json +``` +Any variables that Kilnfile needs for the kiln re-bake command should be set in +~/.kiln/credentials.yml file ### `test` diff --git a/internal/acceptance/bake/bake_test.go b/internal/acceptance/bake/bake_test.go index 089af17d..a677bb80 100644 --- a/internal/acceptance/bake/bake_test.go +++ b/internal/acceptance/bake/bake_test.go @@ -407,7 +407,7 @@ var _ = Describe("bake command", func() { command := exec.Command(pathToMain, commandWithArgs...) session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) Expect(err).NotTo(HaveOccurred()) - Eventually(session.Err).Should(gbytes.Say("non-existent-kilnfile.lock: no such file or directory")) + Eventually(session.Err).Should(gbytes.Say("no such file or directory")) }) }) It("errors out when Kilnfile.lock cannot be unmarshalled", func() { diff --git a/internal/acceptance/workflows/baking_a_tile.feature b/internal/acceptance/workflows/baking_a_tile.feature index e346af95..b88b55b3 100644 --- a/internal/acceptance/workflows/baking_a_tile.feature +++ b/internal/acceptance/workflows/baking_a_tile.feature @@ -26,6 +26,13 @@ Feature: As a developer, I want to bake a tile | --stub-releases | Then a Tile is created + Scenario: it handles tiles with multiple tile names + Given I have a tile source directory "testdata/tiles/multiple-tile-names" + When I invoke kiln + | bake | + | --tile-name=goodbye | + Then a Tile is created + Scenario: it bakes a tile from a bake record Given I have a tile source directory "testdata/tiles/bake-record" When I invoke kiln diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/Kilnfile b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/Kilnfile new file mode 100644 index 00000000..9cef3095 --- /dev/null +++ b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/Kilnfile @@ -0,0 +1,19 @@ +bake_configurations: + - tile_name: hello + metadata_filepath: product_template.yml + variables_filepaths: + - variables/hello.yml + icon_filepath: "gopher.png" + instance_groups_directories: + - job_types + properties_directories: + - configuration + - tile_name: goodbye + metadata_filepath: product_template.yml + variables_filepaths: + - variables/goodbye.yml + icon_filepath: "gopher.png" + instance_groups_directories: + - job_types + properties_directories: + - configuration \ No newline at end of file diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/Kilnfile.lock b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/Kilnfile.lock new file mode 100644 index 00000000..c015566a --- /dev/null +++ b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/Kilnfile.lock @@ -0,0 +1,3 @@ +stemcell_criteria: + os: ubuntu-jammy + version: "1.329" \ No newline at end of file diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/configuration/networking.yml b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/configuration/networking.yml new file mode 100644 index 00000000..eccbf65a --- /dev/null +++ b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/configuration/networking.yml @@ -0,0 +1,5 @@ +--- +- name: port + type: port + configurable: true + default: 8080 \ No newline at end of file diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/gopher.png b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/gopher.png new file mode 100644 index 00000000..3d878e52 Binary files /dev/null and b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/gopher.png differ diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/job_types/hello.yml b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/job_types/hello.yml new file mode 100644 index 00000000..2dc8e682 --- /dev/null +++ b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/job_types/hello.yml @@ -0,0 +1,55 @@ +name: hello-server +label: Server +resource_label: Server +description: HTTP Server + +templates: [] + +static_ip: 1 +dynamic_ip: 0 + +max_in_flight: 1 +single_az_only: true + +instance_definition: + name: instances + type: integer + label: Instances + configurable: true + default: 1 + constraints: + min: 0 + max: 1 + +resource_definitions: + - name: ram + type: integer + label: RAM + configurable: true + default: 1024 + constraints: + min: 1024 + + - name: ephemeral_disk + type: integer + label: Ephemeral Disk + configurable: true + default: 4000 + constraints: + min: 2000 + + - name: persistent_disk + type: integer + label: Persistent Disk + configurable: false + default: 4000 + constraints: + min: 2000 + + - name: cpu + type: integer + label: CPU + configurable: true + default: 1 + constraints: + min: 1 diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/product_template.yml b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/product_template.yml new file mode 100644 index 00000000..aa17ea30 --- /dev/null +++ b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/product_template.yml @@ -0,0 +1,29 @@ +--- +name: $( variable "tile_name" ) +label: "some label" +description: "some description" +icon_image: $( icon ) + +metadata_version: "2.7.0" +minimum_version_for_upgrade: 0.1.0 +product_version: $( version ) +provides_product_versions: + - name: hello + version: $( version ) + +rank: 90 +serial: false + +releases: [] + +stemcell_criteria: $( stemcell ) + +job_types: + - $( instance_group "hello-server" ) + +runtime_configs: [] + +property_blueprints: + - $( property "port" ) + +form_types: [] diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/variables/goodbye.yml b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/variables/goodbye.yml new file mode 100644 index 00000000..6a7a6a2e --- /dev/null +++ b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/variables/goodbye.yml @@ -0,0 +1,2 @@ +label: "goodbye" +description: "some description" \ No newline at end of file diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/variables/hello.yml b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/variables/hello.yml new file mode 100644 index 00000000..5f4bb35b --- /dev/null +++ b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/variables/hello.yml @@ -0,0 +1,2 @@ +label: "hello" +description: "some description" \ No newline at end of file diff --git a/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/version b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/version new file mode 100644 index 00000000..e2cac26c --- /dev/null +++ b/internal/acceptance/workflows/testdata/tiles/multiple-tile-names/version @@ -0,0 +1 @@ +1.2.3 \ No newline at end of file diff --git a/internal/commands/bake.go b/internal/commands/bake.go index 10871584..00de823b 100644 --- a/internal/commands/bake.go +++ b/internal/commands/bake.go @@ -1,7 +1,6 @@ package commands import ( - "bytes" "crypto/sha256" "encoding/hex" "errors" @@ -125,6 +124,8 @@ func NewBake(fs billy.Filesystem, releasesService baking.ReleasesService, outLog writeBakeRecord: writeBakeRecord, + loadKilnfile: cargo.ReadKilnfile, + metadata: metadataService, boshVariables: builder.MetadataPartsDirectoryReader{}, @@ -141,6 +142,13 @@ 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 +} + type writeBakeRecordSignature func(string, string, string, []byte) error type Bake struct { @@ -153,6 +161,8 @@ type Bake struct { stemcell stemcellService releases fromDirectories + loadKilnfile func(string) (cargo.Kilnfile, error) + writeBakeRecord writeBakeRecordSignature KilnVersion string @@ -197,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"` } @@ -213,6 +225,7 @@ func NewBakeWithInterfaces(interpolator interpolator, tileWriter tileWriter, out icon: iconService, metadata: metadataService, writeBakeRecord: writeBakeRecordFn, + loadKilnfile: cargo.ReadKilnfile, boshVariables: boshVariablesService, forms: formsService, @@ -431,11 +444,22 @@ func (b Bake) Execute(args []string) error { b.errLogger.Println("warning: --stemcell-tarball is being deprecated in favor of --stemcells-directory") } + if err := BakeArgumentsFromKilnfileConfiguration(&b.Options, 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) } + if b.Options.TileName != "" { + if tileNameVariable, ok := templateVariables[builder.TileNameVariable]; ok && tileNameVariable != b.Options.TileName { + return fmt.Errorf("tile-name flag value %q does not match tile_name variable %q", b.Options.TileName, tileNameVariable) + } + templateVariables[builder.TileNameVariable] = b.Options.TileName + } + releaseManifests, err := b.releases.FromDirectories(b.Options.ReleaseDirectories) if err != nil { return fmt.Errorf("failed to parse releases: %s", err) @@ -447,14 +471,6 @@ func (b Bake) Execute(args []string) error { // TODO remove when stemcell tarball is deprecated stemcellManifest, err = b.stemcell.FromTarball(b.Options.StemcellTarball) } else if b.Options.Kilnfile != "" { - if err := BakeArgumentsFromKilnfileConfiguration(&b.Options, templateVariables); err != nil { - return fmt.Errorf("failed to parse releases: %s", 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) - } - stemcellManifests, err = b.stemcell.FromKilnfile(b.Options.Kilnfile) } else if len(b.Options.StemcellsDirectories) > 0 { stemcellManifests, err = b.stemcell.FromDirectories(b.Options.StemcellsDirectories) @@ -596,44 +612,46 @@ func (b Bake) Usage() jhanda.Usage { } } -func BakeArgumentsFromKilnfileConfiguration(options *BakeOptions, variables map[string]any) error { - if options.Kilnfile == "" { +func BakeArgumentsFromKilnfileConfiguration(options *BakeOptions, loadKilnfile func(string) (cargo.Kilnfile, error)) error { + if options.Kilnfile == "" || options.StemcellTarball != "" || len(options.StemcellsDirectories) > 0 { return nil } - if variables == nil { - variables = make(map[string]any) - } - buf, err := os.ReadFile(options.Kilnfile) + kf, err := loadKilnfile(options.Kilnfile) if err != nil { - if errors.Is(err, os.ErrNotExist) { - return nil - } return err } - kf, err := cargo.InterpolateAndParseKilnfile(bytes.NewReader(buf), variables) - if err != nil { - return err - } - if tileName, ok := variables[builder.TileNameVariable]; ok { - name, ok := tileName.(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]) - } + + 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 + options.TileName = configuration.TileName + } else { + 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 7b02eff4..45e1b3b7 100644 --- a/internal/commands/bake_test.go +++ b/internal/commands/bake_test.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/pivotal-cf/kiln/pkg/cargo" + "github.com/pivotal-cf/kiln/pkg/bake" . "github.com/onsi/ginkgo" @@ -80,7 +82,7 @@ var _ = Describe("Bake", func() { fakeChecksummer = &fakes.Checksummer{} fakeIconService = &fakes.IconService{} fakeInterpolator = &fakes.Interpolator{} - fakeBakeRecordFunc = new(fakeWriteBakeRecordFunc) + fakeBakeRecordFunc = &fakeWriteBakeRecordFunc{} fakeLogger = log.New(GinkgoWriter, "", 0) @@ -187,6 +189,7 @@ var _ = Describe("Bake", func() { fakeFetcher = &fakes.Fetch{} fakeFetcher.ExecuteReturns(nil) bake = commands.NewBakeWithInterfaces(fakeInterpolator, fakeTileWriter, fakeLogger, fakeLogger, fakeTemplateVariablesService, fakeBOSHVariablesService, fakeReleasesService, fakeStemcellService, fakeFormsService, fakeInstanceGroupsService, fakeJobsService, fakePropertiesService, fakeRuntimeConfigsService, fakeIconService, fakeMetadataService, fakeChecksummer, fakeFetcher, fakeFilesystem, fakeHomeDirFunc, fakeBakeRecordFunc.call) + bake = bake.WithKilnfileFunc(func(s string) (cargo.Kilnfile, error) { return cargo.Kilnfile{}, nil }) }) AfterEach(func() { @@ -395,6 +398,63 @@ var _ = Describe("Bake", func() { Expect(string(fakeBakeRecordFunc.productTemplate)).To(Equal("some-interpolated-metadata"), "it gives the bake recorder the product template") }) + Context("when bake configuration is in the Kilnfile", func() { + BeforeEach(func() { + bake = bake.WithKilnfileFunc(func(s string) (cargo.Kilnfile, error) { + return cargo.Kilnfile{ + BakeConfigurations: []cargo.BakeConfiguration{ + {TileName: "p-each", Metadata: "peach.yml"}, + }, + }, nil + }) + }) + When("a metadata flag is not passed", func() { + It("it uses the value from the bake configuration", func() { + err := bake.Execute([]string{}) + Expect(err).To(Not(HaveOccurred())) + Expect(fakeMetadataService.ReadArgsForCall(0)).To(Equal("peach.yml")) + }) + }) + When("generating metadata", func() { + It("it uses the value from the bake configuration", func() { + err := bake.Execute([]string{}) + Expect(err).To(Not(HaveOccurred())) + input, _, _ := fakeInterpolator.InterpolateArgsForCall(0) + Expect(input.Variables).To(HaveKeyWithValue("tile_name", "p-each")) + }) + }) + }) + 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{ @@ -944,71 +1004,116 @@ var _ = Describe("Bake", func() { var _ = Describe("BakeArgumentsFromKilnfileConfiguration", func() { It("handles empty options and variables", func() { - opts := &commands.BakeOptions{} - variables := map[string]any{} - - err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables) + loadKilnfile := func(s string) (cargo.Kilnfile, error) { + return cargo.Kilnfile{}, nil + } + err := commands.BakeArgumentsFromKilnfileConfiguration(new(commands.BakeOptions), loadKilnfile) Expect(err).NotTo(HaveOccurred()) }) - It("handles a Kilnfile that does not exist", func() { + It("handles when an error occurs loading the kilnfile", func() { opts := &commands.BakeOptions{ Standard: flags.Standard{ - Kilnfile: "/does/not/exist", + Kilnfile: "non-empty-path/Kilnfile", }, } - variables := map[string]any{} - err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables) - Expect(err).NotTo(HaveOccurred()) + loadKilnfile := func(s string) (cargo.Kilnfile, error) { + return cargo.Kilnfile{}, os.ErrNotExist + } + + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, loadKilnfile) + Expect(err).To(HaveOccurred()) }) - const validKilnfile = `--- -release_sources: - - type: s3 - compiled: true - bucket: bucket - region: region - access_key_id: access_key_id - secret_access_key: secret_access_key - path_template: path_template -` When("passing a valid Kilnfile", func() { var opts *commands.BakeOptions - BeforeEach(func() { - tempDir, err := os.MkdirTemp("", "bake_") - Expect(err).NotTo(HaveOccurred()) - - path := filepath.Join(tempDir, "Kilnfile") - Expect(os.WriteFile(path, []byte(validKilnfile), 0o644)).ToNot(HaveOccurred()) + const kilnfilePath = "tile/Kilnfile" + BeforeEach(func() { opts = &commands.BakeOptions{ Standard: flags.Standard{ - Kilnfile: path, + Kilnfile: kilnfilePath, }, } }) - It("handles empty variables", func() { - variables := map[string]any{} - - err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables) + It("handles empty TileName", func() { + var kilnfilePathArg string + loadKilnfile := func(s string) (cargo.Kilnfile, error) { + kilnfilePathArg = s + return cargo.Kilnfile{}, nil + } + opts.TileName = "" + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, loadKilnfile) Expect(err).NotTo(HaveOccurred()) + Expect(kilnfilePathArg).To(Equal(kilnfilePath)) }) - It("handles a valid tile_name variable", func() { - variables := map[string]any{"tile_name": "SRT"} - - err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables) - Expect(err).NotTo(HaveOccurred()) + 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() { + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, 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() { + opts.TileName = "banana" + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, loadKilnfile) + Expect(err).To(HaveOccurred()) + }) + }) }) - It("returns an error for unexpected tile_name type", func() { - variables := map[string]any{"tile_name": 8675309} - - err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables) - Expect(err).To(MatchError("tile_name value must be a string got value 8675309 with type int")) + When("there are multiple tile configurations", 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", + }, + { + TileName: "pair", + Metadata: "pair.yml", + }, + }, + }, nil + } + }) + It("handles getting the first configuration by name", func() { + opts.TileName = "pair" + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, loadKilnfile) + Expect(err).NotTo(HaveOccurred()) + Expect(opts.Metadata).To(Equal("pair.yml")) + }) + It("handles getting the second configuration by name", func() { + opts.TileName = "peach" + err := commands.BakeArgumentsFromKilnfileConfiguration(opts, loadKilnfile) + Expect(err).NotTo(HaveOccurred()) + Expect(opts.Metadata).To(Equal("peach.yml")) + }) + //It("handles getting the first configuration when no tile_name is passed", func() { + // variables := map[string]any{} + // err := commands.BakeArgumentsFromKilnfileConfiguration(opts, variables, loadKilnfile) + // Expect(err).NotTo(HaveOccurred()) + // Expect(opts.Metadata).To(Equal("peach.yml")) + //}) }) }) }) diff --git a/internal/commands/rebake.go b/internal/commands/rebake.go index e5aa29ad..78113a4b 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 @@ -84,6 +84,6 @@ func (cmd ReBake) Usage() jhanda.Usage { return jhanda.Usage{ Description: "re-bake (aka record bake) builds a tile from a bake record. You must check out the repository to the revision of the source_revision in the bake record before running this command.", ShortDescription: "re-bake constructs a tile from a bake record", - Flags: &cmd.Options, + Flags: cmd.Options, } }