diff --git a/cmd/lifecycle/analyzer.go b/cmd/lifecycle/analyzer.go index 0bbdbec27..1421626e1 100644 --- a/cmd/lifecycle/analyzer.go +++ b/cmd/lifecycle/analyzer.go @@ -12,6 +12,7 @@ import ( "github.com/buildpacks/lifecycle/image" "github.com/buildpacks/lifecycle/phase" "github.com/buildpacks/lifecycle/platform" + "github.com/buildpacks/lifecycle/platform/files" "github.com/buildpacks/lifecycle/priv" ) @@ -99,7 +100,7 @@ func (a *analyzeCmd) Exec() error { a.PlatformAPI, &cmd.BuildpackAPIVerifier{}, NewCacheHandler(a.keychain), - phase.Config, + files.Handler, image.NewHandler(a.docker, a.keychain, a.LayoutDir, a.UseLayout, a.InsecureRegistries), image.NewRegistryHandler(a.keychain, a.InsecureRegistries), ) @@ -111,5 +112,5 @@ func (a *analyzeCmd) Exec() error { if err != nil { return cmd.FailErrCode(err, a.CodeFor(platform.AnalyzeError), "analyze") } - return phase.Config.WriteAnalyzed(a.AnalyzedPath, &analyzedMD, cmd.DefaultLogger) + return files.Handler.WriteAnalyzed(a.AnalyzedPath, &analyzedMD, cmd.DefaultLogger) } diff --git a/cmd/lifecycle/builder.go b/cmd/lifecycle/builder.go index 50b78eaa2..872c2b4a4 100644 --- a/cmd/lifecycle/builder.go +++ b/cmd/lifecycle/builder.go @@ -3,12 +3,9 @@ package main import ( "errors" - "github.com/BurntSushi/toml" - "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cmd" "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" - "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/launch" "github.com/buildpacks/lifecycle/phase" "github.com/buildpacks/lifecycle/platform" @@ -66,7 +63,7 @@ func (b *buildCmd) Exec() error { if err = verifyBuildpackApis(group); err != nil { return err } - amd, err := files.ReadAnalyzed(b.AnalyzedPath, cmd.DefaultLogger) + amd, err := files.Handler.ReadAnalyzed(b.AnalyzedPath, cmd.DefaultLogger) if err != nil { return unwrapErrorFailWithMessage(err, "reading analyzed.toml") } @@ -93,10 +90,7 @@ func (b *buildCmd) build(group buildpack.Group, plan files.Plan, analyzedMD file if err != nil { return b.unwrapBuildFail(err) } - if err = encoding.WriteTOML(launch.GetMetadataFilePath(b.LayersDir), md); err != nil { - return cmd.FailErr(err, "write build metadata") - } - return nil + return files.Handler.WriteBuildMetadata(launch.GetMetadataFilePath(b.LayersDir), md) } func (b *buildCmd) unwrapBuildFail(err error) error { @@ -109,14 +103,13 @@ func (b *buildCmd) unwrapBuildFail(err error) error { } func (b *buildCmd) readData() (buildpack.Group, files.Plan, error) { - group, err := phase.ReadGroup(b.GroupPath) + group, _, err := files.Handler.ReadGroup(b.GroupPath) if err != nil { - return buildpack.Group{}, files.Plan{}, cmd.FailErr(err, "read buildpack group") + return buildpack.Group{}, files.Plan{}, err } - - var plan files.Plan - if _, err := toml.DecodeFile(b.PlanPath, &plan); err != nil { - return buildpack.Group{}, files.Plan{}, cmd.FailErr(err, "parse detect plan") + plan, err := files.Handler.ReadPlan(b.PlanPath) + if err != nil { + return buildpack.Group{}, files.Plan{}, err } - return group, plan, nil + return buildpack.Group{Group: group}, plan, nil } diff --git a/cmd/lifecycle/creator.go b/cmd/lifecycle/creator.go index c7e16a31d..fda907924 100644 --- a/cmd/lifecycle/creator.go +++ b/cmd/lifecycle/creator.go @@ -127,7 +127,7 @@ func (c *createCmd) Exec() error { c.PlatformAPI, &cmd.BuildpackAPIVerifier{}, NewCacheHandler(c.keychain), - phase.NewConfigHandler(), + files.NewHandler(), image.NewHandler(c.docker, c.keychain, c.LayoutDir, c.UseLayout, c.InsecureRegistries), image.NewRegistryHandler(c.keychain, c.InsecureRegistries), ) @@ -139,7 +139,7 @@ func (c *createCmd) Exec() error { if err != nil { return err } - if err := phase.Config.WriteAnalyzed(c.AnalyzedPath, &analyzedMD, cmd.DefaultLogger); err != nil { + if err := files.Handler.WriteAnalyzed(c.AnalyzedPath, &analyzedMD, cmd.DefaultLogger); err != nil { return err } @@ -148,7 +148,7 @@ func (c *createCmd) Exec() error { detectorFactory := phase.NewHermeticFactory( c.PlatformAPI, &cmd.BuildpackAPIVerifier{}, - phase.NewConfigHandler(), + files.NewHandler(), dirStore, ) detector, err := detectorFactory.NewDetector(c.Inputs(), cmd.DefaultLogger) diff --git a/cmd/lifecycle/detector.go b/cmd/lifecycle/detector.go index de6c2ddee..458010a86 100644 --- a/cmd/lifecycle/detector.go +++ b/cmd/lifecycle/detector.go @@ -62,7 +62,7 @@ func (d *detectCmd) Exec() error { detectorFactory := phase.NewHermeticFactory( d.PlatformAPI, &cmd.BuildpackAPIVerifier{}, - phase.NewConfigHandler(), + files.NewHandler(), dirStore, ) detector, err := detectorFactory.NewDetector( @@ -85,7 +85,7 @@ func (d *detectCmd) Exec() error { generatorFactory := phase.NewHermeticFactory( d.PlatformAPI, &cmd.BuildpackAPIVerifier{}, - phase.Config, + files.Handler, dirStore, ) var generator *phase.Generator @@ -102,10 +102,10 @@ func (d *detectCmd) Exec() error { if err != nil { return d.unwrapGenerateFail(err) } - if err := phase.Config.WriteAnalyzed(d.AnalyzedPath, &result.AnalyzedMD, cmd.DefaultLogger); err != nil { + if err := files.Handler.WriteAnalyzed(d.AnalyzedPath, &result.AnalyzedMD, cmd.DefaultLogger); err != nil { return err } - if err := phase.Config.WritePlan(d.PlanPath, &result.Plan); err != nil { + if err := files.Handler.WritePlan(d.PlanPath, &result.Plan); err != nil { return err } } @@ -149,10 +149,10 @@ func doDetect(detector *phase.Detector, p *platform.Platform) (buildpack.Group, return buildpack.Group{}, files.Plan{}, cmd.FailErrCode(err, p.CodeFor(platform.DetectError), "detect") } } - if err := phase.Config.WriteGroup(p.GroupPath, &group); err != nil { + if err := files.Handler.WriteGroup(p.GroupPath, &group); err != nil { return buildpack.Group{}, files.Plan{}, err } - if err := phase.Config.WritePlan(p.PlanPath, &plan); err != nil { + if err := files.Handler.WritePlan(p.PlanPath, &plan); err != nil { return buildpack.Group{}, files.Plan{}, err } return group, plan, nil diff --git a/cmd/lifecycle/exporter.go b/cmd/lifecycle/exporter.go index dd1b73aae..05870e20d 100644 --- a/cmd/lifecycle/exporter.go +++ b/cmd/lifecycle/exporter.go @@ -7,7 +7,6 @@ import ( "strconv" "time" - "github.com/BurntSushi/toml" "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/layout" "github.com/buildpacks/imgutil/local" @@ -26,7 +25,6 @@ import ( "github.com/buildpacks/lifecycle/cmd" "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/image" - "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/layers" "github.com/buildpacks/lifecycle/phase" "github.com/buildpacks/lifecycle/platform" @@ -96,7 +94,7 @@ func (e *exportCmd) Args(nargs int, args []string) error { } // read analyzed metadata for use in later stages var err error - e.persistedData.analyzedMD, err = files.ReadAnalyzed(e.AnalyzedPath, cmd.DefaultLogger) + e.persistedData.analyzedMD, err = files.Handler.ReadAnalyzed(e.AnalyzedPath, cmd.DefaultLogger) if err != nil { return err } @@ -131,11 +129,11 @@ func (e *exportCmd) Privileges() error { } func (e *exportCmd) Exec() error { - group, err := phase.ReadGroup(e.GroupPath) + group, _, err := files.Handler.ReadGroup(e.GroupPath) if err != nil { return cmd.FailErr(err, "read buildpack group") } - if err = verifyBuildpackApis(group); err != nil { + if err = verifyBuildpackApis(buildpack.Group{Group: group}); err != nil { return err } cacheStore, err := initCache(e.CacheImageRef, e.CacheDir, e.keychain, e.PlatformAPI.LessThan("0.13")) @@ -147,7 +145,7 @@ func (e *exportCmd) Exec() error { return err } } - return e.export(group, cacheStore, e.persistedData.analyzedMD) + return e.export(buildpack.Group{Group: group}, cacheStore, e.persistedData.analyzedMD) } func (e *exportCmd) registryImages() []string { @@ -162,19 +160,14 @@ func (e *exportCmd) registryImages() []string { func (e *exportCmd) export(group buildpack.Group, cacheStore phase.Cache, analyzedMD files.Analyzed) error { artifactsDir, err := os.MkdirTemp("", "lifecycle.exporter.layer") - if err != nil { return cmd.FailErr(err, "create temp directory") } defer os.RemoveAll(artifactsDir) - var projectMD files.ProjectMetadata - _, err = toml.DecodeFile(e.ProjectMetadataPath, &projectMD) + projectMD, err := files.Handler.ReadProjectMetadata(e.ProjectMetadataPath, cmd.DefaultLogger) if err != nil { - if !os.IsNotExist(err) { - return err - } - cmd.DefaultLogger.Debugf("no project metadata found at path '%s', project metadata will not be exported\n", e.ProjectMetadataPath) + return err } exporter := &phase.Exporter{ @@ -226,7 +219,7 @@ func (e *exportCmd) export(group buildpack.Group, cacheStore phase.Cache, analyz if err != nil { return cmd.FailErrCode(err, e.CodeFor(platform.ExportError), "export") } - if err = encoding.WriteTOML(e.ReportPath, &report); err != nil { + if err = files.Handler.WriteReport(e.ReportPath, &report); err != nil { return cmd.FailErrCode(err, e.CodeFor(platform.ExportError), "write export report") } diff --git a/cmd/lifecycle/extender.go b/cmd/lifecycle/extender.go index 595ae1f85..4e8e2b58c 100644 --- a/cmd/lifecycle/extender.go +++ b/cmd/lifecycle/extender.go @@ -10,6 +10,7 @@ import ( "github.com/buildpacks/lifecycle/internal/extend/kaniko" "github.com/buildpacks/lifecycle/phase" "github.com/buildpacks/lifecycle/platform" + "github.com/buildpacks/lifecycle/platform/files" "github.com/buildpacks/lifecycle/priv" ) @@ -52,7 +53,7 @@ func (e *extendCmd) Privileges() error { } func (e *extendCmd) Exec() error { - extenderFactory := phase.NewHermeticFactory(e.PlatformAPI, &cmd.BuildpackAPIVerifier{}, phase.NewConfigHandler(), platform.NewDirStore("", "")) + extenderFactory := phase.NewHermeticFactory(e.PlatformAPI, &cmd.BuildpackAPIVerifier{}, files.NewHandler(), platform.NewDirStore("", "")) applier, err := kaniko.NewDockerfileApplier() if err != nil { return err diff --git a/cmd/lifecycle/rebaser.go b/cmd/lifecycle/rebaser.go index c687be4e9..e40944a55 100644 --- a/cmd/lifecycle/rebaser.go +++ b/cmd/lifecycle/rebaser.go @@ -15,7 +15,6 @@ import ( "github.com/buildpacks/lifecycle/cmd" "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/image" - "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/phase" "github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/platform/files" @@ -137,8 +136,7 @@ func (r *rebaseCmd) Exec() error { if err != nil { return cmd.FailErrCode(err, r.CodeFor(platform.RebaseError), "rebase") } - - if err := encoding.WriteTOML(r.ReportPath, &report); err != nil { + if err = files.Handler.WriteRebaseReport(r.ReportPath, &report); err != nil { return cmd.FailErrCode(err, r.CodeFor(platform.RebaseError), "write rebase report") } return nil diff --git a/cmd/lifecycle/restorer.go b/cmd/lifecycle/restorer.go index be1d2ed2a..8ef5735fb 100644 --- a/cmd/lifecycle/restorer.go +++ b/cmd/lifecycle/restorer.go @@ -96,7 +96,7 @@ func (r *restoreCmd) Privileges() error { } func (r *restoreCmd) Exec() error { - group, groupExt, err := phase.Config.ReadGroup(r.GroupPath) + group, groupExt, err := files.Handler.ReadGroup(r.GroupPath) if err != nil { return cmd.FailErr(err, "read buildpack group") } @@ -106,7 +106,7 @@ func (r *restoreCmd) Exec() error { groupFile := buildpack.Group{Group: group, GroupExtensions: groupExt} var analyzedMD files.Analyzed - if analyzedMD, err = files.ReadAnalyzed(r.AnalyzedPath, cmd.DefaultLogger); err == nil { + if analyzedMD, err = files.Handler.ReadAnalyzed(r.AnalyzedPath, cmd.DefaultLogger); err == nil { if r.supportsBuildImageExtension() && r.BuildImageRef != "" { cmd.DefaultLogger.Debugf("Pulling builder image metadata for %s...", r.BuildImageRef) remoteBuildImage, err := r.pullSparse(r.BuildImageRef) @@ -148,7 +148,7 @@ func (r *restoreCmd) Exec() error { return cmd.FailErr(err, "update analyzed metadata") } } - if err = encoding.WriteTOML(r.AnalyzedPath, analyzedMD); err != nil { + if err = files.Handler.WriteAnalyzed(r.AnalyzedPath, &analyzedMD, cmd.DefaultLogger); err != nil { return cmd.FailErr(err, "write analyzed metadata") } } else { diff --git a/phase/exporter.go b/phase/exporter.go index 004a041a5..f33057516 100644 --- a/phase/exporter.go +++ b/phase/exporter.go @@ -123,8 +123,8 @@ func (e *Exporter) Export(opts ExportOptions) (files.Report, error) { // ensure we always copy the new RunImage into the old stack to preserve old behavior meta.Stack = &files.Stack{RunImage: opts.RunImageForExport} - buildMD := &files.BuildMetadata{} - if err := files.DecodeBuildMetadata(launch.GetMetadataFilePath(opts.LayersDir), e.PlatformAPI, buildMD); err != nil { + buildMD, err := files.Handler.ReadBuildMetadata(launch.GetMetadataFilePath(opts.LayersDir), e.PlatformAPI) + if err != nil { return files.Report{}, errors.Wrap(err, "read build metadata") } diff --git a/phase/handlers.go b/phase/handlers.go index 033edf50c..d29be0f37 100644 --- a/phase/handlers.go +++ b/phase/handlers.go @@ -1,18 +1,11 @@ package phase import ( - "fmt" - - "github.com/BurntSushi/toml" - "github.com/buildpacks/lifecycle/buildpack" - "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform/files" ) -var Config = &DefaultConfigHandler{} - // CacheHandler wraps initialization of a cache image or cache volume. // //go:generate mockgen -package testmock -destination testmock/cache_handler.go github.com/buildpacks/lifecycle/phase CacheHandler @@ -48,99 +41,3 @@ type ConfigHandler interface { ReadRun(runPath string, logger log.Logger) (files.Run, error) ReadPlan(path string) (files.Plan, error) } - -type DefaultConfigHandler struct{} - -func NewConfigHandler() *DefaultConfigHandler { - return &DefaultConfigHandler{} -} - -// ReadAnalyzed reads the provided analyzed.toml file. -func (h *DefaultConfigHandler) ReadAnalyzed(path string, logger log.Logger) (files.Analyzed, error) { - return files.ReadAnalyzed(path, logger) -} - -// WriteAnalyzed writes the provided analyzed metadata to analyzed.toml. -func (h *DefaultConfigHandler) WriteAnalyzed(path string, analyzedMD *files.Analyzed, logger log.Logger) error { - logger.Debugf("Run image info in analyzed metadata is: ") - logger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage)) - if err := encoding.WriteTOML(path, analyzedMD); err != nil { - return fmt.Errorf("failed to write analyzed: %w", err) - } - return nil -} - -func (h *DefaultConfigHandler) ReadGroup(path string) (buildpackGroup []buildpack.GroupElement, extensionsGroup []buildpack.GroupElement, err error) { - var groupFile buildpack.Group - groupFile, err = ReadGroup(path) - if err != nil { - return nil, nil, fmt.Errorf("failed to read group file: %w", err) - } - return groupFile.Group, groupFile.GroupExtensions, nil -} - -func ReadGroup(path string) (buildpack.Group, error) { - var group buildpack.Group - _, err := toml.DecodeFile(path, &group) - for e := range group.GroupExtensions { - group.GroupExtensions[e].Extension = true - group.GroupExtensions[e].Optional = true - } - return group, err -} - -// WriteGroup writes the provided group information to group.toml. -func (h *DefaultConfigHandler) WriteGroup(path string, group *buildpack.Group) error { - if err := encoding.WriteTOML(path, group); err != nil { - return fmt.Errorf("failed to write group: %w", err) - } - return nil -} - -// ReadPlan reads the provided plan.toml file. -func (h *DefaultConfigHandler) ReadPlan(path string) (files.Plan, error) { - var plan files.Plan - if _, err := toml.DecodeFile(path, &plan); err != nil { - return files.Plan{}, err - } - return plan, nil -} - -// WritePlan writes the provided plan information to plan.toml. -func (h *DefaultConfigHandler) WritePlan(path string, plan *files.Plan) error { - if err := encoding.WriteTOML(path, plan); err != nil { - return fmt.Errorf("failed to write plan: %w", err) - } - return nil -} - -func (h *DefaultConfigHandler) ReadOrder(path string) (buildpack.Order, buildpack.Order, error) { - orderBp, orderExt, err := ReadOrder(path) - if err != nil { - return buildpack.Order{}, buildpack.Order{}, err - } - return orderBp, orderExt, nil -} - -func ReadOrder(path string) (buildpack.Order, buildpack.Order, error) { - var order struct { - Order buildpack.Order `toml:"order"` - OrderExtensions buildpack.Order `toml:"order-extensions"` - } - _, err := toml.DecodeFile(path, &order) - if err != nil { - return nil, nil, fmt.Errorf("failed to read order file: %w", err) - } - for g, group := range order.OrderExtensions { - for e := range group.Group { - group.Group[e].Extension = true - group.Group[e].Optional = true - } - order.OrderExtensions[g] = group - } - return order.Order, order.OrderExtensions, err -} - -func (h *DefaultConfigHandler) ReadRun(runPath string, logger log.Logger) (files.Run, error) { - return files.ReadRun(runPath, logger) -} diff --git a/phase/handlers_test.go b/phase/handlers_test.go index 6518f67d7..891465868 100644 --- a/phase/handlers_test.go +++ b/phase/handlers_test.go @@ -9,7 +9,7 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle/buildpack" - "github.com/buildpacks/lifecycle/phase" + "github.com/buildpacks/lifecycle/platform/files" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -80,19 +80,17 @@ group = [{id = "D", version = "v1"}] when("#ReadGroup", func() { it("returns a single group object with a buildpack group and an extensions group", func() { h.Mkfile(t, groupTOMLContents, filepath.Join(tmpDir, "group.toml")) - foundGroup, err := phase.ReadGroup(filepath.Join(tmpDir, "group.toml")) + foundGroup, foundGroupExt, err := files.Handler.ReadGroup(filepath.Join(tmpDir, "group.toml")) h.AssertNil(t, err) - h.AssertEq(t, foundGroup, buildpack.Group{ - Group: expectedGroupBp, - GroupExtensions: expectedGroupExt, - }) + h.AssertEq(t, foundGroup, expectedGroupBp) + h.AssertEq(t, foundGroupExt, expectedGroupExt) }) }) when("#ReadOrder", func() { it("returns an ordering of buildpacks and an ordering of extensions", func() { h.Mkfile(t, orderTOMLContents, filepath.Join(tmpDir, "order.toml")) - foundOrder, foundOrderExt, err := phase.ReadOrder(filepath.Join(tmpDir, "order.toml")) + foundOrder, foundOrderExt, err := files.Handler.ReadOrder(filepath.Join(tmpDir, "order.toml")) h.AssertNil(t, err) h.AssertEq(t, foundOrder, expectedOrderBp) h.AssertEq(t, foundOrderExt, expectedOrderExt) @@ -101,11 +99,11 @@ group = [{id = "D", version = "v1"}] when("DefaultConfigHandler", func() { var ( - configHandler *phase.DefaultConfigHandler + configHandler *files.TOMLHandler ) it.Before(func() { - configHandler = phase.NewConfigHandler() + configHandler = files.NewHandler() }) when(".ReadGroup", func() { diff --git a/phase/rebaser.go b/phase/rebaser.go index 7887332bf..e6a02a7e6 100644 --- a/phase/rebaser.go +++ b/phase/rebaser.go @@ -31,49 +31,46 @@ type Rebaser struct { Force bool } -type RebaseReport struct { - Image files.ImageReport `toml:"image"` -} - -func (r *Rebaser) Rebase(workingImage imgutil.Image, newBaseImage imgutil.Image, outputImageRef string, additionalNames []string) (RebaseReport, error) { +// Rebase changes the underlying base image for an application image. +func (r *Rebaser) Rebase(workingImage imgutil.Image, newBaseImage imgutil.Image, outputImageRef string, additionalNames []string) (files.RebaseReport, error) { defer log.NewMeasurement("Rebaser", r.Logger)() appPlatformAPI, err := workingImage.Env(platform.EnvPlatformAPI) if err != nil { - return RebaseReport{}, fmt.Errorf("failed to get app image platform API: %w", err) + return files.RebaseReport{}, fmt.Errorf("failed to get app image platform API: %w", err) } // perform platform API-specific validations if appPlatformAPI == "" || api.MustParse(appPlatformAPI).LessThan("0.12") { if err = validateStackID(workingImage, newBaseImage); err != nil { - return RebaseReport{}, err + return files.RebaseReport{}, err } if err = validateMixins(workingImage, newBaseImage); err != nil { - return RebaseReport{}, err + return files.RebaseReport{}, err } } else { if err = r.validateTarget(workingImage, newBaseImage); err != nil { - return RebaseReport{}, err + return files.RebaseReport{}, err } } // get existing metadata label var origMetadata files.LayersMetadataCompat if err = image.DecodeLabel(workingImage, platform.LifecycleMetadataLabel, &origMetadata); err != nil { - return RebaseReport{}, fmt.Errorf("get image metadata: %w", err) + return files.RebaseReport{}, fmt.Errorf("get image metadata: %w", err) } // rebase if err = workingImage.Rebase(origMetadata.RunImage.TopLayer, newBaseImage); err != nil { - return RebaseReport{}, fmt.Errorf("rebase app image: %w", err) + return files.RebaseReport{}, fmt.Errorf("rebase app image: %w", err) } // update metadata label origMetadata.RunImage.TopLayer, err = newBaseImage.TopLayer() if err != nil { - return RebaseReport{}, fmt.Errorf("get rebase run image top layer SHA: %w", err) + return files.RebaseReport{}, fmt.Errorf("get rebase run image top layer SHA: %w", err) } identifier, err := newBaseImage.Identifier() if err != nil { - return RebaseReport{}, fmt.Errorf("get run image id or digest: %w", err) + return files.RebaseReport{}, fmt.Errorf("get run image id or digest: %w", err) } origMetadata.RunImage.Reference = identifier.String() if r.PlatformAPI.AtLeast("0.12") { @@ -100,7 +97,7 @@ func (r *Rebaser) Rebase(workingImage imgutil.Image, newBaseImage imgutil.Image, newStackMD := origMetadata.RunImage.ToStack() origMetadata.Stack = &newStackMD } else { - return RebaseReport{}, fmt.Errorf( + return files.RebaseReport{}, fmt.Errorf( msgRunImageMDNotContainsName+"; "+msgProvideForceToOverride, newBaseImage.Name(), existingRunImageMD, @@ -112,10 +109,10 @@ func (r *Rebaser) Rebase(workingImage imgutil.Image, newBaseImage imgutil.Image, // set metadata label data, err := json.Marshal(origMetadata) if err != nil { - return RebaseReport{}, fmt.Errorf("marshall metadata: %w", err) + return files.RebaseReport{}, fmt.Errorf("marshall metadata: %w", err) } if err := workingImage.SetLabel(platform.LifecycleMetadataLabel, string(data)); err != nil { - return RebaseReport{}, fmt.Errorf("set app image metadata label: %w", err) + return files.RebaseReport{}, fmt.Errorf("set app image metadata label: %w", err) } // update other labels @@ -126,14 +123,14 @@ func (r *Rebaser) Rebase(workingImage imgutil.Image, newBaseImage imgutil.Image, return strings.HasPrefix(l, "io.buildpacks.stack.") } if err := image.SyncLabels(newBaseImage, workingImage, hasPrefix); err != nil { - return RebaseReport{}, fmt.Errorf("set stack labels: %w", err) + return files.RebaseReport{}, fmt.Errorf("set stack labels: %w", err) } // save - report := RebaseReport{} + report := files.RebaseReport{} report.Image, err = saveImageAs(workingImage, outputImageRef, additionalNames, r.Logger) if err != nil { - return RebaseReport{}, err + return files.RebaseReport{}, err } return report, err } diff --git a/platform/files/analyzed.go b/platform/files/analyzed.go index 9eddedb5b..b2b0709e6 100644 --- a/platform/files/analyzed.go +++ b/platform/files/analyzed.go @@ -1,13 +1,8 @@ package files import ( - "os" - - "github.com/BurntSushi/toml" - "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/internal/encoding" - "github.com/buildpacks/lifecycle/log" ) // Analyzed is written by the analyzer as analyzed.toml and updated in subsequent phases to record information about: @@ -33,18 +28,6 @@ type Analyzed struct { RunImage *RunImage `toml:"run-image,omitempty"` } -func ReadAnalyzed(path string, logger log.Logger) (Analyzed, error) { - var analyzed Analyzed - if _, err := toml.DecodeFile(path, &analyzed); err != nil { - if os.IsNotExist(err) { - logger.Warnf("no analyzed metadata found at path '%s'", path) - return Analyzed{}, nil - } - return Analyzed{}, err - } - return analyzed, nil -} - func (a Analyzed) PreviousImageRef() string { if a.PreviousImage == nil { return "" diff --git a/platform/files/analyzed_test.go b/platform/files/analyzed_test.go index 51169536d..4fc27d894 100644 --- a/platform/files/analyzed_test.go +++ b/platform/files/analyzed_test.go @@ -30,7 +30,7 @@ func testAnalyzed(t *testing.T, when spec.G, it spec.S) { } f := h.TempFile(t, "", "") h.AssertNil(t, encoding.WriteTOML(f, amd)) - amd2, err := files.ReadAnalyzed(f, nil) + amd2, err := files.Handler.ReadAnalyzed(f, nil) h.AssertNil(t, err) h.AssertEq(t, amd.PreviousImageRef(), amd2.PreviousImageRef()) h.AssertEq(t, amd.LayersMetadata, amd2.LayersMetadata) @@ -87,7 +87,7 @@ func testAnalyzed(t *testing.T, when spec.G, it spec.S) { } f := h.TempFile(t, "", "") h.AssertNil(t, encoding.WriteTOML(f, amd)) - amd2, err := files.ReadAnalyzed(f, nil) + amd2, err := files.Handler.ReadAnalyzed(f, nil) h.AssertNil(t, err) h.AssertEq(t, amd.PreviousImageRef(), amd2.PreviousImageRef()) h.AssertEq(t, amd.LayersMetadata, amd2.LayersMetadata) diff --git a/platform/files/handle_toml.go b/platform/files/handle_toml.go new file mode 100644 index 000000000..35fc7b087 --- /dev/null +++ b/platform/files/handle_toml.go @@ -0,0 +1,204 @@ +// Package files contains schema and helper methods for working with lifecycle configuration files. +package files + +import ( + "fmt" + "os" + + "github.com/BurntSushi/toml" + + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/buildpack" + "github.com/buildpacks/lifecycle/internal/encoding" + "github.com/buildpacks/lifecycle/log" +) + +// Handler is the default handler used to read and write lifecycle configuration files. +var Handler = &TOMLHandler{} + +// TOMLHandler reads and writes lifecycle configuration files in TOML format. +type TOMLHandler struct{} + +// NewHandler returns a new file handler. +func NewHandler() *TOMLHandler { + return &TOMLHandler{} +} + +// ReadAnalyzed reads the provided analyzed.toml file. +// It logs a warning and returns empty analyzed metadata if the file does not exist. +func (h *TOMLHandler) ReadAnalyzed(path string, logger log.Logger) (Analyzed, error) { + var analyzed Analyzed + if _, err := toml.DecodeFile(path, &analyzed); err != nil { + if os.IsNotExist(err) { + logger.Warnf("No analyzed metadata found at path %q", path) + return Analyzed{}, nil + } + return Analyzed{}, fmt.Errorf("failed to read analyzed file: %w", err) + } + return analyzed, nil +} + +// WriteAnalyzed writes the provided analyzed metadata at the provided path. +func (h *TOMLHandler) WriteAnalyzed(path string, analyzedMD *Analyzed, logger log.Logger) error { + logger.Debugf("Run image info in analyzed metadata is: ") + logger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage)) + if err := encoding.WriteTOML(path, analyzedMD); err != nil { + return fmt.Errorf("failed to write analyzed file: %w", err) + } + return nil +} + +// ReadGroup reads the provided group.toml file. +func (h *TOMLHandler) ReadGroup(path string) (buildpackGroup []buildpack.GroupElement, extensionsGroup []buildpack.GroupElement, err error) { + var groupFile buildpack.Group + groupFile, err = readGroup(path) + if err != nil { + return nil, nil, err + } + return groupFile.Group, groupFile.GroupExtensions, nil +} + +func readGroup(path string) (buildpack.Group, error) { + var group buildpack.Group + _, err := toml.DecodeFile(path, &group) + if err != nil { + return buildpack.Group{}, fmt.Errorf("failed to read group file: %w", err) + } + for e := range group.GroupExtensions { + group.GroupExtensions[e].Extension = true + group.GroupExtensions[e].Optional = true + } + return group, nil +} + +// WriteGroup writes the provided group information at the provided path. +func (h *TOMLHandler) WriteGroup(path string, group *buildpack.Group) error { + if err := encoding.WriteTOML(path, group); err != nil { + return fmt.Errorf("failed to write group file: %w", err) + } + return nil +} + +// ReadBuildMetadata reads the provided metadata.toml file, +// and sets the provided Platform API version on the returned struct so that the data can be re-encoded properly later. +func (h *TOMLHandler) ReadBuildMetadata(path string, platformAPI *api.Version) (*BuildMetadata, error) { + buildMD := BuildMetadata{} + _, err := toml.DecodeFile(path, &buildMD) + if err != nil { + return nil, fmt.Errorf("failed to read build metadata file: %w", err) + } + buildMD.PlatformAPI = platformAPI + for i, process := range buildMD.Processes { + buildMD.Processes[i] = process.WithPlatformAPI(platformAPI) + } + return &buildMD, nil +} + +// WriteBuildMetadata writes the provided build metadata at the provided path. +func (h *TOMLHandler) WriteBuildMetadata(path string, buildMD *BuildMetadata) error { + if err := encoding.WriteTOML(path, buildMD); err != nil { + return fmt.Errorf("failed to write build metadata file: %w", err) + } + return nil +} + +// ReadOrder reads the provided order.toml file. +func (h *TOMLHandler) ReadOrder(path string) (buildpack.Order, buildpack.Order, error) { + orderBp, orderExt, err := readOrder(path) + if err != nil { + return buildpack.Order{}, buildpack.Order{}, err + } + return orderBp, orderExt, nil +} + +func readOrder(path string) (buildpack.Order, buildpack.Order, error) { + var order struct { + Order buildpack.Order `toml:"order"` + OrderExtensions buildpack.Order `toml:"order-extensions"` + } + _, err := toml.DecodeFile(path, &order) + if err != nil { + return nil, nil, fmt.Errorf("failed to read order file: %w", err) + } + for g, group := range order.OrderExtensions { + for e := range group.Group { + group.Group[e].Extension = true + group.Group[e].Optional = true + } + order.OrderExtensions[g] = group + } + return order.Order, order.OrderExtensions, nil +} + +// ReadPlan reads the provided plan.toml file. +func (h *TOMLHandler) ReadPlan(path string) (Plan, error) { + var plan Plan + if _, err := toml.DecodeFile(path, &plan); err != nil { + return Plan{}, fmt.Errorf("failed to read plan file: %w", err) + } + return plan, nil +} + +// WritePlan writes the provided plan information at the provided path. +func (h *TOMLHandler) WritePlan(path string, plan *Plan) error { + if err := encoding.WriteTOML(path, plan); err != nil { + return fmt.Errorf("failed to write plan file: %w", err) + } + return nil +} + +// ReadProjectMetadata reads the provided project_metadata.toml file. +// It logs a warning and returns empty project metadata if the file does not exist. +func (h *TOMLHandler) ReadProjectMetadata(path string, logger log.Logger) (ProjectMetadata, error) { + var projectMD ProjectMetadata + if _, err := toml.DecodeFile(path, &projectMD); err != nil { + if os.IsNotExist(err) { + logger.Debugf("No project metadata found at path %q, project metadata will not be exported", path) + return ProjectMetadata{}, nil + } + return ProjectMetadata{}, fmt.Errorf("failed to read project metadata file: %w", err) + } + return projectMD, nil +} + +// WriteReport writes the provided report information at the provided path. +func (h *TOMLHandler) WriteReport(path string, report *Report) error { + if err := encoding.WriteTOML(path, report); err != nil { + return fmt.Errorf("failed to write report file: %w", err) + } + return nil +} + +// WriteRebaseReport writes the provided report information at the provided path. +func (h *TOMLHandler) WriteRebaseReport(path string, report *RebaseReport) error { + if err := encoding.WriteTOML(path, report); err != nil { + return fmt.Errorf("failed to write report file: %w", err) + } + return nil +} + +// ReadRun reads the provided run.toml file. +func (h *TOMLHandler) ReadRun(path string, logger log.Logger) (Run, error) { + var runMD Run + if _, err := toml.DecodeFile(path, &runMD); err != nil { + if os.IsNotExist(err) { + logger.Infof("No run metadata found at path %q", path) + return Run{}, nil + } + return Run{}, fmt.Errorf("failed to read run file: %w", err) + } + return runMD, nil +} + +// ReadStack reads the provided stack.toml file. +func (h *TOMLHandler) ReadStack(path string, logger log.Logger) (Stack, error) { + var stackMD Stack + if _, err := toml.DecodeFile(path, &stackMD); err != nil { + if os.IsNotExist(err) { + logger.Infof("No stack metadata found at path %q", path) + return Stack{}, nil + } + return Stack{}, fmt.Errorf("failed to read stack file: %w", err) + } + return stackMD, nil +} diff --git a/platform/files/metadata.go b/platform/files/metadata.go index 95eee2261..e023e8b36 100644 --- a/platform/files/metadata.go +++ b/platform/files/metadata.go @@ -3,8 +3,6 @@ package files import ( "encoding/json" - "github.com/BurntSushi/toml" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/launch" @@ -36,25 +34,6 @@ type BuildMetadata struct { PlatformAPI *api.Version `toml:"-" json:"-"` } -// DecodeBuildMetadata reads a metadata.toml file -func DecodeBuildMetadata(path string, platformAPI *api.Version, buildmd *BuildMetadata) error { - // decode the common bits - _, err := toml.DecodeFile(path, &buildmd) - if err != nil { - return err - } - - // set the platform API on all the appropriate fields - // this will allow us to re-encode the metadata.toml file with - // the current platform API - buildmd.PlatformAPI = platformAPI - for i, process := range buildmd.Processes { - buildmd.Processes[i] = process.WithPlatformAPI(platformAPI) - } - - return nil -} - func (m *BuildMetadata) MarshalJSON() ([]byte, error) { if m.PlatformAPI == nil || m.PlatformAPI.LessThan("0.9") { return json.Marshal(*m) diff --git a/platform/files/report.go b/platform/files/report.go index 00b5404b1..c29e86508 100644 --- a/platform/files/report.go +++ b/platform/files/report.go @@ -2,7 +2,7 @@ package files import "github.com/buildpacks/lifecycle/buildpack" -// Report is written by the exporter as report.toml to record information about the build. +// Report is written by the exporter to record information about the build. // It is not included in the output image, but can be saved off by the platform before the build container exits. // The location of the file can be specified by providing `-report ` to the lifecycle. type Report struct { @@ -20,3 +20,8 @@ type ImageReport struct { Digest string `toml:"digest,omitempty"` ManifestSize int64 `toml:"manifest-size,omitzero"` } + +// RebaseReport is written by the rebaser to record information about the rebased image. +type RebaseReport struct { + Image ImageReport `toml:"image"` +} diff --git a/platform/files/run.go b/platform/files/run.go index 84866745c..00fbe038a 100644 --- a/platform/files/run.go +++ b/platform/files/run.go @@ -1,13 +1,5 @@ package files -import ( - "os" - - "github.com/BurntSushi/toml" - - "github.com/buildpacks/lifecycle/log" -) - // Run is provided by the platform as run.toml to record information about the run images // that may be used during export. // Data from the selected run image is serialized by the exporter as the `runImage` key in the `io.buildpacks.lifecycle.metadata` label @@ -27,15 +19,3 @@ func (r *Run) Contains(providedImage string) bool { } return false } - -func ReadRun(runPath string, logger log.Logger) (Run, error) { - var runMD Run - if _, err := toml.DecodeFile(runPath, &runMD); err != nil { - if os.IsNotExist(err) { - logger.Infof("no run metadata found at path '%s'\n", runPath) - return Run{}, nil - } - return Run{}, err - } - return runMD, nil -} diff --git a/platform/files/stack.go b/platform/files/stack.go index 7931d8340..70ed450c2 100644 --- a/platform/files/stack.go +++ b/platform/files/stack.go @@ -1,12 +1,7 @@ package files import ( - "os" - - "github.com/BurntSushi/toml" - iname "github.com/buildpacks/lifecycle/internal/name" - "github.com/buildpacks/lifecycle/log" ) // Stack (deprecated as of Platform API 0.12) is provided by the platform as stack.toml to record information about the run images @@ -37,15 +32,3 @@ func (r *RunImageForExport) Contains(providedImage string) bool { } return false } - -func ReadStack(stackPath string, logger log.Logger) (Stack, error) { - var stackMD Stack - if _, err := toml.DecodeFile(stackPath, &stackMD); err != nil { - if os.IsNotExist(err) { - logger.Infof("no stack metadata found at path '%s'\n", stackPath) - return Stack{}, nil - } - return Stack{}, err - } - return stackMD, nil -} diff --git a/platform/resolve_inputs.go b/platform/resolve_inputs.go index 68d633be1..257b88ee4 100644 --- a/platform/resolve_inputs.go +++ b/platform/resolve_inputs.go @@ -133,7 +133,7 @@ func FillExportRunImage(i *LifecycleInputs, logger log.Logger) error { case i.DeprecatedRunImageRef != "": return errors.New(ErrImageUnsupported) default: - analyzedMD, err := files.ReadAnalyzed(i.AnalyzedPath, logger) + analyzedMD, err := files.Handler.ReadAnalyzed(i.AnalyzedPath, logger) if err != nil { return err } @@ -156,7 +156,7 @@ func fillRunImageFromRunTOMLIfNeeded(i *LifecycleInputs, logger log.Logger) erro if err != nil { return err } - runMD, err := files.ReadRun(i.RunPath, logger) + runMD, err := files.Handler.ReadRun(i.RunPath, logger) if err != nil { return err } @@ -177,7 +177,7 @@ func fillRunImageFromStackTOMLIfNeeded(i *LifecycleInputs, logger log.Logger) er if err != nil { return err } - stackMD, err := files.ReadStack(i.StackPath, logger) + stackMD, err := files.Handler.ReadStack(i.StackPath, logger) if err != nil { return err } diff --git a/platform/run_image.go b/platform/run_image.go index 12cc283b7..c80e6b283 100644 --- a/platform/run_image.go +++ b/platform/run_image.go @@ -80,20 +80,20 @@ func byRegistry(reg string, images []string, checkReadAccess CheckReadAccess, ke // as the run image "reference" could be a daemon image ID (which we'd not expect to find in run.toml). func GetRunImageForExport(inputs LifecycleInputs) (files.RunImageForExport, error) { if inputs.PlatformAPI.LessThan("0.12") { - stackMD, err := files.ReadStack(inputs.StackPath, cmd.DefaultLogger) + stackMD, err := files.Handler.ReadStack(inputs.StackPath, cmd.DefaultLogger) if err != nil { return files.RunImageForExport{}, err } return stackMD.RunImage, nil } - runMD, err := files.ReadRun(inputs.RunPath, cmd.DefaultLogger) + runMD, err := files.Handler.ReadRun(inputs.RunPath, cmd.DefaultLogger) if err != nil { return files.RunImageForExport{}, err } if len(runMD.Images) == 0 { return files.RunImageForExport{}, nil } - analyzedMD, err := files.ReadAnalyzed(inputs.AnalyzedPath, cmd.DefaultLogger) + analyzedMD, err := files.Handler.ReadAnalyzed(inputs.AnalyzedPath, cmd.DefaultLogger) if err != nil { return files.RunImageForExport{}, err } @@ -102,8 +102,8 @@ func GetRunImageForExport(inputs LifecycleInputs) (files.RunImageForExport, erro return runImage, nil } } - buildMD := &files.BuildMetadata{} - if err = files.DecodeBuildMetadata(launch.GetMetadataFilePath(inputs.LayersDir), inputs.PlatformAPI, buildMD); err != nil { + buildMD, err := files.Handler.ReadBuildMetadata(launch.GetMetadataFilePath(inputs.LayersDir), inputs.PlatformAPI) + if err != nil { return files.RunImageForExport{}, err } if len(buildMD.Extensions) > 0 { // FIXME: try to know for sure if extensions were used to switch the run image