Repository: GoogleContainerTools/skaffold
Pull request #7393 (2022-05-11 19:22:13)

fix: fetch images by platform tag when using` --platform`
Fix fetching digest by platform. 

This is carry over of PR #7301 from an external contributor. They opened it against the v1 branch. 
Initial commit belongs to #3559 with rebase on main after v2 merge. 
Second commit is tests fixed.


codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#7393](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (1b8ddac) into [main](https://codecov.io/gh/GoogleContainerTools/skaffold/commit/290280e8c6c19231e9ed5b5c027f2c01d22f75be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (290280e) will **decrease** coverage by `3.93%`.
> The diff coverage is `55.51%`.

```diff
@@            Coverage Diff             @@
##             main    #7393      +/-   ##
==========================================
- Coverage   70.48%   66.54%   -3.94%     
==========================================
  Files         515      566      +51     
  Lines       23150    27120    +3970     
==========================================
+ Hits        16317    18048    +1731     
- Misses       5776     7771    +1995     
- Partials     1057     1301     +244     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | Coverage Δ | |
|---|---|---|
| [cmd/skaffold/app/cmd/credits/export.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY3JlZGl0cy9leHBvcnQuZ28=) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/deploy.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZGVwbG95Lmdv) | `50.00% <0.00%> (-3.85%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/test.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdGVzdC5nbw==) | `44.44% <0.00%> (ø)` | |
| [cmd/skaffold/skaffold.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL3NrYWZmb2xkLmdv) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/render.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcmVuZGVyLmdv) | `36.66% <12.50%> (-4.72%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/inspect\_tests.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF90ZXN0cy5nbw==) | `62.50% <14.28%> (-1.14%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/lsp.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvbHNwLmdv) | `28.12% <28.12%> (ø)` | |
| [cmd/skaffold/app/cmd/fix.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZml4Lmdv) | `54.09% <38.88%> (-22.38%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/verify.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdmVyaWZ5Lmdv) | `41.17% <41.17%> (ø)` | |
| [cmd/skaffold/app/cmd/lint.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvbGludC5nbw==) | `42.85% <42.85%> (ø)` | |
| ... and [287 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7393/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

:mega: Codecov can now indicate which changes are the most critical in Pull Requests. [Learn more](https://about.codecov.io/product/feature/runtime-insights/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)


Patch:
diff --git a/cmd/skaffold/app/cmd/fix.go b/cmd/skaffold/app/cmd/fix.go
index 196f5b80c..fe1d60cd9 100644
--- a/cmd/skaffold/app/cmd/fix.go
+++ b/cmd/skaffold/app/cmd/fix.go
@@ -87,9 +87,6 @@ func fix(out io.Writer, configFile, outFile string, toVersion string) error {
 	if err != nil {
 		return err
 	}
-	if ok, err := schema.IsCompatibleWith(versionedCfgs, toVersion); !ok {
-		return err
-	}
 	if versionedCfgs, err = schema.UpgradeTo(versionedCfgs, toVersion); err != nil {
 		return err
 	}
diff --git a/hack/schemas/main.go b/hack/schemas/main.go
index c62681eed..3be52dde8 100644
--- a/hack/schemas/main.go
+++ b/hack/schemas/main.go
@@ -134,7 +134,6 @@ func generateV1Schema(root string, dryRun bool, version schema.Version) (bool, e
 	folder := apiVersion
 	strict := false
 	if version.APIVersion == schema.SchemaVersionsV1[len(schema.SchemaVersionsV1)-1].APIVersion {
-		folder = "latest/v1"
 		strict = true
 	}
 
diff --git a/hack/versions/pkg/schema/version.go b/hack/versions/pkg/schema/version.go
index de75ea9f3..69069a345 100644
--- a/hack/versions/pkg/schema/version.go
+++ b/hack/versions/pkg/schema/version.go
@@ -58,6 +58,7 @@ func GetLastReleasedVersion() string {
 	logrus.Infof("last release tag: %s", lastTag)
 	// we split the config in v1.25.0
 	for _, url := range []string{
+		// TODO(marlongamez): update this URL once a release is done for v2
 		fmt.Sprintf("https://raw.githubusercontent.com/GoogleContainerTools/skaffold/%s/pkg/skaffold/schema/latest/v1/config.go", lastTag),
 		fmt.Sprintf("https://raw.githubusercontent.com/GoogleContainerTools/skaffold/%s/pkg/skaffold/schema/latest/config.go", lastTag),
 	} {
diff --git a/pkg/skaffold/build/bazel/build.go b/pkg/skaffold/build/bazel/build.go
index 64a642037..4e3b57130 100644
--- a/pkg/skaffold/build/bazel/build.go
+++ b/pkg/skaffold/build/bazel/build.go
@@ -48,7 +48,7 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, artifact *latest.Art
 	}
 
 	if b.pushImages {
-		return docker.Push(tarPath, tag, b.cfg)
+		return docker.Push(tarPath, tag, b.cfg, nil)
 	}
 	return b.loadImage(ctx, out, tarPath, a, tag)
 }
diff --git a/pkg/skaffold/build/cache/details.go b/pkg/skaffold/build/cache/details.go
index d2c54085d..9cc805ee4 100644
--- a/pkg/skaffold/build/cache/details.go
+++ b/pkg/skaffold/build/cache/details.go
@@ -19,8 +19,12 @@ package cache
 import (
 	"context"
 	"io"
+	"path/filepath"
+	"strings"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
 )
 
 type cacheDetails interface {
@@ -55,7 +59,7 @@ func (d found) Hash() string {
 }
 
 type needsTagging interface {
-	Tag(context.Context, *cache) error
+	Tag(context.Context, *cache, platform.Resolver) error
 }
 
 // Found locally with wrong tag. Needs retagging
@@ -69,7 +73,7 @@ func (d needsLocalTagging) Hash() string {
 	return d.hash
 }
 
-func (d needsLocalTagging) Tag(ctx context.Context, c *cache) error {
+func (d needsLocalTagging) Tag(ctx context.Context, c *cache, platforms platform.Resolver) error {
 	return c.client.Tag(ctx, d.imageID, d.tag)
 }
 
@@ -84,9 +88,10 @@ func (d needsRemoteTagging) Hash() string {
 	return d.hash
 }
 
-func (d needsRemoteTagging) Tag(ctx context.Context, c *cache) error {
+func (d needsRemoteTagging) Tag(ctx context.Context, c *cache, platforms platform.Resolver) error {
 	fqn := d.tag + "@" + d.digest // Tag is not important. We just need the registry and the digest to locate the image.
-	return docker.AddRemoteTag(fqn, d.tag, c.cfg)
+	matched := platforms.GetPlatforms(strings.Split(filepath.Base(d.tag), ":")[0])
+	return docker.AddRemoteTag(fqn, d.tag, c.cfg, util.ConvertToV1Platform(matched.Platforms[0]))
 }
 
 // Found locally. Needs pushing
diff --git a/pkg/skaffold/build/cache/lookup.go b/pkg/skaffold/build/cache/lookup.go
index 1cd308f27..86b39acb7 100644
--- a/pkg/skaffold/build/cache/lookup.go
+++ b/pkg/skaffold/build/cache/lookup.go
@@ -108,7 +108,7 @@ func (c *cache) lookupLocal(ctx context.Context, hash, tag string, entry ImageDe
 }
 
 func (c *cache) lookupRemote(ctx context.Context, hash, tag string, entry ImageDetails) cacheDetails {
-	if remoteDigest, err := docker.RemoteDigest(tag, c.cfg); err == nil {
+	if remoteDigest, err := docker.RemoteDigest(tag, c.cfg, nil); err == nil {
 		// Image exists remotely with the same tag and digest
 		if remoteDigest == entry.Digest {
 			return found{hash: hash}
@@ -117,7 +117,7 @@ func (c *cache) lookupRemote(ctx context.Context, hash, tag string, entry ImageD
 
 	// Image exists remotely with a different tag
 	fqn := tag + "@" + entry.Digest // Actual tag will be ignored but we need the registry and the digest part of it.
-	if remoteDigest, err := docker.RemoteDigest(fqn, c.cfg); err == nil {
+	if remoteDigest, err := docker.RemoteDigest(fqn, c.cfg, nil); err == nil {
 		if remoteDigest == entry.Digest {
 			return needsRemoteTagging{hash: hash, tag: tag, digest: entry.Digest}
 		}
@@ -159,7 +159,7 @@ func (c *cache) tryImport(ctx context.Context, a *latest.Artifact, tag string, h
 		entry.ID = imageID
 	}
 
-	if digest, err := docker.RemoteDigest(tag, c.cfg); err == nil {
+	if digest, err := docker.RemoteDigest(tag, c.cfg, nil); err == nil {
 		log.Entry(ctx).Debugf("Added digest for %s to cache entry", tag)
 		entry.Digest = digest
 	}
diff --git a/pkg/skaffold/build/cache/retrieve.go b/pkg/skaffold/build/cache/retrieve.go
index 7671c615e..7d5f72418 100644
--- a/pkg/skaffold/build/cache/retrieve.go
+++ b/pkg/skaffold/build/cache/retrieve.go
@@ -82,7 +82,7 @@ func (c *cache) Build(ctx context.Context, out io.Writer, tags tag.ImageTags, ar
 		case needsTagging:
 			eventV2.CacheCheckHit(artifact.ImageName)
 			output.Green.Fprintln(out, "Found. Tagging")
-			if err := result.Tag(ctx, c); err != nil {
+			if err := result.Tag(ctx, c, platforms); err != nil {
 				endTrace(instrumentation.TraceEndError(err))
 				return nil, fmt.Errorf("tagging image: %w", err)
 			}
diff --git a/pkg/skaffold/build/cluster/kaniko.go b/pkg/skaffold/build/cluster/kaniko.go
index 79c8c5014..73c1a3b63 100644
--- a/pkg/skaffold/build/cluster/kaniko.go
+++ b/pkg/skaffold/build/cluster/kaniko.go
@@ -97,7 +97,7 @@ func (b *Builder) buildWithKaniko(ctx context.Context, out io.Writer, workspace
 
 	waitForLogs()
 
-	return docker.RemoteDigest(tag, b.cfg)
+	return docker.RemoteDigest(tag, b.cfg, nil)
 }
 
 // first copy over the buildcontext tarball into the init container tmp dir via kubectl cp
diff --git a/pkg/skaffold/build/custom/build.go b/pkg/skaffold/build/custom/build.go
index da892605f..477738ddf 100644
--- a/pkg/skaffold/build/custom/build.go
+++ b/pkg/skaffold/build/custom/build.go
@@ -39,7 +39,7 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, artifact *latest.Art
 	}
 
 	if b.pushImages {
-		return docker.RemoteDigest(tag, b.cfg)
+		return docker.RemoteDigest(tag, b.cfg, nil)
 	}
 
 	imageID, err := b.localDocker.ImageID(ctx, tag)
diff --git a/pkg/skaffold/build/gcb/cloud_build.go b/pkg/skaffold/build/gcb/cloud_build.go
index a9418e878..8c9197e86 100644
--- a/pkg/skaffold/build/gcb/cloud_build.go
+++ b/pkg/skaffold/build/gcb/cloud_build.go
@@ -229,7 +229,7 @@ watch:
 		switch cb.Status {
 		case StatusQueued, StatusWorking, StatusUnknown:
 		case StatusSuccess:
-			digest, err = b.getDigest(cb, tag)
+			digest, err = b.getDigest(cb, tag, platform)
 			if err != nil {
 				return "", sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
 					ErrCode: proto.StatusCode_BUILD_GCB_GET_BUILT_IMAGE_ERR,
@@ -290,7 +290,7 @@ func getBuildID(op *cloudbuild.Operation) (string, error) {
 	return buildMeta.Build.Id, nil
 }
 
-func (b *Builder) getDigest(cb *cloudbuild.Build, defaultToTag string) (string, error) {
+func (b *Builder) getDigest(cb *cloudbuild.Build, defaultToTag string, platforms platform.Matcher) (string, error) {
 	if cb.Results != nil && len(cb.Results.Images) == 1 {
 		return cb.Results.Images[0].Digest, nil
 	}
@@ -298,7 +298,7 @@ func (b *Builder) getDigest(cb *cloudbuild.Build, defaultToTag string) (string,
 	// The build steps pushed the image directly like when we use Jib.
 	// Retrieve the digest for that tag.
 	// TODO(dgageot): I don't think GCB can push to an insecure registry.
-	return docker.RemoteDigest(defaultToTag, b.cfg)
+	return docker.RemoteDigest(defaultToTag, b.cfg, util.ConvertToV1Platform(platforms.Platforms[0]))
 }
 
 func (b *Builder) getLogs(ctx context.Context, c *cstorage.Client, offset int64, bucket, objectName string) (io.ReadCloser, error) {
diff --git a/pkg/skaffold/build/jib/gradle.go b/pkg/skaffold/build/jib/gradle.go
index 65bac302b..a58c23b62 100644
--- a/pkg/skaffold/build/jib/gradle.go
+++ b/pkg/skaffold/build/jib/gradle.go
@@ -59,7 +59,7 @@ func (b *Builder) buildJibGradleToRegistry(ctx context.Context, out io.Writer, w
 		return "", jibToolErr(err)
 	}
 
-	return docker.RemoteDigest(tag, b.cfg)
+	return docker.RemoteDigest(tag, b.cfg, nil)
 }
 
 func (b *Builder) runGradleCommand(ctx context.Context, out io.Writer, workspace string, args []string) error {
diff --git a/pkg/skaffold/build/jib/maven.go b/pkg/skaffold/build/jib/maven.go
index c33329e9a..fe8d0a5e2 100644
--- a/pkg/skaffold/build/jib/maven.go
+++ b/pkg/skaffold/build/jib/maven.go
@@ -59,7 +59,7 @@ func (b *Builder) buildJibMavenToRegistry(ctx context.Context, out io.Writer, wo
 		return "", jibToolErr(err)
 	}
 
-	return docker.RemoteDigest(tag, b.cfg)
+	return docker.RemoteDigest(tag, b.cfg, nil)
 }
 
 func (b *Builder) runMavenCommand(ctx context.Context, out io.Writer, workspace string, args []string) error {
diff --git a/pkg/skaffold/docker/image.go b/pkg/skaffold/docker/image.go
index ab5cb28aa..24b23d76e 100644
--- a/pkg/skaffold/docker/image.go
+++ b/pkg/skaffold/docker/image.go
@@ -287,7 +287,7 @@ func (l *localDaemon) ConfigFile(ctx context.Context, image string) (*v1.ConfigF
 			return nil, err
 		}
 	} else {
-		cfg, err = RetrieveRemoteConfig(image, l.cfg)
+		cfg, err = RetrieveRemoteConfig(image, l.cfg, nil)
 		if err != nil {
 			return nil, err
 		}
@@ -434,7 +434,7 @@ func (l *localDaemon) Push(ctx context.Context, out io.Writer, ref string) (stri
 	if digest == "" {
 		// Maybe this version of Docker doesn't return the digest of the image
 		// that has been pushed.
-		digest, err = RemoteDigest(ref, l.cfg)
+		digest, err = RemoteDigest(ref, l.cfg, nil)
 		if err != nil {
 			return "", fmt.Errorf("getting digest: %w", err)
 		}
diff --git a/pkg/skaffold/docker/image_util.go b/pkg/skaffold/docker/image_util.go
index 8e9a4111b..29e3c14ed 100644
--- a/pkg/skaffold/docker/image_util.go
+++ b/pkg/skaffold/docker/image_util.go
@@ -40,7 +40,7 @@ func RetrieveConfigFile(ctx context.Context, tagged string, cfg Config) (*v1.Con
 	}
 	if err != nil {
 		// No local Docker is available
-		cf, err = RetrieveRemoteConfig(tagged, cfg)
+		cf, err = RetrieveRemoteConfig(tagged, cfg, nil)
 	}
 	if err != nil {
 		return nil, fmt.Errorf("retrieving image config: %w", err)
diff --git a/pkg/skaffold/docker/remote.go b/pkg/skaffold/docker/remote.go
index 3ac4bcf6a..afa53dd5f 100644
--- a/pkg/skaffold/docker/remote.go
+++ b/pkg/skaffold/docker/remote.go
@@ -36,9 +36,9 @@ var (
 	remoteIndex  = remote.Index
 )
 
-func AddRemoteTag(src, target string, cfg Config) error {
+func AddRemoteTag(src, target string, cfg Config, platform *v1.Platform) error {
 	log.Entry(context.TODO()).Debugf("attempting to add tag %s to src %s", target, src)
-	img, err := getRemoteImage(src, cfg)
+	img, err := getRemoteImage(src, cfg, platform)
 	if err != nil {
 		return fmt.Errorf("getting image: %w", err)
 	}
@@ -51,13 +51,13 @@ func AddRemoteTag(src, target string, cfg Config) error {
 	return remote.Write(targetRef, img, remote.WithAuthFromKeychain(primaryKeychain))
 }
 
-func getRemoteDigest(identifier string, cfg Config) (string, error) {
+func getRemoteDigest(identifier string, cfg Config, platform *v1.Platform) (string, error) {
 	idx, err := getRemoteIndex(identifier, cfg)
 	if err == nil {
 		return digest(idx)
 	}
 
-	img, err := getRemoteImage(identifier, cfg)
+	img, err := getRemoteImage(identifier, cfg, platform)
 	if err != nil {
 		return "", fmt.Errorf("getting image: %w", err)
 	}
@@ -66,8 +66,8 @@ func getRemoteDigest(identifier string, cfg Config) (string, error) {
 }
 
 // RetrieveRemoteConfig retrieves the remote config file for an image
-func RetrieveRemoteConfig(identifier string, cfg Config) (*v1.ConfigFile, error) {
-	img, err := getRemoteImage(identifier, cfg)
+func RetrieveRemoteConfig(identifier string, cfg Config, platform *v1.Platform) (*v1.ConfigFile, error) {
+	img, err := getRemoteImage(identifier, cfg, platform)
 	if err != nil {
 		return nil, err
 	}
@@ -76,7 +76,7 @@ func RetrieveRemoteConfig(identifier string, cfg Config) (*v1.ConfigFile, error)
 }
 
 // Push pushes the tarball image
-func Push(tarPath, tag string, cfg Config) (string, error) {
+func Push(tarPath, tag string, cfg Config, platform *v1.Platform) (string, error) {
 	t, err := name.NewTag(tag, name.WeakValidation)
 	if err != nil {
 		return "", fmt.Errorf("parsing tag %q: %w", tag, err)
@@ -91,16 +91,22 @@ func Push(tarPath, tag string, cfg Config) (string, error) {
 		return "", fmt.Errorf("%s %q: %w", sErrors.PushImageErr, t, err)
 	}
 
-	return getRemoteDigest(tag, cfg)
+	return getRemoteDigest(tag, cfg, platform)
 }
 
-func getRemoteImage(identifier string, cfg Config) (v1.Image, error) {
+func getRemoteImage(identifier string, cfg Config, platform *v1.Platform) (v1.Image, error) {
 	ref, err := parseReference(identifier, cfg)
 	if err != nil {
 		return nil, err
 	}
+	options := []remote.Option{
+		remote.WithAuthFromKeychain(primaryKeychain),
+	}
+	if platform != nil {
+		options = append(options, remote.WithPlatform(*platform))
+	}
 
-	return remoteImage(ref, remote.WithAuthFromKeychain(primaryKeychain))
+	return remoteImage(ref, options...)
 }
 
 func getRemoteIndex(identifier string, cfg Config) (v1.ImageIndex, error) {
diff --git a/pkg/skaffold/runner/deployer.go b/pkg/skaffold/runner/deployer.go
index 92d340330..375ee1786 100644
--- a/pkg/skaffold/runner/deployer.go
+++ b/pkg/skaffold/runner/deployer.go
@@ -66,14 +66,14 @@ func (d *deployerCtx) JSONParseConfig() latest.JSONParseConfig {
 }
 
 // GetDeployer creates a deployer from a given RunContext and deploy pipeline definitions.
-func GetDeployer(ctx context.Context, runCtx *runcontext.RunContext, labeller *label.DefaultLabeller, hydrationDir string) (deploy.Deployer, error) {
-	deployerCfg := runCtx.Deployers()
+func GetDeployer(ctx context.Context, runCtx *runcontext.RunContext, labeller *label.DefaultLabeller, hydrationDir string, usingLegacyHelmDeploy bool) (deploy.Deployer, error) {
+	pipelines := runCtx.Pipelines
 
 	if runCtx.Opts.Apply {
 		helmNamespaces := make(map[string]bool)
 		nonHelmDeployFound := false
 
-		for _, d := range deployerCfg {
+		for _, d := range pipelines.Deployers() {
 			if d.DockerDeploy != nil || d.KptDeploy != nil || d.KubectlDeploy != nil || d.KustomizeDeploy != nil {
 				nonHelmDeployFound = true
 			}
@@ -107,7 +107,9 @@ func GetDeployer(ctx context.Context, runCtx *runcontext.RunContext, labeller *l
 	var deployers []deploy.Deployer
 	localDeploy := false
 	remoteDeploy := false
-	for _, d := range deployerCfg {
+	for _, pl := range pipelines.All() {
+		d := pl.Deploy
+		r := pl.Render
 		dCtx := &deployerCtx{runCtx, d}
 
 		if d.DockerDeploy != nil {
@@ -128,6 +130,12 @@ func GetDeployer(ctx context.Context, runCtx *runcontext.RunContext, labeller *l
 		}
 
 		if d.LegacyHelmDeploy != nil {
+			// copy relevant render config to legacy helm deployer
+			if r.Helm != nil {
+				d.LegacyHelmDeploy.Releases = r.Helm.Releases
+				d.LegacyHelmDeploy.Flags = r.Helm.Flags
+			}
+
 			h, err := helm.NewDeployer(ctx, dCtx, labeller, d.LegacyHelmDeploy, runCtx.Artifacts())
 			if err != nil {
 				return nil, err
diff --git a/pkg/skaffold/runner/v2/new.go b/pkg/skaffold/runner/v2/new.go
index 4c34a2208..951dc5746 100644
--- a/pkg/skaffold/runner/v2/new.go
+++ b/pkg/skaffold/runner/v2/new.go
@@ -88,7 +88,7 @@ func NewForConfig(ctx context.Context, runCtx *runcontext.RunContext) (*Skaffold
 		return nil, fmt.Errorf("creating renderer: %w", err)
 	}
 
-	deployer, err = runner.GetDeployer(ctx, runCtx, labeller, hydrationDir)
+	deployer, err = runner.GetDeployer(ctx, runCtx, labeller, hydrationDir, runCtx.UsingLegacyHelmDeploy())
 	if err != nil {
 		endTrace(instrumentation.TraceEndError(err))
 		return nil, fmt.Errorf("creating deployer: %w", err)
diff --git a/pkg/skaffold/runner/v2/render.go b/pkg/skaffold/runner/v2/render.go
index d5817f549..3b643894d 100644
--- a/pkg/skaffold/runner/v2/render.go
+++ b/pkg/skaffold/runner/v2/render.go
@@ -34,7 +34,8 @@ func (r *SkaffoldRunner) Render(ctx context.Context, out io.Writer, builds []gra
 		// Fetch the digest and append it to the tag with the format of "tag@digest"
 		if r.runCtx.DigestSource() == constants.RemoteDigestSource {
 			for i, a := range builds {
-				digest, err := docker.RemoteDigest(a.Tag, r.runCtx)
+				// remote digest to platform dependant build not supported
+				digest, err := docker.RemoteDigest(a.Tag, r.runCtx, nil)
 				if err != nil {
 					return nil, fmt.Errorf("failed to resolve the digest of %s: does the image exist remotely?", a.Tag)
 				}
diff --git a/pkg/skaffold/schema/v2beta27/upgrade.go b/pkg/skaffold/schema/v2beta27/upgrade.go
index d8f46bf5f..29775c5e5 100755
--- a/pkg/skaffold/schema/v2beta27/upgrade.go
+++ b/pkg/skaffold/schema/v2beta27/upgrade.go
@@ -17,8 +17,8 @@ limitations under the License.
 package v2beta27
 
 import (
-	next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
+	next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta28"
 	pkgutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
 )
 
diff --git a/pkg/skaffold/schema/v2beta28/upgrade.go b/pkg/skaffold/schema/v2beta28/upgrade.go
new file mode 100644
index 000000000..6e46daa67
--- /dev/null
+++ b/pkg/skaffold/schema/v2beta28/upgrade.go
@@ -0,0 +1,91 @@
+/*
+Copyright 2022 The Skaffold Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v2beta28
+
+import (
+	"encoding/json"
+
+	"github.com/pkg/errors"
+
+	next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
+	pkgutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
+)
+
+// Upgrade upgrades a configuration to the next version.
+// v2beta28 is the last config version for skaffold v1, and future version will
+// follow the naming scheme of v3*
+func (c *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) {
+	var newConfig next.SkaffoldConfig
+	pkgutil.CloneThroughJSON(c, &newConfig)
+	newConfig.APIVersion = next.Version
+
+	err := util.UpgradePipelines(c, &newConfig, upgradeOnePipeline)
+	return &newConfig, err
+}
+
+func upgradeOnePipeline(oldPipeline, newPipeline interface{}) error {
+	oldPL := oldPipeline.(*Pipeline)
+	newPL := newPipeline.(*next.Pipeline)
+
+	// Copy kubectl deploy config to render config
+	if oldPL.Deploy.KubectlDeploy != nil {
+		newPL.Render.RawK8s = oldPL.Deploy.KubectlDeploy.Manifests
+		newPL.Deploy.KubectlDeploy.Manifests = nil
+	}
+
+	// TODO(marlongamez): port over buildArgs config and copy that
+	// Copy kustomize deploy config to render config
+	if oldPL.Deploy.KustomizeDeploy != nil {
+		newPL.Render.Kustomize = oldPL.Deploy.KustomizeDeploy.KustomizePaths
+		// nil out kustomize deployer as it shouldn't be a thing anymore
+		newPL.Deploy.KustomizeDeploy = nil
+
+		if len(oldPL.Deploy.KustomizeDeploy.BuildArgs) != 0 {
+			return errors.New("converting deploy.kustomize.buildArgs isn't currently supported")
+		}
+	}
+
+	// TODO(marlongamez): what should happen when migrating v2?
+	// Copy Kpt deploy config to render config
+	if oldPL.Deploy.KptDeploy != nil {
+		return errors.New("converting deploy.kpt isn't currently supported")
+	}
+
+	// Copy helm deploy config
+	if oldPL.Deploy.HelmDeploy != nil {
+		oldHelm, err := json.Marshal(*oldPL.Deploy.HelmDeploy)
+		if err != nil {
+			return errors.Wrap(err, "marshalling old helm deploy")
+		}
+		newHelm := next.LegacyHelmDeploy{}
+		if err = json.Unmarshal(oldHelm, &newHelm); err != nil {
+			return errors.Wrap(err, "unmarshalling into new helm deploy")
+		}
+
+		// Copy Releases and Flags into the render config
+		newPL.Render.Helm = &next.Helm{}
+		newPL.Render.Helm.Releases = newHelm.Releases
+		newPL.Render.Helm.Flags = newHelm.Flags
+
+		// Copy over lifecyle hooks for helm deployer
+		newPL.Deploy.LegacyHelmDeploy = &next.LegacyHelmDeploy{}
+		newPL.Deploy.LegacyHelmDeploy.LifecycleHooks = newHelm.LifecycleHooks
+	}
+
+	return nil
+}
diff --git a/pkg/skaffold/schema/versions.go b/pkg/skaffold/schema/versions.go
index f64354b18..508f5c532 100644
--- a/pkg/skaffold/schema/versions.go
+++ b/pkg/skaffold/schema/versions.go
@@ -31,7 +31,6 @@ import (
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
 	sErrors "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/errors"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
-	latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
 	v1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1"
@@ -80,6 +79,7 @@ import (
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta25"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta26"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta27"
+	latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta28"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta3"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta4"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta5"
@@ -215,11 +215,8 @@ func ParseConfigAndUpgrade(filename string) ([]util.VersionedConfig, error) {
 	if err != nil {
 		return nil, err
 	}
-	toVersion, err := getLatestFromCompatibilityCheck(configs)
-	if err != nil {
-		return nil, err
-	}
-	return UpgradeTo(configs, toVersion)
+
+	return UpgradeTo(configs, latest.Version)
 }
 
 // configFactoryFromAPIVersion checks that all configs in the input stream have the same API version, and returns a function to create a config with that API version.


Test patch:
diff --git a/cmd/skaffold/app/cmd/fix_test.go b/cmd/skaffold/app/cmd/fix_test.go
index f4f2353fa..58f898cc7 100644
--- a/cmd/skaffold/app/cmd/fix_test.go
+++ b/cmd/skaffold/app/cmd/fix_test.go
@@ -23,8 +23,8 @@ import (
 	"testing"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
-	latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
 	v1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1"
+	latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta28"
 	"github.com/GoogleContainerTools/skaffold/testutil"
 )
 
diff --git a/cmd/skaffold/app/cmd/schema/list_test.go b/cmd/skaffold/app/cmd/schema/list_test.go
index 8194f3593..2caa11cf4 100644
--- a/cmd/skaffold/app/cmd/schema/list_test.go
+++ b/cmd/skaffold/app/cmd/schema/list_test.go
@@ -22,7 +22,7 @@ import (
 	"strings"
 	"testing"
 
-	latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
+	latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta28"
 	"github.com/GoogleContainerTools/skaffold/testutil"
 )
 
diff --git a/pkg/skaffold/build/cache/lookup_test.go b/pkg/skaffold/build/cache/lookup_test.go
index 16931decf..bd6cef8b8 100644
--- a/pkg/skaffold/build/cache/lookup_test.go
+++ b/pkg/skaffold/build/cache/lookup_test.go
@@ -24,6 +24,7 @@ import (
 	"testing"
 
 	"github.com/docker/docker/client"
+	v1 "github.com/google/go-containerregistry/pkg/v1"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
@@ -196,7 +197,7 @@ func TestLookupRemote(t *testing.T) {
 	}
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
-			t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config) (string, error) {
+			t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config, _ *v1.Platform) (string, error) {
 				switch {
 				case identifier == "tag":
 					return "digest", nil
diff --git a/pkg/skaffold/build/cache/retrieve_test.go b/pkg/skaffold/build/cache/retrieve_test.go
index a4bd625c5..afc323a25 100644
--- a/pkg/skaffold/build/cache/retrieve_test.go
+++ b/pkg/skaffold/build/cache/retrieve_test.go
@@ -24,6 +24,7 @@ import (
 	"testing"
 
 	"github.com/docker/docker/api/types"
+	v1 "github.com/google/go-containerregistry/pkg/v1"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
@@ -218,7 +219,7 @@ func TestCacheBuildRemote(t *testing.T) {
 			return dockerDaemon, nil
 		})
 		t.Override(&docker.DefaultAuthHelper, stubAuth{})
-		t.Override(&docker.RemoteDigest, func(ref string, _ docker.Config) (string, error) {
+		t.Override(&docker.RemoteDigest, func(ref string, _ docker.Config, _ *v1.Platform) (string, error) {
 			switch ref {
 			case "artifact1:tag1":
 				return "sha256:51ae7fa00c92525c319404a3a6d400e52ff9372c5a39cb415e0486fe425f3165", nil
@@ -303,7 +304,7 @@ func TestCacheFindMissing(t *testing.T) {
 			return dockerDaemon, nil
 		})
 		t.Override(&docker.DefaultAuthHelper, stubAuth{})
-		t.Override(&docker.RemoteDigest, func(ref string, _ docker.Config) (string, error) {
+		t.Override(&docker.RemoteDigest, func(ref string, _ docker.Config, _ *v1.Platform) (string, error) {
 			switch ref {
 			case "artifact1:tag1":
 				return "sha256:51ae7fa00c92525c319404a3a6d400e52ff9372c5a39cb415e0486fe425f3165", nil
diff --git a/pkg/skaffold/build/jib/gradle_test.go b/pkg/skaffold/build/jib/gradle_test.go
index 9ddefea44..06013c9a1 100644
--- a/pkg/skaffold/build/jib/gradle_test.go
+++ b/pkg/skaffold/build/jib/gradle_test.go
@@ -28,6 +28,7 @@ import (
 	"testing"
 	"time"
 
+	cv1 "github.com/google/go-containerregistry/pkg/v1"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
@@ -149,7 +150,7 @@ func TestBuildJibGradleToRegistry(t *testing.T) {
 			t.NewTempDir().Touch("build.gradle").Chdir()
 			t.Override(&gradleBuildArgsFunc, getGradleBuildArgsFuncFake(t, MinimumJibGradleVersion))
 			t.Override(&util.DefaultExecCommand, test.commands)
-			t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config) (string, error) {
+			t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config, _ *cv1.Platform) (string, error) {
 				if identifier == "img:tag" {
 					return "digest", nil
 				}
diff --git a/pkg/skaffold/build/jib/maven_test.go b/pkg/skaffold/build/jib/maven_test.go
index 95c426f27..4e981da48 100644
--- a/pkg/skaffold/build/jib/maven_test.go
+++ b/pkg/skaffold/build/jib/maven_test.go
@@ -28,6 +28,7 @@ import (
 	"testing"
 	"time"
 
+	cv1 "github.com/google/go-containerregistry/pkg/v1"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
@@ -143,7 +144,7 @@ func TestBuildJibMavenToRegistry(t *testing.T) {
 			t.Override(&mavenBuildArgsFunc, getMavenBuildArgsFuncFake(t, MinimumJibMavenVersion))
 			t.NewTempDir().Touch("pom.xml").Chdir()
 			t.Override(&util.DefaultExecCommand, test.commands)
-			t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config) (string, error) {
+			t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config, _ *cv1.Platform) (string, error) {
 				if identifier == "img:tag" {
 					return "digest", nil
 				}
diff --git a/pkg/skaffold/docker/remote_test.go b/pkg/skaffold/docker/remote_test.go
index 07df8d916..970ac202e 100644
--- a/pkg/skaffold/docker/remote_test.go
+++ b/pkg/skaffold/docker/remote_test.go
@@ -90,7 +90,7 @@ func TestRemoteImage(t *testing.T) {
 
 			img, err := getRemoteImage(test.image, &mockConfig{
 				insecureRegistries: test.insecureRegistries,
-			})
+			}, nil)
 
 			t.CheckError(test.shouldErr, err)
 			if !test.shouldErr {
@@ -145,7 +145,7 @@ func TestRemoteDigest(t *testing.T) {
 				}, nil
 			})
 
-			digest, err := RemoteDigest(test.image, &mockConfig{})
+			digest, err := RemoteDigest(test.image, &mockConfig{}, nil)
 
 			t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedDigest, digest)
 		})
diff --git a/pkg/skaffold/runner/deployer_test.go b/pkg/skaffold/runner/deployer_test.go
index 7edd5c602..4b76d8065 100644
--- a/pkg/skaffold/runner/deployer_test.go
+++ b/pkg/skaffold/runner/deployer_test.go
@@ -231,7 +231,7 @@ func TestGetDeployer(tOuter *testing.T) {
 						Apply: test.apply,
 					},
 					Pipelines: runcontext.NewPipelines([]latest.Pipeline{test.cfg}),
-				}, &label.DefaultLabeller{}, "")
+				}, &label.DefaultLabeller{}, "", false)
 
 				t.CheckError(test.shouldErr, err)
 				t.CheckTypeEquality(test.expected, deployer)
diff --git a/pkg/skaffold/runner/v2/deploy_test.go b/pkg/skaffold/runner/v2/deploy_test.go
index 6c4f4bbcc..7d9a547dc 100644
--- a/pkg/skaffold/runner/v2/deploy_test.go
+++ b/pkg/skaffold/runner/v2/deploy_test.go
@@ -78,7 +78,7 @@ func TestSkaffoldDeployRenderOnly(t *testing.T) {
 			KubeContext: "does-not-exist",
 		}
 
-		deployer, err := runner.GetDeployer(context.Background(), runCtx, nil, "")
+		deployer, err := runner.GetDeployer(context.Background(), runCtx, nil, "", false)
 		t.RequireNoError(err)
 		r := SkaffoldRunner{
 			runCtx:   runCtx,
diff --git a/pkg/skaffold/schema/defaults/defaults_test.go b/pkg/skaffold/schema/defaults/defaults_test.go
index 415e77d37..9b8cadf62 100644
--- a/pkg/skaffold/schema/defaults/defaults_test.go
+++ b/pkg/skaffold/schema/defaults/defaults_test.go
@@ -476,12 +476,12 @@ func TestSetDefaultRenderer(t *testing.T) {
 			description: "helm manifests",
 			input: latest.RenderConfig{
 				Generate: latest.Generate{
-					Helm: &latest.Helm{Releases: &[]latest.HelmRelease{{Name: "test"}}},
+					Helm: &latest.Helm{Releases: []latest.HelmRelease{{Name: "test"}}},
 				},
 			},
 			expected: latest.RenderConfig{
 				Generate: latest.Generate{
-					Helm: &latest.Helm{Releases: &[]latest.HelmRelease{{Name: "test"}}},
+					Helm: &latest.Helm{Releases: []latest.HelmRelease{{Name: "test"}}},
 				},
 			},
 		},
@@ -491,14 +491,14 @@ func TestSetDefaultRenderer(t *testing.T) {
 				Generate: latest.Generate{
 					Kpt:       []string{"/kmanifests1"},
 					Kustomize: []string{"/kmanifests2"},
-					Helm:      &latest.Helm{Releases: &[]latest.HelmRelease{{Name: "test"}}},
+					Helm:      &latest.Helm{Releases: []latest.HelmRelease{{Name: "test"}}},
 				},
 			},
 			expected: latest.RenderConfig{
 				Generate: latest.Generate{
 					Kpt:       []string{"/kmanifests1"},
 					Kustomize: []string{"/kmanifests2"},
-					Helm:      &latest.Helm{Releases: &[]latest.HelmRelease{{Name: "test"}}},
+					Helm:      &latest.Helm{Releases: []latest.HelmRelease{{Name: "test"}}},
 				},
 			},
 		},
diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go
index 7c6461191..50ea60084 100644
--- a/pkg/skaffold/schema/latest/config.go
+++ b/pkg/skaffold/schema/latest/config.go
@@ -580,7 +580,7 @@ type Helm struct {
 	Flags HelmDeployFlags `yaml:"flags,omitempty"`
 
 	// Releases is a list of Helm releases.
-	Releases *[]HelmRelease `yaml:"releases,omitempty"`
+	Releases []HelmRelease `yaml:"releases,omitempty" yamltags:"required"`
 }
 
 // Transformer describes the supported kpt transformers.
@@ -724,7 +724,7 @@ type KubectlFlags struct {
 // LegacyHelmDeploy *beta* uses the `helm` CLI to apply the charts to the cluster.
 type LegacyHelmDeploy struct {
 	// Releases is a list of Helm releases.
-	Releases []HelmRelease `yaml:"releases,omitempty" yamltags:"required"`
+	Releases []HelmRelease `yaml:"releases,omitempty"`
 
 	// Flags are additional option flags that are passed on the command
 	// line to `helm`.
diff --git a/pkg/skaffold/schema/v2beta27/upgrade_test.go b/pkg/skaffold/schema/v2beta27/upgrade_test.go
index 88bed0ba7..01267142a 100755
--- a/pkg/skaffold/schema/v2beta27/upgrade_test.go
+++ b/pkg/skaffold/schema/v2beta27/upgrade_test.go
@@ -19,7 +19,7 @@ package v2beta27
 import (
 	"testing"
 
-	next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
+	next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta28"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
 	"github.com/GoogleContainerTools/skaffold/testutil"
 )
diff --git a/pkg/skaffold/schema/latest/v1/config.go b/pkg/skaffold/schema/v2beta28/config.go
similarity index 99%
rename from pkg/skaffold/schema/latest/v1/config.go
rename to pkg/skaffold/schema/v2beta28/config.go
index d04b5297c..47b7354b3 100644
--- a/pkg/skaffold/schema/latest/v1/config.go
+++ b/pkg/skaffold/schema/v2beta28/config.go
@@ -1,5 +1,5 @@
 /*
-Copyright 2021 The Skaffold Authors
+Copyright 2022 The Skaffold Authors
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v1
+package v2beta28
 
 import (
 	"encoding/json"
diff --git a/pkg/skaffold/schema/v2beta28/upgrade_test.go b/pkg/skaffold/schema/v2beta28/upgrade_test.go
new file mode 100644
index 000000000..7cb8f0757
--- /dev/null
+++ b/pkg/skaffold/schema/v2beta28/upgrade_test.go
@@ -0,0 +1,216 @@
+/*
+Copyright 2020 The Skaffold Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v2beta28
+
+import (
+	"testing"
+
+	next "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
+	"github.com/GoogleContainerTools/skaffold/testutil"
+)
+
+func TestUpgrade(t *testing.T) {
+	yaml := `apiVersion: skaffold/v2beta28
+kind: Config
+build:
+  artifacts:
+  - image: gcr.io/k8s-skaffold/skaffold-example
+    docker:
+      dockerfile: path/to/Dockerfile
+      secrets:
+      - id: id
+        src: /file.txt
+  - image: gcr.io/k8s-skaffold/bazel
+    bazel:
+      target: //mytarget
+  - image: gcr.io/k8s-skaffold/jib-maven
+    jib:
+      args: ['-v', '--activate-profiles', 'prof']
+      project: dir
+  - image: gcr.io/k8s-skaffold/jib-gradle
+    jib:
+      args: ['-v']
+  - image: gcr.io/k8s-skaffold/buildpacks
+    buildpacks:
+      builder: gcr.io/buildpacks/builder:v1
+    sync:
+      auto: true
+  - image: ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold
+    ko: {}
+    platforms: ['linux/arm64', 'linux/amd64']
+  googleCloudBuild:
+    projectId: test-project
+test:
+  - image: gcr.io/k8s-skaffold/skaffold-example
+    structureTests:
+     - ./test/*
+deploy:
+  kubectl:
+    manifests:
+    - k8s-*
+  kustomize:
+    paths:
+    - kustomization-main
+  helm:
+    releases:
+      - name: skaffold-helm
+        chartPath: charts
+portForward:
+  - resourceType: deployment
+    resourceName: leeroy-app
+    port: 8080
+    localPort: 9001
+profiles:
+  - name: test profile
+    build:
+      artifacts:
+      - image: gcr.io/k8s-skaffold/skaffold-example
+        kaniko:
+          cache: {}
+      cluster:
+        pullSecretName: e2esecret
+        pullSecretPath: secret.json
+        namespace: default
+    test:
+     - image: gcr.io/k8s-skaffold/skaffold-example
+       structureTests:
+         - ./test/*
+    deploy:
+      kubectl:
+        manifests:
+        - k8s-*
+      kustomize:
+        paths:
+        - kustomization-test
+  - name: test local
+    build:
+      artifacts:
+      - image: gcr.io/k8s-skaffold/skaffold-example
+        docker:
+          dockerfile: path/to/Dockerfile
+      local:
+        push: false
+    deploy:
+      kubectl:
+        manifests:
+        - k8s-*
+      kustomize: {}
+`
+	expected := `apiVersion: skaffold/v3alpha1
+kind: Config
+build:
+  artifacts:
+  - image: gcr.io/k8s-skaffold/skaffold-example
+    docker:
+      dockerfile: path/to/Dockerfile
+      secrets:
+      - id: id
+        src: /file.txt
+  - image: gcr.io/k8s-skaffold/bazel
+    bazel:
+      target: //mytarget
+  - image: gcr.io/k8s-skaffold/jib-maven
+    jib:
+      args: ['-v', '--activate-profiles', 'prof']
+      project: dir
+  - image: gcr.io/k8s-skaffold/jib-gradle
+    jib:
+      args: ['-v']
+  - image: gcr.io/k8s-skaffold/buildpacks
+    buildpacks:
+      builder: gcr.io/buildpacks/builder:v1
+    sync:
+      auto: true
+  - image: ko://github.com/GoogleContainerTools/skaffold/cmd/skaffold
+    ko: {}
+    platforms: ['linux/arm64', 'linux/amd64']
+  googleCloudBuild:
+    projectId: test-project
+test:
+  - image: gcr.io/k8s-skaffold/skaffold-example
+    structureTests:
+     - ./test/*
+manifests:
+  rawYaml:
+    - k8s-*
+  kustomize:
+    - kustomization-main
+  helm:
+    releases:
+      - name: skaffold-helm
+        chartPath: charts
+deploy:
+  kubectl: {}
+  helm: {}
+portForward:
+  - resourceType: deployment
+    resourceName: leeroy-app
+    port: 8080
+    localPort: 9001
+profiles:
+  - name: test profile
+    build:
+      artifacts:
+      - image: gcr.io/k8s-skaffold/skaffold-example
+        kaniko:
+          cache: {}
+      cluster:
+        pullSecretName: e2esecret
+        pullSecretPath: secret.json
+        namespace: default
+    test:
+     - image: gcr.io/k8s-skaffold/skaffold-example
+       structureTests:
+         - ./test/*
+    manifests:
+      rawYaml:
+      - k8s-*
+      kustomize:
+      - kustomization-test
+    deploy:
+      kubectl: {}
+  - name: test local
+    build:
+      artifacts:
+      - image: gcr.io/k8s-skaffold/skaffold-example
+        docker:
+          dockerfile: path/to/Dockerfile
+      local:
+        push: false
+    manifests:
+      rawYaml:
+      - k8s-*
+    deploy:
+      kubectl: {}
+`
+	verifyUpgrade(t, yaml, expected)
+}
+
+func verifyUpgrade(t *testing.T, input, output string) {
+	config := NewSkaffoldConfig()
+	err := yaml.UnmarshalStrict([]byte(input), config)
+	testutil.CheckErrorAndDeepEqual(t, false, err, Version, config.GetVersion())
+
+	upgraded, err := config.Upgrade()
+	testutil.CheckError(t, false, err)
+
+	expected := next.NewSkaffoldConfig()
+	err = yaml.UnmarshalStrict([]byte(output), expected)
+
+	testutil.CheckErrorAndDeepEqual(t, false, err, expected, upgraded)
+}
diff --git a/pkg/skaffold/schema/versions_test.go b/pkg/skaffold/schema/versions_test.go
index 33531948d..eb8a096b1 100644
--- a/pkg/skaffold/schema/versions_test.go
+++ b/pkg/skaffold/schema/versions_test.go
@@ -29,13 +29,13 @@ import (
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/defaults"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
-	latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha1"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1beta1"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2alpha1"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta1"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta14"
+	latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta28"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta8"
 	"github.com/GoogleContainerTools/skaffold/testutil"
 )
@@ -694,22 +694,6 @@ func TestUpgradeToNextVersion(t *testing.T) {
 	}
 }
 
-func TestCantUpgradeFromLatestV1Version(t *testing.T) {
-	factory, present := SchemaVersionsV1.Find(latestV1.Version)
-	testutil.CheckDeepEqual(t, true, present)
-
-	_, err := factory().Upgrade()
-	testutil.CheckError(t, true, err)
-}
-
-func TestCantUpgradeFromLatestV2Version(t *testing.T) {
-	factory, present := SchemaVersionsV2.Find(latest.Version)
-	testutil.CheckDeepEqual(t, true, present)
-
-	_, err := factory().Upgrade()
-	testutil.CheckError(t, true, err)
-}
-
 func TestParseConfigAndUpgradeToOlderVersion(t *testing.T) {
 	testutil.Run(t, "", func(t *testutil.T) {
 		t.NewTempDir().
diff --git a/pkg/skaffold/tags/paths_test.go b/pkg/skaffold/tags/paths_test.go
index c67bed65e..58b0b2d69 100644
--- a/pkg/skaffold/tags/paths_test.go
+++ b/pkg/skaffold/tags/paths_test.go
@@ -47,7 +47,7 @@ func TestSetAbsFilePaths(t *testing.T) {
 						Generate: latest.Generate{
 							RawK8s: []string{"foo/*", "/a/foo/*"},
 							Kpt:    []string{"."},
-							Helm: &latest.Helm{Releases: &[]latest.HelmRelease{
+							Helm: &latest.Helm{Releases: []latest.HelmRelease{
 								{ChartPath: "../charts", ValuesFiles: []string{"./values1.yaml", "./values2.yaml"}, SetFiles: map[string]string{"envFile": "./values3.yaml", "configFile": "./values4.yaml", "anotherFile": "/c/values5.yaml"}},
 								{RemoteChart: "foo/bar", ValuesFiles: []string{"./values1.yaml", "./values2.yaml"}, SetFiles: map[string]string{"envFile": "./values3.yaml", "configFile": "./values4.yaml", "anotherFile": "/c/values5.yaml"}}},
 							},
@@ -69,7 +69,7 @@ func TestSetAbsFilePaths(t *testing.T) {
 						Generate: latest.Generate{
 							RawK8s: []string{"/a/b/foo/*", "/a/foo/*"},
 							Kpt:    []string{"/a/b"},
-							Helm: &latest.Helm{Releases: &[]latest.HelmRelease{
+							Helm: &latest.Helm{Releases: []latest.HelmRelease{
 								{ChartPath: "/a/charts", ValuesFiles: []string{"/a/b/values1.yaml", "/a/b/values2.yaml"}, SetFiles: map[string]string{"envFile": "/a/b/values3.yaml", "configFile": "/a/b/values4.yaml", "anotherFile": "/c/values5.yaml"}},
 								{RemoteChart: "foo/bar", ValuesFiles: []string{"/a/b/values1.yaml", "/a/b/values2.yaml"}, SetFiles: map[string]string{"envFile": "/a/b/values3.yaml", "configFile": "/a/b/values4.yaml", "anotherFile": "/c/values5.yaml"}},
 							}},
diff --git a/pkg/skaffold/tags/paths_windows_test.go b/pkg/skaffold/tags/paths_windows_test.go
index 7836bb0bb..030c97c48 100644
--- a/pkg/skaffold/tags/paths_windows_test.go
+++ b/pkg/skaffold/tags/paths_windows_test.go
@@ -45,7 +45,7 @@ func TestSetAbsFilePaths(t *testing.T) {
 						Generate: latest.Generate{
 							RawK8s: []string{`foo\*`, `C:\a\foo\*`},
 							Kpt:    []string{"."},
-							Helm: &latest.Helm{Releases: &[]latest.HelmRelease{
+							Helm: &latest.Helm{Releases: []latest.HelmRelease{
 								{ChartPath: `..\charts`, ValuesFiles: []string{"values1.yaml", "values2.yaml"}, SetFiles: map[string]string{"envFile": "values3.yaml", "configFile": "values4.yaml", "anotherFile": `C:\c\values5.yaml`}},
 								{RemoteChart: "foo/bar", ValuesFiles: []string{"values1.yaml", "values2.yaml"}, SetFiles: map[string]string{"envFile": "values3.yaml", "configFile": "values4.yaml", "anotherFile": `C:\c\values5.yaml`}},
 							}},
@@ -66,7 +66,7 @@ func TestSetAbsFilePaths(t *testing.T) {
 						Generate: latest.Generate{
 							RawK8s: []string{`C:\a\b\foo\*`, `C:\a\foo\*`},
 							Kpt:    []string{`C:\a\b`},
-							Helm: &latest.Helm{Releases: &[]latest.HelmRelease{
+							Helm: &latest.Helm{Releases: []latest.HelmRelease{
 								{ChartPath: `C:\a\charts`, ValuesFiles: []string{`C:\a\b\values1.yaml`, `C:\a\b\values2.yaml`}, SetFiles: map[string]string{"envFile": `C:\a\b\values3.yaml`, "configFile": `C:\a\b\values4.yaml`, "anotherFile": `C:\c\values5.yaml`}},
 								{RemoteChart: "foo/bar", ValuesFiles: []string{`C:\a\b\values1.yaml`, `C:\a\b\values2.yaml`}, SetFiles: map[string]string{"envFile": `C:\a\b\values3.yaml`, "configFile": `C:\a\b\values4.yaml`, "anotherFile": `C:\c\values5.yaml`}},
 							}},
diff --git a/pkg/skaffold/schema/latest/v1/upgrade.go b/pkg/skaffold/util/platform.go
similarity index 62%
rename from pkg/skaffold/schema/latest/v1/upgrade.go
rename to pkg/skaffold/util/platform.go
index 6bb60e1e2..a572afe8f 100644
--- a/pkg/skaffold/schema/latest/v1/upgrade.go
+++ b/pkg/skaffold/util/platform.go
@@ -13,15 +13,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
-package v1
 
-import (
-	"errors"
+package util
 
-	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
+import (
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
-// Upgrade upgrades a configuration to the next version.
-func (c *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) {
-	return nil, errors.New("there's no version to upgrade from \"latest\"")
+func ConvertToV1Platform(platform specs.Platform) *v1.Platform {
+	return &v1.Platform{Architecture: platform.Architecture, OS: platform.OS, OSVersion: platform.OSVersion, OSFeatures: platform.OSFeatures, Variant: platform.Variant}
 }