Repository: buildpacks/lifecycle
Pull request #1207 (2023-09-25 15:02:05)

Refactor lifecycle phase factories
<!-- 🎉🎉🎉 Thank you for the PR!!! 🎉🎉🎉 -->

### Summary
<!-- Please describe your changes at a high level. -->

This PR builds off of https://github.com/buildpacks/lifecycle/pull/1205 to change lifecycle "factories" (for the phases that use them) to use `platform.LifecycleInputs` instead of a long list of arguments. This will pave the way for us adding factories for the other phases later.

#### Release notes
<!-- Please provide 1-2 sentences for release notes. -->
<!-- Example: When using platform API `0.7` or greater, the `creator` logs the expected phase header for the analyze phase -->

Refactor: lifecycle phase factories



Patch:
diff --git a/cmd/lifecycle/analyzer.go b/cmd/lifecycle/analyzer.go
index 33deba28..0bbdbec2 100644
--- a/cmd/lifecycle/analyzer.go
+++ b/cmd/lifecycle/analyzer.go
@@ -10,7 +10,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/priv"
@@ -96,15 +95,15 @@ func (a *analyzeCmd) Privileges() error {
 
 // Exec executes the command.
 func (a *analyzeCmd) Exec() error {
-	factory := phase.NewAnalyzerFactory(
+	factory := phase.NewConnectedFactory(
 		a.PlatformAPI,
 		&cmd.BuildpackAPIVerifier{},
 		NewCacheHandler(a.keychain),
-		phase.NewConfigHandler(),
+		phase.Config,
 		image.NewHandler(a.docker, a.keychain, a.LayoutDir, a.UseLayout, a.InsecureRegistries),
 		image.NewRegistryHandler(a.keychain, a.InsecureRegistries),
 	)
-	analyzer, err := factory.NewAnalyzer(a.AdditionalTags, a.CacheImageRef, a.LaunchCacheDir, a.LayersDir, a.OutputImageRef, a.PreviousImageRef, a.RunImageRef, a.SkipLayers, cmd.DefaultLogger)
+	analyzer, err := factory.NewAnalyzer(a.Inputs(), cmd.DefaultLogger)
 	if err != nil {
 		return unwrapErrorFailWithMessage(err, "initialize analyzer")
 	}
@@ -112,10 +111,5 @@ func (a *analyzeCmd) Exec() error {
 	if err != nil {
 		return cmd.FailErrCode(err, a.CodeFor(platform.AnalyzeError), "analyze")
 	}
-	cmd.DefaultLogger.Debugf("Run image info in analyzed metadata is: ")
-	cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage))
-	if err = encoding.WriteTOML(a.AnalyzedPath, analyzedMD); err != nil {
-		return cmd.FailErr(err, "write analyzed")
-	}
-	return nil
+	return phase.Config.WriteAnalyzed(a.AnalyzedPath, &analyzedMD, cmd.DefaultLogger)
 }
diff --git a/cmd/lifecycle/creator.go b/cmd/lifecycle/creator.go
index 223718f4..c7e16a31 100644
--- a/cmd/lifecycle/creator.go
+++ b/cmd/lifecycle/creator.go
@@ -123,7 +123,7 @@ func (c *createCmd) Exec() error {
 		plan       files.Plan
 	)
 	cmd.DefaultLogger.Phase("ANALYZING")
-	analyzerFactory := phase.NewAnalyzerFactory(
+	analyzerFactory := phase.NewConnectedFactory(
 		c.PlatformAPI,
 		&cmd.BuildpackAPIVerifier{},
 		NewCacheHandler(c.keychain),
@@ -131,7 +131,7 @@ func (c *createCmd) Exec() error {
 		image.NewHandler(c.docker, c.keychain, c.LayoutDir, c.UseLayout, c.InsecureRegistries),
 		image.NewRegistryHandler(c.keychain, c.InsecureRegistries),
 	)
-	analyzer, err := analyzerFactory.NewAnalyzer(c.AdditionalTags, c.CacheImageRef, c.LaunchCacheDir, c.LayersDir, c.OutputImageRef, c.PreviousImageRef, c.RunImageRef, c.SkipLayers, cmd.DefaultLogger)
+	analyzer, err := analyzerFactory.NewAnalyzer(c.Inputs(), cmd.DefaultLogger)
 	if err != nil {
 		return unwrapErrorFailWithMessage(err, "initialize analyzer")
 	}
@@ -139,16 +139,19 @@ func (c *createCmd) Exec() error {
 	if err != nil {
 		return err
 	}
+	if err := phase.Config.WriteAnalyzed(c.AnalyzedPath, &analyzedMD, cmd.DefaultLogger); err != nil {
+		return err
+	}
 
 	// Detect
 	cmd.DefaultLogger.Phase("DETECTING")
-	detectorFactory := phase.NewDetectorFactory(
+	detectorFactory := phase.NewHermeticFactory(
 		c.PlatformAPI,
 		&cmd.BuildpackAPIVerifier{},
 		phase.NewConfigHandler(),
 		dirStore,
 	)
-	detector, err := detectorFactory.NewDetector(analyzedMD, c.AppDir, c.BuildConfigDir, c.OrderPath, c.PlatformDir, cmd.DefaultLogger)
+	detector, err := detectorFactory.NewDetector(c.Inputs(), cmd.DefaultLogger)
 	if err != nil {
 		return unwrapErrorFailWithMessage(err, "initialize detector")
 	}
@@ -164,8 +167,7 @@ func (c *createCmd) Exec() error {
 			Platform: c.Platform,
 			keychain: c.keychain,
 		}
-		err := restoreCmd.restore(analyzedMD.LayersMetadata, group, cacheStore)
-		if err != nil {
+		if err := restoreCmd.restore(analyzedMD.LayersMetadata, group, cacheStore); err != nil {
 			return err
 		}
 	}
diff --git a/cmd/lifecycle/detector.go b/cmd/lifecycle/detector.go
index 3be1dcfd..de6c2dde 100644
--- a/cmd/lifecycle/detector.go
+++ b/cmd/lifecycle/detector.go
@@ -6,7 +6,6 @@ import (
 	"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/phase"
 	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
@@ -60,22 +59,14 @@ func (d *detectCmd) Privileges() error {
 
 func (d *detectCmd) Exec() error {
 	dirStore := platform.NewDirStore(d.BuildpacksDir, d.ExtensionsDir)
-	detectorFactory := phase.NewDetectorFactory(
+	detectorFactory := phase.NewHermeticFactory(
 		d.PlatformAPI,
 		&cmd.BuildpackAPIVerifier{},
 		phase.NewConfigHandler(),
 		dirStore,
 	)
-	amd, err := files.ReadAnalyzed(d.AnalyzedPath, cmd.DefaultLogger)
-	if err != nil {
-		return unwrapErrorFailWithMessage(err, "reading analyzed.toml")
-	}
 	detector, err := detectorFactory.NewDetector(
-		amd,
-		d.AppDir,
-		d.BuildConfigDir,
-		d.OrderPath,
-		d.PlatformDir,
+		d.Inputs(),
 		cmd.DefaultLogger,
 	)
 	if err != nil {
@@ -86,27 +77,20 @@ func (d *detectCmd) Exec() error {
 			return err
 		}
 	}
-	group, plan, err := doDetect(detector, d.Platform)
+	group, _, err := doDetect(detector, d.Platform)
 	if err != nil {
 		return err // pass through error
 	}
 	if group.HasExtensions() {
-		generatorFactory := phase.NewGeneratorFactory(
+		generatorFactory := phase.NewHermeticFactory(
+			d.PlatformAPI,
 			&cmd.BuildpackAPIVerifier{},
 			phase.Config,
 			dirStore,
 		)
 		var generator *phase.Generator
 		generator, err = generatorFactory.NewGenerator(
-			d.AnalyzedPath,
-			d.AppDir,
-			d.BuildConfigDir,
-			group.GroupExtensions,
-			d.GeneratedDir,
-			plan,
-			d.PlatformAPI,
-			d.PlatformDir,
-			d.RunPath,
+			d.Inputs(),
 			cmd.Stdout, cmd.Stderr,
 			cmd.DefaultLogger,
 		)
@@ -118,16 +102,14 @@ func (d *detectCmd) Exec() error {
 		if err != nil {
 			return d.unwrapGenerateFail(err)
 		}
-
-		if err = d.writeGenerateData(result.AnalyzedMD); err != nil {
+		if err := phase.Config.WriteAnalyzed(d.AnalyzedPath, &result.AnalyzedMD, cmd.DefaultLogger); err != nil {
 			return err
 		}
-		// was the build plan updated?
-		if result.UsePlan {
-			plan = result.Plan
+		if err := phase.Config.WritePlan(d.PlanPath, &result.Plan); err != nil {
+			return err
 		}
 	}
-	return d.writeDetectData(group, plan)
+	return nil
 }
 
 func unwrapErrorFailWithMessage(err error, msg string) error {
@@ -167,25 +149,11 @@ func doDetect(detector *phase.Detector, p *platform.Platform) (buildpack.Group,
 			return buildpack.Group{}, files.Plan{}, cmd.FailErrCode(err, p.CodeFor(platform.DetectError), "detect")
 		}
 	}
-	return group, plan, nil
-}
-
-func (d *detectCmd) writeDetectData(group buildpack.Group, plan files.Plan) error {
-	if err := encoding.WriteTOML(d.GroupPath, group); err != nil {
-		return cmd.FailErr(err, "write buildpack group")
-	}
-	if err := encoding.WriteTOML(d.PlanPath, plan); err != nil {
-		return cmd.FailErr(err, "write detect plan")
+	if err := phase.Config.WriteGroup(p.GroupPath, &group); err != nil {
+		return buildpack.Group{}, files.Plan{}, err
 	}
-	return nil
-}
-
-// writeGenerateData re-outputs the analyzedMD that we read previously, but now we've added the RunImage, if a custom runImage was configured
-func (d *detectCmd) writeGenerateData(analyzedMD files.Analyzed) error {
-	cmd.DefaultLogger.Debugf("Run image info in analyzed metadata is: ")
-	cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.RunImage))
-	if err := encoding.WriteTOML(d.AnalyzedPath, analyzedMD); err != nil {
-		return cmd.FailErr(err, "write analyzed metadata")
+	if err := phase.Config.WritePlan(p.PlanPath, &plan); err != nil {
+		return buildpack.Group{}, files.Plan{}, err
 	}
-	return nil
+	return group, plan, nil
 }
diff --git a/cmd/lifecycle/exporter.go b/cmd/lifecycle/exporter.go
index a95a9005..dd1b73aa 100644
--- a/cmd/lifecycle/exporter.go
+++ b/cmd/lifecycle/exporter.go
@@ -205,7 +205,7 @@ func (e *exportCmd) export(group buildpack.Group, cacheStore phase.Cache, analyz
 		return err
 	}
 
-	runImageForExport, err := platform.GetRunImageForExport(*e.LifecycleInputs)
+	runImageForExport, err := platform.GetRunImageForExport(e.Inputs())
 	if err != nil {
 		return err
 	}
diff --git a/cmd/lifecycle/extender.go b/cmd/lifecycle/extender.go
index 4991c95d..595ae1f8 100644
--- a/cmd/lifecycle/extender.go
+++ b/cmd/lifecycle/extender.go
@@ -52,22 +52,14 @@ func (e *extendCmd) Privileges() error {
 }
 
 func (e *extendCmd) Exec() error {
-	extenderFactory := phase.NewExtenderFactory(&cmd.BuildpackAPIVerifier{}, phase.NewConfigHandler())
+	extenderFactory := phase.NewHermeticFactory(e.PlatformAPI, &cmd.BuildpackAPIVerifier{}, phase.NewConfigHandler(), platform.NewDirStore("", ""))
 	applier, err := kaniko.NewDockerfileApplier()
 	if err != nil {
 		return err
 	}
 	extender, err := extenderFactory.NewExtender(
-		e.AnalyzedPath,
-		e.AppDir,
-		e.ExtendedDir,
-		e.GeneratedDir,
-		e.GroupPath,
-		e.LayersDir,
-		e.PlatformDir,
-		e.KanikoCacheTTL,
+		e.Inputs(),
 		applier,
-		e.ExtendKind,
 		cmd.DefaultLogger,
 	)
 	if err != nil {
diff --git a/phase/analyzer.go b/phase/analyzer.go
index e3ec7a77..0217411b 100644
--- a/phase/analyzer.go
+++ b/phase/analyzer.go
@@ -5,7 +5,6 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/buildpacks/lifecycle/api"
-	"github.com/buildpacks/lifecycle/cache"
 	"github.com/buildpacks/lifecycle/image"
 	"github.com/buildpacks/lifecycle/internal/fsutil"
 	"github.com/buildpacks/lifecycle/internal/layer"
@@ -14,33 +13,8 @@ import (
 	"github.com/buildpacks/lifecycle/platform/files"
 )
 
-type AnalyzerFactory struct {
-	platformAPI     *api.Version
-	apiVerifier     BuildpackAPIVerifier
-	cacheHandler    CacheHandler
-	configHandler   ConfigHandler
-	imageHandler    image.Handler
-	registryHandler image.RegistryHandler
-}
-
-func NewAnalyzerFactory(
-	platformAPI *api.Version,
-	apiVerifier BuildpackAPIVerifier,
-	cacheHandler CacheHandler,
-	configHandler ConfigHandler,
-	imageHandler image.Handler,
-	registryHandler image.RegistryHandler,
-) *AnalyzerFactory {
-	return &AnalyzerFactory{
-		platformAPI:     platformAPI,
-		apiVerifier:     apiVerifier,
-		cacheHandler:    cacheHandler,
-		configHandler:   configHandler,
-		imageHandler:    imageHandler,
-		registryHandler: registryHandler,
-	}
-}
-
+// Analyzer reads metadata from the previous image (if it exists) and the run image,
+// and additionally restores the SBOM layer from the previous image for use later in the build.
 type Analyzer struct {
 	PreviousImage imgutil.Image
 	RunImage      imgutil.Image
@@ -50,90 +24,34 @@ type Analyzer struct {
 }
 
 // NewAnalyzer configures a new Analyzer according to the provided Platform API version.
-func (f *AnalyzerFactory) NewAnalyzer(additionalTags []string, cacheImageRef string, launchCacheDir string, layersDir string, outputImageRef string, previousImageRef string, runImageRef string, skipLayers bool, logger log.Logger) (*Analyzer, error) {
+func (f *ConnectedFactory) NewAnalyzer(inputs platform.LifecycleInputs, logger log.Logger) (*Analyzer, error) {
 	analyzer := &Analyzer{
 		Logger:       logger,
 		SBOMRestorer: &layer.NopSBOMRestorer{},
 		PlatformAPI:  f.platformAPI,
 	}
 
-	if err := f.ensureRegistryAccess(additionalTags, cacheImageRef, outputImageRef, runImageRef, previousImageRef); err != nil {
+	if err := f.ensureRegistryAccess(inputs); err != nil {
 		return nil, err
 	}
 
-	if f.platformAPI.AtLeast("0.8") && !skipLayers {
-		analyzer.SBOMRestorer = &layer.DefaultSBOMRestorer{ // FIXME: eventually layer.NewSBOMRestorer should always return the default one, and then we can use the constructor
-			LayersDir: layersDir,
+	if f.platformAPI.AtLeast("0.8") && !inputs.SkipLayers {
+		analyzer.SBOMRestorer = &layer.DefaultSBOMRestorer{
+			LayersDir: inputs.LayersDir,
 			Logger:    logger,
 		}
 	}
 
-	if err := f.setPrevious(analyzer, previousImageRef, launchCacheDir); err != nil {
+	var err error
+	if analyzer.PreviousImage, err = f.getPreviousImage(inputs.PreviousImageRef, inputs.LaunchCacheDir); err != nil {
 		return nil, err
 	}
-	if err := f.setRun(analyzer, runImageRef); err != nil {
+	if analyzer.RunImage, err = f.getRunImage(inputs.RunImageRef); err != nil {
 		return nil, err
 	}
 	return analyzer, nil
 }
 
-func (f *AnalyzerFactory) ensureRegistryAccess(
-	additionalTags []string,
-	cacheImageRef string,
-	outputImageRef string,
-	runImageRef string,
-	previousImageRef string,
-) error {
-	var readImages, writeImages []string
-	writeImages = append(writeImages, cacheImageRef)
-	if f.imageHandler.Kind() == image.RemoteKind {
-		readImages = append(readImages, previousImageRef, runImageRef)
-		writeImages = append(writeImages, outputImageRef)
-		writeImages = append(writeImages, additionalTags...)
-	}
-
-	if err := f.registryHandler.EnsureReadAccess(readImages...); err != nil {
-		return errors.Wrap(err, "validating registry read access")
-	}
-	if err := f.registryHandler.EnsureWriteAccess(writeImages...); err != nil {
-		return errors.Wrap(err, "validating registry write access")
-	}
-	return nil
-}
-
-func (f *AnalyzerFactory) setPrevious(analyzer *Analyzer, imageRef string, launchCacheDir string) error {
-	if imageRef == "" {
-		return nil
-	}
-	var err error
-	analyzer.PreviousImage, err = f.imageHandler.InitImage(imageRef)
-	if err != nil {
-		return errors.Wrap(err, "getting previous image")
-	}
-	if launchCacheDir == "" || f.imageHandler.Kind() != image.LocalKind {
-		return nil
-	}
-
-	volumeCache, err := cache.NewVolumeCache(launchCacheDir)
-	if err != nil {
-		return errors.Wrap(err, "creating launch cache")
-	}
-	analyzer.PreviousImage = cache.NewCachingImage(analyzer.PreviousImage, volumeCache)
-	return nil
-}
-
-func (f *AnalyzerFactory) setRun(analyzer *Analyzer, imageRef string) error {
-	if imageRef == "" {
-		return nil
-	}
-	var err error
-	analyzer.RunImage, err = f.imageHandler.InitImage(imageRef)
-	if err != nil {
-		return errors.Wrap(err, "getting run image")
-	}
-	return nil
-}
-
 // Analyze fetches the layers metadata from the previous image and writes analyzed.toml.
 func (a *Analyzer) Analyze() (files.Analyzed, error) {
 	defer log.NewMeasurement("Analyzer", a.Logger)()
diff --git a/phase/connected_factory.go b/phase/connected_factory.go
new file mode 100644
index 00000000..3514938a
--- /dev/null
+++ b/phase/connected_factory.go
@@ -0,0 +1,88 @@
+package phase
+
+import (
+	"fmt"
+
+	"github.com/buildpacks/imgutil"
+
+	"github.com/buildpacks/lifecycle/api"
+	"github.com/buildpacks/lifecycle/cache"
+	"github.com/buildpacks/lifecycle/image"
+	"github.com/buildpacks/lifecycle/platform"
+)
+
+// ConnectedFactory is used to construct lifecycle phases that require access to an image repository
+// (registry, layout directory, or docker daemon) and/or a cache.
+type ConnectedFactory struct {
+	platformAPI     *api.Version
+	apiVerifier     BuildpackAPIVerifier
+	cacheHandler    CacheHandler
+	configHandler   ConfigHandler
+	imageHandler    image.Handler
+	registryHandler image.RegistryHandler
+}
+
+// NewConnectedFactory constructs a new ConnectedFactory.
+func NewConnectedFactory(
+	platformAPI *api.Version,
+	apiVerifier BuildpackAPIVerifier,
+	cacheHandler CacheHandler,
+	configHandler ConfigHandler,
+	imageHandler image.Handler,
+	registryHandler image.RegistryHandler,
+) *ConnectedFactory {
+	return &ConnectedFactory{
+		platformAPI:     platformAPI,
+		apiVerifier:     apiVerifier,
+		cacheHandler:    cacheHandler,
+		configHandler:   configHandler,
+		imageHandler:    imageHandler,
+		registryHandler: registryHandler,
+	}
+}
+
+func (f *ConnectedFactory) ensureRegistryAccess(inputs platform.LifecycleInputs) error {
+	var readImages, writeImages []string
+	writeImages = append(writeImages, inputs.CacheImageRef)
+	if f.imageHandler.Kind() == image.RemoteKind {
+		readImages = append(readImages, inputs.PreviousImageRef, inputs.RunImageRef)
+		writeImages = append(writeImages, inputs.OutputImageRef)
+		writeImages = append(writeImages, inputs.AdditionalTags...)
+	}
+	if err := f.registryHandler.EnsureReadAccess(readImages...); err != nil {
+		return fmt.Errorf("validating registry read access: %w", err)
+	}
+	if err := f.registryHandler.EnsureWriteAccess(writeImages...); err != nil {
+		return fmt.Errorf("validating registry write access: %w", err)
+	}
+	return nil
+}
+
+func (f *ConnectedFactory) getPreviousImage(imageRef string, launchCacheDir string) (imgutil.Image, error) {
+	if imageRef == "" {
+		return nil, nil
+	}
+	previousImage, err := f.imageHandler.InitImage(imageRef)
+	if err != nil {
+		return nil, fmt.Errorf("getting previous image: %w", err)
+	}
+	if launchCacheDir == "" || f.imageHandler.Kind() != image.LocalKind {
+		return previousImage, nil
+	}
+	volumeCache, err := cache.NewVolumeCache(launchCacheDir)
+	if err != nil {
+		return nil, fmt.Errorf("creating launch cache: %w", err)
+	}
+	return cache.NewCachingImage(previousImage, volumeCache), nil
+}
+
+func (f *ConnectedFactory) getRunImage(imageRef string) (imgutil.Image, error) {
+	if imageRef == "" {
+		return nil, nil
+	}
+	runImage, err := f.imageHandler.InitImage(imageRef)
+	if err != nil {
+		return nil, fmt.Errorf("getting run image: %w", err)
+	}
+	return runImage, nil
+}
diff --git a/phase/detector.go b/phase/detector.go
index 6c4f8faa..f230a878 100644
--- a/phase/detector.go
+++ b/phase/detector.go
@@ -38,27 +38,6 @@ type DetectResolver interface {
 	Resolve(done []buildpack.GroupElement, detectRuns *sync.Map) ([]buildpack.GroupElement, []files.BuildPlanEntry, error)
 }
 
-type DetectorFactory struct {
-	platformAPI   *api.Version
-	apiVerifier   BuildpackAPIVerifier
-	configHandler ConfigHandler
-	dirStore      DirStore
-}
-
-func NewDetectorFactory(
-	platformAPI *api.Version,
-	apiVerifier BuildpackAPIVerifier,
-	configHandler ConfigHandler,
-	dirStore DirStore,
-) *DetectorFactory {
-	return &DetectorFactory{
-		platformAPI:   platformAPI,
-		apiVerifier:   apiVerifier,
-		configHandler: configHandler,
-		dirStore:      dirStore,
-	}
-}
-
 type Detector struct {
 	AppDir         string
 	BuildConfigDir string
@@ -79,55 +58,29 @@ type Detector struct {
 	memHandler *memory.Handler
 }
 
-func (f *DetectorFactory) NewDetector(analyzedMD files.Analyzed, appDir, buildConfigDir, orderPath, platformDir string, logger log.LoggerHandlerWithLevel) (*Detector, error) {
+// NewDetector constructs a new Detector by initializing services and reading the provided analyzed and order files.
+func (f *HermeticFactory) NewDetector(inputs platform.LifecycleInputs, logger log.LoggerHandlerWithLevel) (*Detector, error) {
 	memHandler := memory.New()
 	detector := &Detector{
-		AnalyzeMD:      analyzedMD,
-		AppDir:         appDir,
-		BuildConfigDir: buildConfigDir,
+		AppDir:         inputs.AppDir,
+		BuildConfigDir: inputs.BuildConfigDir,
 		DirStore:       f.dirStore,
 		Executor:       &buildpack.DefaultDetectExecutor{},
 		Logger:         logger,
-		PlatformDir:    platformDir,
+		PlatformDir:    inputs.PlatformDir,
 		Resolver:       NewDefaultDetectResolver(&apexlog.Logger{Handler: memHandler}),
 		Runs:           &sync.Map{},
 		memHandler:     memHandler,
 		PlatformAPI:    f.platformAPI,
 	}
-	if err := f.setOrder(detector, orderPath, logger); err != nil {
+	var err error
+	if detector.AnalyzeMD, err = f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger); err != nil {
 		return nil, err
 	}
-	return detector, nil
-}
-
-func (f *DetectorFactory) setOrder(detector *Detector, path string, logger log.Logger) error {
-	orderBp, orderExt, err := f.configHandler.ReadOrder(path)
-	if err != nil {
-		return errors.Wrap(err, "reading order")
-	}
-	if len(orderExt) > 0 {
-		detector.HasExtensions = true
-	}
-	if err = f.verifyAPIs(orderBp, orderExt, logger); err != nil {
-		return err
-	}
-	detector.Order = PrependExtensions(orderBp, orderExt)
-	return nil
-}
-
-func (f *DetectorFactory) verifyAPIs(orderBp buildpack.Order, orderExt buildpack.Order, logger log.Logger) error {
-	for _, group := range append(orderBp, orderExt...) {
-		for _, groupEl := range group.Group {
-			module, err := f.dirStore.Lookup(groupEl.Kind(), groupEl.ID, groupEl.Version)
-			if err != nil {
-				return err
-			}
-			if err = f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), module.API(), logger); err != nil {
-				return err
-			}
-		}
+	if detector.Order, detector.HasExtensions, err = f.getOrder(inputs.OrderPath, logger); err != nil {
+		return nil, err
 	}
-	return nil
+	return detector, nil
 }
 
 func (d *Detector) Detect() (buildpack.Group, files.Plan, error) {
diff --git a/phase/extender.go b/phase/extender.go
index 7efafa11..dab1dd56 100644
--- a/phase/extender.go
+++ b/phase/extender.go
@@ -24,6 +24,7 @@ import (
 	"github.com/buildpacks/lifecycle/launch"
 	"github.com/buildpacks/lifecycle/layers"
 	"github.com/buildpacks/lifecycle/log"
+	"github.com/buildpacks/lifecycle/platform"
 )
 
 type Extender struct {
@@ -49,89 +50,40 @@ type DockerfileApplier interface {
 	Cleanup() error
 }
 
-type ExtenderFactory struct {
-	apiVerifier   BuildpackAPIVerifier
-	configHandler ConfigHandler
-}
-
-func NewExtenderFactory(apiVerifier BuildpackAPIVerifier, configHandler ConfigHandler) *ExtenderFactory {
-	return &ExtenderFactory{
-		apiVerifier:   apiVerifier,
-		configHandler: configHandler,
-	}
-}
-
-func (f *ExtenderFactory) NewExtender(
-	analyzedPath string,
-	appDir string,
-	extendedDir string,
-	generatedDir string,
-	groupPath string,
-	layersDir string,
-	platformDir string,
-	cacheTTL time.Duration,
-	dockerfileApplier DockerfileApplier,
-	kind string,
-	logger log.Logger,
-) (*Extender, error) {
+// NewExtender constructs a new Extender by initializing services and reading the provided analyzed and group files
+// to determine the image to extend and the extensions to use.
+func (f *HermeticFactory) NewExtender(inputs platform.LifecycleInputs, dockerfileApplier DockerfileApplier, logger log.Logger) (*Extender, error) {
 	extender := &Extender{
-		AppDir:            appDir,
-		ExtendedDir:       extendedDir,
-		GeneratedDir:      generatedDir,
-		LayersDir:         layersDir,
-		PlatformDir:       platformDir,
-		CacheTTL:          cacheTTL,
+		AppDir:            inputs.AppDir,
+		ExtendedDir:       inputs.ExtendedDir,
+		GeneratedDir:      inputs.GeneratedDir,
+		LayersDir:         inputs.LayersDir,
+		PlatformDir:       inputs.PlatformDir,
+		CacheTTL:          inputs.KanikoCacheTTL,
 		DockerfileApplier: dockerfileApplier,
 	}
-	if err := f.setImageRef(extender, kind, analyzedPath, logger); err != nil {
+	var err error
+	if extender.ImageRef, err = f.getExtendImageRef(inputs, logger); err != nil {
 		return nil, err
 	}
-	if err := f.setExtensions(extender, groupPath, logger); err != nil {
+	if extender.Extensions, err = f.getExtensions(inputs.GroupPath, logger); err != nil {
 		return nil, err
 	}
 	return extender, nil
 }
 
-func (f *ExtenderFactory) setImageRef(extender *Extender, kind, path string, logr log.Logger) error {
-	analyzedMD, err := f.configHandler.ReadAnalyzed(path, logr)
-	if err != nil {
-		return err
-	}
-	if kind == "build" {
-		if analyzedMD.BuildImage != nil {
-			extender.ImageRef = analyzedMD.BuildImage.Reference
-		}
-	} else if kind == "run" {
-		if analyzedMD.RunImage != nil {
-			extender.ImageRef = analyzedMD.RunImage.Reference
-		}
-	}
-
-	return nil
-}
-
-func (f *ExtenderFactory) setExtensions(extender *Extender, path string, logger log.Logger) error {
-	_, groupExt, err := f.configHandler.ReadGroup(path)
+func (f *HermeticFactory) getExtendImageRef(inputs platform.LifecycleInputs, logger log.Logger) (string, error) {
+	analyzedMD, err := f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger)
 	if err != nil {
-		return fmt.Errorf("reading group: %w", err)
+		return "", err
 	}
-	for i := range groupExt {
-		groupExt[i].Extension = true
+	if inputs.ExtendKind == "build" && analyzedMD.BuildImage != nil {
+		return analyzedMD.BuildImage.Reference, nil
 	}
-	if err = f.verifyAPIs(groupExt, logger); err != nil {
-		return err
+	if inputs.ExtendKind == "run" && analyzedMD.RunImage != nil {
+		return analyzedMD.RunImage.Reference, nil
 	}
-	extender.Extensions = groupExt
-	return nil
-}
-
-func (f *ExtenderFactory) verifyAPIs(groupExt []buildpack.GroupElement, logger log.Logger) error {
-	for _, groupEl := range groupExt {
-		if err := f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), groupEl.API, logger); err != nil {
-			return err
-		}
-	}
-	return nil
+	return "", nil
 }
 
 func (e *Extender) Extend(kind string, logger log.Logger) error {
diff --git a/phase/generator.go b/phase/generator.go
index 2a92af99..a5f35d53 100644
--- a/phase/generator.go
+++ b/phase/generator.go
@@ -33,88 +33,39 @@ type Generator struct {
 	RunMetadata    files.Run
 }
 
-type GeneratorFactory struct {
-	apiVerifier   BuildpackAPIVerifier
-	configHandler ConfigHandler
-	dirStore      DirStore
-}
-
-func NewGeneratorFactory(
-	apiVerifier BuildpackAPIVerifier,
-	configHandler ConfigHandler,
-	dirStore DirStore,
-) *GeneratorFactory {
-	return &GeneratorFactory{
-		apiVerifier:   apiVerifier,
-		configHandler: configHandler,
-		dirStore:      dirStore,
-	}
-}
-
-func (f *GeneratorFactory) NewGenerator(
-	analyzedPath string,
-	appDir string,
-	buildConfigDir string,
-	extensions []buildpack.GroupElement,
-	generatedDir string,
-	plan files.Plan,
-	platformAPI *api.Version,
-	platformDir string,
-	runPath string,
-	stdout, stderr io.Writer,
-	logger log.Logger,
-) (*Generator, error) {
+// NewGenerator constructs a new Generator by initializing services and reading the provided analyzed, group, plan, and run files.
+func (f *HermeticFactory) NewGenerator(inputs platform.LifecycleInputs, stdout, stderr io.Writer, logger log.Logger) (*Generator, error) {
 	generator := &Generator{
-		AppDir:         appDir,
-		BuildConfigDir: buildConfigDir,
-		GeneratedDir:   generatedDir,
-		PlatformAPI:    platformAPI,
-		PlatformDir:    platformDir,
+		AppDir:         inputs.AppDir,
+		BuildConfigDir: inputs.BuildConfigDir,
+		GeneratedDir:   inputs.GeneratedDir,
+		PlatformAPI:    inputs.PlatformAPI,
+		PlatformDir:    inputs.PlatformDir,
 		DirStore:       f.dirStore,
 		Executor:       &buildpack.DefaultGenerateExecutor{},
 		Logger:         logger,
-		Plan:           plan,
 		Out:            stdout,
 		Err:            stderr,
 	}
-	if err := f.setExtensions(generator, extensions, logger); err != nil {
+	var err error
+	if generator.Extensions, err = f.getExtensions(inputs.GroupPath, logger); err != nil {
 		return nil, err
 	}
-	if err := f.setAnalyzedMD(generator, analyzedPath, logger); err != nil {
+	if generator.Plan, err = f.configHandler.ReadPlan(inputs.PlanPath); err != nil {
 		return nil, err
 	}
-	if err := f.setRunMD(generator, runPath, logger); err != nil {
+	if generator.RunMetadata, err = f.configHandler.ReadRun(inputs.RunPath, logger); err != nil {
 		return nil, err
 	}
-	return generator, nil
-}
-
-func (f *GeneratorFactory) setExtensions(generator *Generator, extensions []buildpack.GroupElement, logger log.Logger) error {
-	generator.Extensions = extensions
-	for _, el := range generator.Extensions {
-		if err := f.apiVerifier.VerifyBuildpackAPI(buildpack.KindExtension, el.String(), el.API, logger); err != nil {
-			return err
-		}
+	if generator.AnalyzedMD, err = f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger); err != nil {
+		return nil, err
 	}
-	return nil
-}
-
-func (f *GeneratorFactory) setAnalyzedMD(generator *Generator, analyzedPath string, logger log.Logger) error {
-	var err error
-	generator.AnalyzedMD, err = f.configHandler.ReadAnalyzed(analyzedPath, logger)
-	return err
-}
-
-func (f *GeneratorFactory) setRunMD(generator *Generator, runPath string, logger log.Logger) error {
-	var err error
-	generator.RunMetadata, err = f.configHandler.ReadRun(runPath, logger)
-	return err
+	return generator, nil
 }
 
 type GenerateResult struct {
 	AnalyzedMD files.Analyzed
 	Plan       files.Plan
-	UsePlan    bool
 }
 
 func (g *Generator) Generate() (GenerateResult, error) {
@@ -185,7 +136,6 @@ func (g *Generator) Generate() (GenerateResult, error) {
 	return GenerateResult{
 		AnalyzedMD: finalAnalyzedMD,
 		Plan:       filteredPlan,
-		UsePlan:    true,
 	}, nil
 }
 
diff --git a/phase/handlers.go b/phase/handlers.go
index d5dbd398..033edf50 100644
--- a/phase/handlers.go
+++ b/phase/handlers.go
@@ -6,6 +6,7 @@ import (
 	"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"
 )
@@ -45,6 +46,7 @@ type ConfigHandler interface {
 	ReadGroup(path string) (buildpackGroup []buildpack.GroupElement, extensionsGroup []buildpack.GroupElement, err error)
 	ReadOrder(path string) (buildpack.Order, buildpack.Order, error)
 	ReadRun(runPath string, logger log.Logger) (files.Run, error)
+	ReadPlan(path string) (files.Plan, error)
 }
 
 type DefaultConfigHandler struct{}
@@ -53,8 +55,19 @@ func NewConfigHandler() *DefaultConfigHandler {
 	return &DefaultConfigHandler{}
 }
 
-func (h *DefaultConfigHandler) ReadAnalyzed(path string, logr log.Logger) (files.Analyzed, error) {
-	return files.ReadAnalyzed(path, logr)
+// 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) {
@@ -76,6 +89,31 @@ func ReadGroup(path string) (buildpack.Group, error) {
 	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 {
diff --git a/phase/hermetic_factory.go b/phase/hermetic_factory.go
new file mode 100644
index 00000000..f3b1516b
--- /dev/null
+++ b/phase/hermetic_factory.go
@@ -0,0 +1,88 @@
+package phase
+
+import (
+	"fmt"
+
+	"github.com/pkg/errors"
+
+	"github.com/buildpacks/lifecycle/api"
+	"github.com/buildpacks/lifecycle/buildpack"
+	"github.com/buildpacks/lifecycle/log"
+)
+
+// HermeticFactory is used to construct lifecycle phases that do NOT require access to an image repository.
+type HermeticFactory struct {
+	platformAPI   *api.Version
+	apiVerifier   BuildpackAPIVerifier
+	configHandler ConfigHandler
+	dirStore      DirStore
+}
+
+// NewHermeticFactory is used to construct a new HermeticFactory.
+func NewHermeticFactory(
+	platformAPI *api.Version,
+	apiVerifier BuildpackAPIVerifier,
+	configHandler ConfigHandler,
+	dirStore DirStore,
+) *HermeticFactory {
+	return &HermeticFactory{
+		platformAPI:   platformAPI,
+		apiVerifier:   apiVerifier,
+		configHandler: configHandler,
+		dirStore:      dirStore,
+	}
+}
+
+func (f *HermeticFactory) getExtensions(groupPath string, logger log.Logger) ([]buildpack.GroupElement, error) {
+	_, groupExt, err := f.configHandler.ReadGroup(groupPath)
+	if err != nil {
+		return nil, fmt.Errorf("reading group: %w", err)
+	}
+	for i := range groupExt {
+		groupExt[i].Extension = true
+	}
+	if err = f.verifyGroup(groupExt, logger); err != nil {
+		return nil, err
+	}
+	return groupExt, nil
+}
+
+func (f *HermeticFactory) getOrder(path string, logger log.Logger) (order buildpack.Order, hasExtensions bool, err error) {
+	orderBp, orderExt, orderErr := f.configHandler.ReadOrder(path)
+	if orderErr != nil {
+		err = errors.Wrap(orderErr, "reading order")
+		return
+	}
+	if len(orderExt) > 0 {
+		hasExtensions = true
+	}
+	if err = f.verifyOrder(orderBp, orderExt, logger); err != nil {
+		return
+	}
+	order = PrependExtensions(orderBp, orderExt)
+	return
+}
+
+func (f *HermeticFactory) verifyGroup(group []buildpack.GroupElement, logger log.Logger) error {
+	for _, groupEl := range group {
+		if err := f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), groupEl.API, logger); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (f *HermeticFactory) verifyOrder(orderBp buildpack.Order, orderExt buildpack.Order, logger log.Logger) error {
+	for _, group := range append(orderBp, orderExt...) {
+		for _, groupEl := range group.Group {
+			module, err := f.dirStore.Lookup(groupEl.Kind(), groupEl.ID, groupEl.Version)
+			if err != nil {
+				return err
+			}
+			if err = f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), module.API(), logger); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
diff --git a/platform/platform.go b/platform/platform.go
index 8820ab18..908ea7c5 100644
--- a/platform/platform.go
+++ b/platform/platform.go
@@ -34,3 +34,8 @@ func NewPlatformFor(platformAPI string) *Platform {
 func (p *Platform) API() *api.Version {
 	return p.PlatformAPI
 }
+
+// Inputs exposes the platform's inputs to the lifecycle.
+func (p *Platform) Inputs() LifecycleInputs {
+	return *p.LifecycleInputs
+}


Test patch:
diff --git a/phase/analyzer_test.go b/phase/analyzer_test.go
index 73d0c8be..cfa165f3 100644
--- a/phase/analyzer_test.go
+++ b/phase/analyzer_test.go
@@ -29,15 +29,15 @@ import (
 
 func TestAnalyzer(t *testing.T) {
 	spec.Run(t, "unit-new-analyzer/", testAnalyzerFactory, spec.Parallel(), spec.Report(report.Terminal{}))
-	for _, api := range api.Platform.Supported {
-		spec.Run(t, "unit-analyzer/"+api.String(), testAnalyzer(api.String()), spec.Parallel(), spec.Report(report.Terminal{}))
+	for _, platformAPI := range api.Platform.Supported {
+		spec.Run(t, "unit-analyzer/"+platformAPI.String(), testAnalyzer(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{}))
 	}
 }
 
 func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 	when("#NewAnalyzer", func() {
 		var (
-			analyzerFactory     *phase.AnalyzerFactory
+			analyzerFactory     *phase.ConnectedFactory
 			fakeAPIVerifier     *testmock.MockBuildpackAPIVerifier
 			fakeCacheHandler    *testmock.MockCacheHandler
 			fakeConfigHandler   *testmock.MockConfigHandler
@@ -63,12 +63,12 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 
 		it.After(func() {
 			mockController.Finish()
-			os.RemoveAll(tempDir)
+			_ = os.RemoveAll(tempDir)
 		})
 
 		when("platform api >= 0.8", func() {
 			it.Before(func() {
-				analyzerFactory = phase.NewAnalyzerFactory(
+				analyzerFactory = phase.NewConnectedFactory(
 					api.Platform.Latest(),
 					fakeAPIVerifier,
 					fakeCacheHandler,
@@ -98,7 +98,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 					t.Log("processes run image")
 					fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil)
 
-					analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+					analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+						AdditionalTags:   []string{"some-additional-tag"},
+						CacheImageRef:    "some-cache-image-ref",
+						LaunchCacheDir:   "some-launch-cache-dir",
+						LayersDir:        "some-layers-dir",
+						OutputImageRef:   "some-output-image-ref",
+						PreviousImageRef: "some-previous-image-ref",
+						RunImageRef:      "some-run-image-ref",
+						SkipLayers:       false,
+					}, logger)
 					h.AssertNil(t, err)
 					h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 					h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -131,7 +140,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 				t.Log("processes run image")
 				fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil)
 
-				analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+				analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+					AdditionalTags:   []string{"some-additional-tag"},
+					CacheImageRef:    "some-cache-image-ref",
+					LaunchCacheDir:   "some-launch-cache-dir",
+					LayersDir:        "some-layers-dir",
+					OutputImageRef:   "some-output-image-ref",
+					PreviousImageRef: "some-previous-image-ref",
+					RunImageRef:      "some-run-image-ref",
+					SkipLayers:       false,
+				}, logger)
 				h.AssertNil(t, err)
 				h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 				h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -165,7 +183,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 
 					launchCacheDir := filepath.Join(tempDir, "some-launch-cache-dir")
 					h.AssertNil(t, os.MkdirAll(launchCacheDir, 0777))
-					analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", launchCacheDir, "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+					analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+						AdditionalTags:   []string{"some-additional-tag"},
+						CacheImageRef:    "some-cache-image-ref",
+						LaunchCacheDir:   launchCacheDir,
+						LayersDir:        "some-layers-dir",
+						OutputImageRef:   "some-output-image-ref",
+						PreviousImageRef: "some-previous-image-ref",
+						RunImageRef:      "some-run-image-ref",
+						SkipLayers:       false,
+					}, logger)
 					h.AssertNil(t, err)
 					h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 					h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -186,7 +213,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 					fakeImageHandler.EXPECT().InitImage(gomock.Any())
 					fakeImageHandler.EXPECT().InitImage(gomock.Any())
 
-					analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", true, logger)
+					analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+						AdditionalTags:   []string{"some-additional-tag"},
+						CacheImageRef:    "some-cache-image-ref",
+						LaunchCacheDir:   "some-launch-cache-dir",
+						LayersDir:        "some-layers-dir",
+						OutputImageRef:   "some-output-image-ref",
+						PreviousImageRef: "some-previous-image-ref",
+						RunImageRef:      "some-run-image-ref",
+						SkipLayers:       true,
+					}, logger)
 					h.AssertNil(t, err)
 
 					_, ok := analyzer.SBOMRestorer.(*layer.NopSBOMRestorer)
@@ -197,7 +233,7 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 
 		when("platform api = 0.7", func() {
 			it.Before(func() {
-				analyzerFactory = phase.NewAnalyzerFactory(
+				analyzerFactory = phase.NewConnectedFactory(
 					api.MustParse("0.7"),
 					fakeAPIVerifier,
 					fakeCacheHandler,
@@ -222,7 +258,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 				t.Log("processes run image")
 				fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil)
 
-				analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+				analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+					AdditionalTags:   []string{"some-additional-tag"},
+					CacheImageRef:    "some-cache-image-ref",
+					LaunchCacheDir:   "some-launch-cache-dir",
+					LayersDir:        "some-layers-dir",
+					OutputImageRef:   "some-output-image-ref",
+					PreviousImageRef: "some-previous-image-ref",
+					RunImageRef:      "some-run-image-ref",
+					SkipLayers:       true,
+				}, logger)
 				h.AssertNil(t, err)
 				h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 				h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -253,7 +298,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 
 					launchCacheDir := filepath.Join(tempDir, "some-launch-cache-dir")
 					h.AssertNil(t, os.MkdirAll(launchCacheDir, 0777))
-					analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", launchCacheDir, "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger)
+					analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{
+						AdditionalTags:   []string{"some-additional-tag"},
+						CacheImageRef:    "some-cache-image-ref",
+						LaunchCacheDir:   launchCacheDir,
+						LayersDir:        "some-layers-dir",
+						OutputImageRef:   "some-output-image-ref",
+						PreviousImageRef: "some-previous-image-ref",
+						RunImageRef:      "some-run-image-ref",
+						SkipLayers:       true,
+					}, logger)
 					h.AssertNil(t, err)
 					h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name())
 					h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name())
@@ -266,14 +320,14 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) {
 func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) {
 	return func(t *testing.T, when spec.G, it spec.S) {
 		var (
-			cacheDir     string
-			layersDir    string
-			tmpDir       string
-			analyzer     *phase.Analyzer
-			image        *fakes.Image
-			mockCtrl     *gomock.Controller
-			sbomRestorer *testmock.MockSBOMRestorer
-			testCache    phase.Cache
+			cacheDir      string
+			layersDir     string
+			tmpDir        string
+			analyzer      *phase.Analyzer
+			previousImage *fakes.Image
+			mockCtrl      *gomock.Controller
+			sbomRestorer  *testmock.MockSBOMRestorer
+			testCache     phase.Cache
 		)
 
 		it.Before(func() {
@@ -291,7 +345,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			testCache, err = cache.NewVolumeCache(cacheDir)
 			h.AssertNil(t, err)
 
-			image = fakes.NewImage("image-repo-name", "", local.IDIdentifier{
+			previousImage = fakes.NewImage("image-repo-name", "", local.IDIdentifier{
 				ImageID: "s0m3D1g3sT",
 			})
 
@@ -303,7 +357,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			h.AssertNil(t, err)
 			analyzer = &phase.Analyzer{
-				PreviousImage: image,
+				PreviousImage: previousImage,
 				Logger:        &discardLogger,
 				SBOMRestorer:  sbomRestorer,
 				PlatformAPI:   api.MustParse(platformAPI),
@@ -319,7 +373,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			h.AssertNil(t, os.RemoveAll(tmpDir))
 			h.AssertNil(t, os.RemoveAll(layersDir))
 			h.AssertNil(t, os.RemoveAll(cacheDir))
-			h.AssertNil(t, image.Cleanup())
+			h.AssertNil(t, previousImage.Cleanup())
 			mockCtrl.Finish()
 		})
 
@@ -338,7 +392,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			when("previous image exists", func() {
 				it.Before(func() {
 					metadata := h.MustReadFile(t, filepath.Join("testdata", "analyzer", "app_metadata.json"))
-					h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", string(metadata)))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", string(metadata)))
 					h.AssertNil(t, json.Unmarshal(metadata, &expectedAppMetadata))
 				})
 
@@ -369,7 +423,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			when("previous image not found", func() {
 				it.Before(func() {
-					h.AssertNil(t, image.Delete())
+					h.AssertNil(t, previousImage.Delete())
 				})
 
 				it("returns a nil image in the analyzed metadata", func() {
@@ -383,7 +437,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			when("previous image does not have metadata label", func() {
 				it.Before(func() {
-					h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", ""))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", ""))
 				})
 
 				it("returns empty analyzed metadata", func() {
@@ -395,7 +449,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			when("previous image has incompatible metadata", func() {
 				it.Before(func() {
-					h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", `{["bad", "metadata"]}`))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", `{["bad", "metadata"]}`))
 				})
 
 				it("returns empty analyzed metadata", func() {
@@ -408,12 +462,12 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 			when("previous image has an SBOM layer digest in the analyzed metadata", func() {
 				it.Before(func() {
 					metadata := fmt.Sprintf(`{"sbom": {"sha":"%s"}}`, "some-digest")
-					h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", metadata))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", metadata))
 					h.AssertNil(t, json.Unmarshal([]byte(metadata), &expectedAppMetadata))
 				})
 
 				it("calls the SBOM restorer with the SBOM layer digest", func() {
-					sbomRestorer.EXPECT().RestoreFromPrevious(image, "some-digest")
+					sbomRestorer.EXPECT().RestoreFromPrevious(previousImage, "some-digest")
 					_, err := analyzer.Analyze()
 					h.AssertNil(t, err)
 				})
@@ -421,7 +475,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 
 			when("run image is provided", func() {
 				it.Before(func() {
-					analyzer.RunImage = image
+					analyzer.RunImage = previousImage
 				})
 
 				it("returns the run image digest in the analyzed metadata", func() {
@@ -431,13 +485,13 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
 					h.AssertEq(t, md.RunImage.Reference, "s0m3D1g3sT")
 				})
 				it("populates target metadata from the run image", func() {
-					h.AssertNil(t, image.SetLabel("io.buildpacks.base.id", "id software"))
-					h.AssertNil(t, image.SetOS("windows"))
-					h.AssertNil(t, image.SetOSVersion("95"))
-					h.AssertNil(t, image.SetArchitecture("Pentium"))
-					h.AssertNil(t, image.SetVariant("MMX"))
-					h.AssertNil(t, image.SetLabel("io.buildpacks.distro.name", "moobuntu"))
-					h.AssertNil(t, image.SetLabel("io.buildpacks.distro.version", "Helpful Holstein"))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.base.id", "id software"))
+					h.AssertNil(t, previousImage.SetOS("windows"))
+					h.AssertNil(t, previousImage.SetOSVersion("95"))
+					h.AssertNil(t, previousImage.SetArchitecture("Pentium"))
+					h.AssertNil(t, previousImage.SetVariant("MMX"))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.distro.name", "moobuntu"))
+					h.AssertNil(t, previousImage.SetLabel("io.buildpacks.distro.version", "Helpful Holstein"))
 
 					md, err := analyzer.Analyze()
 					h.AssertNil(t, err)
diff --git a/phase/detector_test.go b/phase/detector_test.go
index 4851ec96..3663c2d1 100644
--- a/phase/detector_test.go
+++ b/phase/detector_test.go
@@ -20,6 +20,7 @@ import (
 	"github.com/buildpacks/lifecycle/log"
 	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/phase/testmock"
+	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
 )
@@ -37,7 +38,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 		dirStore      *testmock.MockDirStore
 		logger        log.LoggerHandlerWithLevel
 
-		detectorFactory *phase.DetectorFactory
+		detectorFactory *phase.HermeticFactory
 	)
 
 	it.Before(func() {
@@ -48,7 +49,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 		dirStore = testmock.NewMockDirStore(mockController)
 		logger = log.NewDefaultLogger(io.Discard)
 
-		detectorFactory = phase.NewDetectorFactory(
+		detectorFactory = phase.NewHermeticFactory(
 			api.Platform.Latest(),
 			apiVerifier,
 			configHandler,
@@ -61,6 +62,10 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 	})
 
 	when("#NewDetector", func() {
+		it.Before(func() {
+			configHandler.EXPECT().ReadAnalyzed("some-analyzed-path", gomock.Any()).Return(files.Analyzed{}, nil).AnyTimes()
+		})
+
 		it("configures the detector", func() {
 			order := buildpack.Order{
 				buildpack.Group{Group: []buildpack.GroupElement{{ID: "A", Version: "v1"}}},
@@ -71,9 +76,14 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 			bpA1 := &buildpack.BpDescriptor{WithAPI: "0.2"}
 			dirStore.EXPECT().Lookup(buildpack.KindBuildpack, "A", "v1").Return(bpA1, nil)
 			apiVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindBuildpack, "A@v1", "0.2", logger)
-			amd := files.Analyzed{}
 
-			detector, err := detectorFactory.NewDetector(amd, "some-app-dir", "some-build-config-dir", "some-order-path", "some-platform-dir", logger)
+			detector, err := detectorFactory.NewDetector(platform.LifecycleInputs{
+				AnalyzedPath:   "some-analyzed-path",
+				AppDir:         "some-app-dir",
+				BuildConfigDir: "some-build-config-dir",
+				OrderPath:      "some-order-path",
+				PlatformDir:    "some-platform-dir",
+			}, logger)
 			h.AssertNil(t, err)
 
 			h.AssertEq(t, detector.AppDir, "some-app-dir")
@@ -133,8 +143,13 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 				dirStore.EXPECT().Lookup(buildpack.KindExtension, "D", "v1").Return(extD1, nil)
 				apiVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "D@v1", "some-other-api-version", logger)
 
-				amd := files.Analyzed{}
-				detector, err := detectorFactory.NewDetector(amd, "some-app-dir", "some-build-config-dir", "some-order-path", "some-platform-dir", logger)
+				detector, err := detectorFactory.NewDetector(platform.LifecycleInputs{
+					AnalyzedPath:   "some-analyzed-path",
+					AppDir:         "some-app-dir",
+					BuildConfigDir: "some-build-config-dir",
+					OrderPath:      "some-order-path",
+					PlatformDir:    "some-platform-dir",
+				}, logger)
 				h.AssertNil(t, err)
 
 				h.AssertEq(t, detector.AppDir, "some-app-dir")
@@ -157,17 +172,16 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
 		)
 
 		it.Before(func() {
-			configHandler.EXPECT().ReadOrder(gomock.Any()).Return(buildpack.Order{}, buildpack.Order{}, nil)
-			amd := files.Analyzed{}
+			configHandler.EXPECT().ReadAnalyzed("some-analyzed-path", gomock.Any()).Return(files.Analyzed{}, nil).AnyTimes()
+			configHandler.EXPECT().ReadOrder("some-order-path").Return(buildpack.Order{}, buildpack.Order{}, nil)
 			var err error
-			detector, err = detectorFactory.NewDetector(
-				amd,
-				"some-app-dir",
-				"some-build-config-dir",
-				"some-order-path",
-				"some-platform-dir",
-				logger,
-			)
+			detector, err = detectorFactory.NewDetector(platform.LifecycleInputs{
+				AnalyzedPath:   "some-analyzed-path",
+				AppDir:         "some-app-dir",
+				BuildConfigDir: "some-build-config-dir",
+				OrderPath:      "some-order-path",
+				PlatformDir:    "some-platform-dir",
+			}, logger)
 			h.AssertNil(t, err)
 			// override factory-provided services
 			executor = testmock.NewMockDetectExecutor(mockController)
diff --git a/phase/extender_test.go b/phase/extender_test.go
index eee87370..50e13ff7 100644
--- a/phase/extender_test.go
+++ b/phase/extender_test.go
@@ -25,6 +25,7 @@ import (
 	llog "github.com/buildpacks/lifecycle/log"
 	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/phase/testmock"
+	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
 )
@@ -40,7 +41,7 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 	when("#NewExtender", func() {
 		var (
 			mockController    *gomock.Controller
-			extenderFactory   *phase.ExtenderFactory
+			extenderFactory   *phase.HermeticFactory
 			fakeAPIVerifier   *testmock.MockBuildpackAPIVerifier
 			fakeConfigHandler *testmock.MockConfigHandler
 			fakeDirStore      *testmock.MockDirStore
@@ -56,10 +57,10 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 		createExtender := func() {
 			fakeConfigHandler.EXPECT().ReadAnalyzed("some-analyzed-path", logger).Return(
 				analyzedMD, nil,
-			)
+			).AnyTimes()
 			fakeConfigHandler.EXPECT().ReadGroup("some-group-path").Return(
 				[]buildpack.GroupElement{}, []buildpack.GroupElement{{ID: "A", Version: "v1", API: "0.9"}}, nil,
-			)
+			).AnyTimes()
 			fakeDirStore.EXPECT().LookupExt("A", "v1").Return(&buildpack.ExtDescriptor{
 				WithAPI: "0.9",
 				Extension: buildpack.ExtInfo{
@@ -69,23 +70,21 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 					},
 				},
 			}, nil).AnyTimes()
-			fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger)
-
+			fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger).AnyTimes()
 			fakeDockerfileApplier := testmock.NewMockDockerfileApplier(mockController)
+
 			var err error
-			extender, err = extenderFactory.NewExtender(
-				"some-analyzed-path",
-				"some-app-dir",
-				"some-extended-dir",
-				"some-generated-dir",
-				"some-group-path",
-				"some-layers-dir",
-				"some-platform-dir",
-				7*(24*time.Hour),
-				fakeDockerfileApplier,
-				kind,
-				logger,
-			)
+			extender, err = extenderFactory.NewExtender(platform.LifecycleInputs{
+				AnalyzedPath:   "some-analyzed-path",
+				AppDir:         "some-app-dir",
+				ExtendedDir:    "some-extended-dir",
+				GeneratedDir:   "some-generated-dir",
+				GroupPath:      "some-group-path",
+				LayersDir:      "some-layers-dir",
+				PlatformDir:    "some-platform-dir",
+				KanikoCacheTTL: 7 * (24 * time.Hour),
+				ExtendKind:     kind,
+			}, fakeDockerfileApplier, logger)
 			h.AssertNil(t, err)
 		}
 
@@ -94,7 +93,7 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
 			fakeAPIVerifier = testmock.NewMockBuildpackAPIVerifier(mockController)
 			fakeConfigHandler = testmock.NewMockConfigHandler(mockController)
 			fakeDirStore = testmock.NewMockDirStore(mockController)
-			extenderFactory = phase.NewExtenderFactory(fakeAPIVerifier, fakeConfigHandler)
+			extenderFactory = phase.NewHermeticFactory(api.Platform.Latest(), fakeAPIVerifier, fakeConfigHandler, nil)
 
 			logger = &log.Logger{Handler: &discard.Handler{}}
 		})
diff --git a/phase/generator_test.go b/phase/generator_test.go
index 0862e413..d6b91abe 100644
--- a/phase/generator_test.go
+++ b/phase/generator_test.go
@@ -21,6 +21,7 @@ import (
 	llog "github.com/buildpacks/lifecycle/log"
 	"github.com/buildpacks/lifecycle/phase"
 	"github.com/buildpacks/lifecycle/phase/testmock"
+	"github.com/buildpacks/lifecycle/platform"
 	"github.com/buildpacks/lifecycle/platform/files"
 	h "github.com/buildpacks/lifecycle/testhelpers"
 )
@@ -35,7 +36,7 @@ func TestGenerator(t *testing.T) {
 func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 	when("#NewGenerator", func() {
 		var (
-			generatorFactory  *phase.GeneratorFactory
+			generatorFactory  *phase.HermeticFactory
 			fakeAPIVerifier   *testmock.MockBuildpackAPIVerifier
 			fakeConfigHandler *testmock.MockConfigHandler
 			fakeDirStore      *testmock.MockDirStore
@@ -51,7 +52,8 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 			fakeDirStore = testmock.NewMockDirStore(mockController)
 			logger = &log.Logger{Handler: &discard.Handler{}}
 
-			generatorFactory = phase.NewGeneratorFactory(
+			generatorFactory = phase.NewHermeticFactory(
+				api.Platform.Latest(),
 				fakeAPIVerifier,
 				fakeConfigHandler,
 				fakeDirStore,
@@ -66,7 +68,10 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 			fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger)
 			fakeConfigHandler.EXPECT().ReadAnalyzed("some-analyzed-path", logger).Return(files.Analyzed{RunImage: &files.RunImage{Reference: "some-run-image-ref"}}, nil)
 			fakeConfigHandler.EXPECT().ReadRun("some-run-path", logger).Return(files.Run{Images: []files.RunImageForExport{{Image: "some-run-image"}}}, nil)
-
+			providedExtensions := []buildpack.GroupElement{
+				{ID: "A", Version: "v1", API: "0.9"},
+			}
+			fakeConfigHandler.EXPECT().ReadGroup("some-group-path").Return([]buildpack.GroupElement{}, providedExtensions, nil)
 			providedPlan := files.Plan{Entries: []files.BuildPlanEntry{
 				{
 					Providers: []buildpack.GroupElement{
@@ -77,20 +82,19 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 					},
 				},
 			}}
-			generator, err := generatorFactory.NewGenerator(
-				"some-analyzed-path",
-				"some-app-dir",
-				"some-build-config-dir",
-				[]buildpack.GroupElement{
-					{ID: "A", Version: "v1", API: "0.9"},
-				},
-				"some-output-dir",
-				providedPlan,
-				api.Platform.Latest(),
-				"some-platform-dir",
-				"some-run-path",
-				stdout, stderr,
-				logger,
+			fakeConfigHandler.EXPECT().ReadPlan("some-plan-path").Return(providedPlan, nil)
+
+			generator, err := generatorFactory.NewGenerator(platform.LifecycleInputs{
+				AnalyzedPath:   "some-analyzed-path",
+				AppDir:         "some-app-dir",
+				BuildConfigDir: "some-build-config-dir",
+				GroupPath:      "some-group-path",
+				GeneratedDir:   "some-output-dir",
+				PlanPath:       "some-plan-path",
+				PlatformAPI:    api.Platform.Latest(),
+				PlatformDir:    "some-platform-dir",
+				RunPath:        "some-run-path",
+			}, stdout, stderr, logger,
 			)
 			h.AssertNil(t, err)
 
@@ -98,7 +102,7 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) {
 			h.AssertEq(t, generator.AppDir, "some-app-dir")
 			h.AssertNotNil(t, generator.DirStore)
 			h.AssertEq(t, generator.Extensions, []buildpack.GroupElement{
-				{ID: "A", Version: "v1", API: "0.9"},
+				{ID: "A", Version: "v1", API: "0.9", Extension: true},
 			})
 			h.AssertEq(t, generator.GeneratedDir, "some-output-dir")
 			h.AssertEq(t, generator.Logger, logger)
diff --git a/phase/testmock/config_handler.go b/phase/testmock/config_handler.go
index f1ceb503..a0dc6b1b 100644
--- a/phase/testmock/config_handler.go
+++ b/phase/testmock/config_handler.go
@@ -84,6 +84,21 @@ func (mr *MockConfigHandlerMockRecorder) ReadOrder(arg0 interface{}) *gomock.Cal
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadOrder", reflect.TypeOf((*MockConfigHandler)(nil).ReadOrder), arg0)
 }
 
+// ReadPlan mocks base method.
+func (m *MockConfigHandler) ReadPlan(arg0 string) (files.Plan, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ReadPlan", arg0)
+	ret0, _ := ret[0].(files.Plan)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ReadPlan indicates an expected call of ReadPlan.
+func (mr *MockConfigHandlerMockRecorder) ReadPlan(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadPlan", reflect.TypeOf((*MockConfigHandler)(nil).ReadPlan), arg0)
+}
+
 // ReadRun mocks base method.
 func (m *MockConfigHandler) ReadRun(arg0 string, arg1 log.Logger) (files.Run, error) {
 	m.ctrl.T.Helper()