Repository: GoogleContainerTools/skaffold
Pull request #8199 (2022-12-07 21:53:09)

feat: enable --tag flag in render command
Fixes: #5587

**Description**
Enables the `--tag` flag in the `render` command.

codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#8199](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (08a3a52) 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 `4.35%`.
> The diff coverage is `53.26%`.

```diff
@@            Coverage Diff             @@
##             main    #8199      +/-   ##
==========================================
- Coverage   70.48%   66.13%   -4.36%     
==========================================
  Files         515      601      +86     
  Lines       23150    29433    +6283     
==========================================
+ Hits        16317    19465    +3148     
- Misses       5776     8510    +2734     
- Partials     1057     1458     +401     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199?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/completion.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29tcGxldGlvbi5nbw==) | `13.04% <0.00%> (-1.25%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/credits/export.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/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/8199/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZGVwbG95Lmdv) | `40.90% <0.00%> (-12.94%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/test.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/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/app/exitcode.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9leGl0Y29kZS5nbw==) | `100.00% <ø> (+6.66%)` | :arrow_up: |
| [cmd/skaffold/skaffold.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/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/inspect\_tests.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/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/render.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcmVuZGVyLmdv) | `35.48% <18.18%> (-5.90%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/lsp.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/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/run.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcnVuLmdv) | `64.28% <33.33%> (-9.63%)` | :arrow_down: |
| ... and [395 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8199/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)


Patch:
diff --git a/cmd/skaffold/app/cmd/flags.go b/cmd/skaffold/app/cmd/flags.go
index 4685242d8..0590cb029 100644
--- a/cmd/skaffold/app/cmd/flags.go
+++ b/cmd/skaffold/app/cmd/flags.go
@@ -402,7 +402,7 @@ var flagRegistry = []Flag{
 		Value:         &opts.CustomTag,
 		DefValue:      "",
 		FlagAddMethod: "StringVar",
-		DefinedOn:     []string{"build", "debug", "dev", "run", "deploy"},
+		DefinedOn:     []string{"build", "debug", "dev", "run", "deploy", "render"},
 	},
 	{
 		Name:          "platform",
diff --git a/docs-v2/config.toml b/docs-v2/config.toml
index b12062e2e..6a7756a21 100644
--- a/docs-v2/config.toml
+++ b/docs-v2/config.toml
@@ -82,7 +82,7 @@ weight = 1
 copyright = "Skaffold Authors"
 privacy_policy = "https://policies.google.com/privacy"
 github_repo = "https://github.com/GoogleContainerTools/skaffold"
-skaffold_version = "skaffold/v3"
+skaffold_version = "skaffold/v4beta2"
 
 # Google Custom Search Engine ID. Remove or comment out to disable search.
 # gcs_engine_id = "013756393218025596041:3nojel67sum"
diff --git a/docs-v2/content/en/docs/environment/templating.md b/docs-v2/content/en/docs/environment/templating.md
index dad639cf1..ac1168a21 100644
--- a/docs-v2/content/en/docs/environment/templating.md
+++ b/docs-v2/content/en/docs/environment/templating.md
@@ -37,6 +37,7 @@ List of variables that are available for templating:
 * For the `envTemplate` tagger:
   * `IMAGE_NAME` - the artifact's image name - the [image name rewriting]({{< relref "/docs/environment/image-registries.md" >}}) acts after the template is calculated
 * For Helm deployments:
-  * `IMAGE_NAME`, `IMAGE_TAG`, `IMAGE_DIGEST` - the first (by order of declaration in `build.artifacts`) artifact's image name, tag, and sha256 digest. Note: the [image name rewriting]({{< relref "/docs/environment/image-registries.md" >}}) acts after the template is calculated.
-  * `IMAGE_NAME2`, `IMAGE_TAG2`, `IMAGE_DIGEST2` - the 2nd artifact's image name, tag, and sha256 digest
-  * `IMAGE_NAMEN`, `IMAGE_TAGN`, `IMAGE_DIGESTN` - the Nth artifact's image name, tag, and sha256 digest
+  * `IMAGE_NAME`, `IMAGE_TAG`, `IMAGE_DIGEST, IMAGE_DOMAIN, IMAGE_REPO_NO_DOMAIN` - the first (by order of declaration in `build.artifacts`) artifact's image name, repo, tag, sha256 digest, registry/domain and repository w/o the registry/domain prefixed . Note: the [image name rewriting]({{< relref "/docs/environment/image-registries.md" >}}) acts after the template is calculated.
+  * `IMAGE_NAME_<artifact-name>`, `IMAGE_REPO_<artifact-name>`, `IMAGE_TAG_<artifact-name>`, `IMAGE_DIGEST_<artifact-name>` - the named artifact's image name, repo, tag, and sha256 digest. NOTE: When used in for templating all `/` and `-` chars must be changed to `_` characters as go templates do not accept `/` and `-`.
+  * `IMAGE_NAME2`, `IMAGE_REPO2`, `IMAGE_TAG2`, `IMAGE_DIGEST2` - the 2nd artifact's image name, tag, and sha256 digest
+  * `IMAGE_NAME2`, `IMAGE_REPON`, `IMAGE_TAGN`, `IMAGE_DIGESTN` - the Nth artifact's image name, tag, and sha256 digest
diff --git a/docs/content/en/docs/references/cli/_index.md b/docs/content/en/docs/references/cli/_index.md
index 1a74cb012..9b219e970 100644
--- a/docs/content/en/docs/references/cli/_index.md
+++ b/docs/content/en/docs/references/cli/_index.md
@@ -1010,6 +1010,7 @@ Options:
       --remote-cache-dir='': Specify the location of the git repositories cache (default $HOME/.skaffold/repos)
       --resource-selector-rules-file='': Path to JSON file specifying the deny list of yaml objects for skaffold to NOT transform with 'image' and 'label' field replacements.  NOTE: this list is additive to skaffold's default denylist and denylist has priority over allowlist
       --sync-remote-cache='always': Controls how Skaffold manages the remote config cache (see `remote-cache-dir`). One of `always` (default), `missing`, or `never`. `always` syncs remote repositories to latest on access. `missing` only clones remote repositories if they do not exist locally. `never` means the user takes responsibility for updating remote repositories.
+  -t, --tag='': The optional custom tag to use for images which overrides the current Tagger configuration
       --wait-for-connection=false: Blocks ending execution of skaffold until the /v2/events gRPC/HTTP endpoint is hit
 
 Usage:
@@ -1043,6 +1044,7 @@ Env vars:
 * `SKAFFOLD_REMOTE_CACHE_DIR` (same as `--remote-cache-dir`)
 * `SKAFFOLD_RESOURCE_SELECTOR_RULES_FILE` (same as `--resource-selector-rules-file`)
 * `SKAFFOLD_SYNC_REMOTE_CACHE` (same as `--sync-remote-cache`)
+* `SKAFFOLD_TAG` (same as `--tag`)
 * `SKAFFOLD_WAIT_FOR_CONNECTION` (same as `--wait-for-connection`)
 
 ### skaffold run
diff --git a/integration/examples/kaniko/skaffold.yaml b/integration/examples/kaniko/skaffold.yaml
index 16197973a..d104be00b 100644
--- a/integration/examples/kaniko/skaffold.yaml
+++ b/integration/examples/kaniko/skaffold.yaml
@@ -6,9 +6,7 @@ build:
       kaniko:
         cache: {}
   cluster:
-    pullSecretName: e2esecret-with-path
-    pullSecretPath:     kaniko-secret.json
-    namespace: default
+    pullSecretName: e2esecret
 manifests:
   rawYaml:
   - k8s-*
diff --git a/pkg/skaffold/build/build_problems.go b/pkg/skaffold/build/build_problems.go
index 1aeffa356..b5fca1619 100644
--- a/pkg/skaffold/build/build_problems.go
+++ b/pkg/skaffold/build/build_problems.go
@@ -134,10 +134,15 @@ func suggestBuildPushAccessDeniedAction(cfg interface{}) []*proto.Suggestion {
 			return append(suggestions, makeAuthSuggestionsForRepo(defaultRepo))
 		}
 	}
+	action := "Try running with `--default-repo` flag"
+	if buildCfg.Mode() != config.RunModes.Build {
+		// for `interactive` modes, also suggest user to use a local kubernetes cluster
+		action += ". Otherwise start a local kubernetes cluster like `minikube`"
+	}
 
 	return []*proto.Suggestion{{
 		SuggestionCode: proto.SuggestionCode_ADD_DEFAULT_REPO,
-		Action:         "Try running with `--default-repo` flag",
+		Action:         action,
 	}}
 }
 
diff --git a/pkg/skaffold/build/builder_mux.go b/pkg/skaffold/build/builder_mux.go
index 588b1c748..2b7a1d7c8 100644
--- a/pkg/skaffold/build/builder_mux.go
+++ b/pkg/skaffold/build/builder_mux.go
@@ -22,6 +22,7 @@ import (
 	"io"
 	"reflect"
 
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/hooks"
@@ -48,6 +49,7 @@ type Cache interface {
 type Config interface {
 	GetPipelines() []latest.Pipeline
 	DefaultRepo() *string
+	Mode() config.RunMode
 	MultiLevelRepo() *bool
 	GlobalConfig() string
 	BuildConcurrency() int
diff --git a/pkg/skaffold/build/cache/retrieve.go b/pkg/skaffold/build/cache/retrieve.go
index df1221343..82ba05057 100644
--- a/pkg/skaffold/build/cache/retrieve.go
+++ b/pkg/skaffold/build/cache/retrieve.go
@@ -43,7 +43,10 @@ func (c *cache) Build(ctx context.Context, out io.Writer, tags tag.ImageTags, ar
 
 	start := time.Now()
 
-	output.Default.Fprintln(out, "Checking cache...")
+	if len(artifacts) > 0 {
+		output.Default.Fprintln(out, "Checking cache...")
+	}
+
 	ctx, endTrace := instrumentation.StartTrace(ctx, "Build_CheckBuildCache")
 	defer endTrace()
 
diff --git a/pkg/skaffold/runner/build.go b/pkg/skaffold/runner/build.go
index e5b42dc76..85c2ad33f 100644
--- a/pkg/skaffold/runner/build.go
+++ b/pkg/skaffold/runner/build.go
@@ -161,7 +161,12 @@ func (r *Builder) ApplyDefaultRepo(tag string) (string, error) {
 func (r *Builder) imageTags(ctx context.Context, out io.Writer, artifacts []*latest.Artifact) (tag.ImageTags, error) {
 	start := time.Now()
 	maxWorkers := runtime.GOMAXPROCS(0)
-	output.Default.Fprintln(out, "Generating tags...")
+
+	if len(artifacts) > 0 {
+		output.Default.Fprintln(out, "Generating tags...")
+	} else {
+		output.Default.Fprintln(out, "No tags generated")
+	}
 
 	tagErrs := make([]chan tagErr, len(artifacts))
 
diff --git a/pkg/skaffold/runner/deploy.go b/pkg/skaffold/runner/deploy.go
index 0a71d44fe..4edd03875 100644
--- a/pkg/skaffold/runner/deploy.go
+++ b/pkg/skaffold/runner/deploy.go
@@ -68,7 +68,9 @@ func (r *SkaffoldRunner) Deploy(ctx context.Context, out io.Writer, artifacts []
 
 	out, ctx = output.WithEventContext(ctx, out, constants.Deploy, constants.SubtaskIDNone)
 
-	output.Default.Fprintln(out, "Tags used in deployment:")
+	if len(artifacts) > 0 {
+		output.Default.Fprintln(out, "Tags used in deployment:")
+	}
 
 	for _, artifact := range artifacts {
 		output.Default.Fprintf(out, " - %s -> ", artifact.ImageName)
diff --git a/pkg/skaffold/runner/dev.go b/pkg/skaffold/runner/dev.go
index 9bca721c0..221fccf51 100644
--- a/pkg/skaffold/runner/dev.go
+++ b/pkg/skaffold/runner/dev.go
@@ -227,7 +227,12 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la
 	g := getTransposeGraph(artifacts)
 	// Watch artifacts
 	start := time.Now()
-	output.Default.Fprintln(out, "Listing files to watch...")
+
+	if len(artifacts) > 0 {
+		output.Default.Fprintln(out, "Listing files to watch...")
+	} else {
+		output.Default.Fprintln(out, "No artifacts found to watch")
+	}
 
 	for i := range artifacts {
 		artifact := artifacts[i]


Test patch:
diff --git a/integration/render_test.go b/integration/render_test.go
index 1394bfa07..8a345ca83 100644
--- a/integration/render_test.go
+++ b/integration/render_test.go
@@ -837,3 +837,59 @@ spec:
 		})
 	}
 }
+
+func TestRenderWithTagFlag(t *testing.T) {
+	MarkIntegrationTest(t, CanRunWithoutGcp)
+
+	tests := []struct {
+		description    string
+		projectDir     string
+		expectedOutput string
+	}{
+		{
+			description: "tag flag in a single module project",
+			projectDir:  "testdata/getting-started",
+			expectedOutput: `apiVersion: v1
+kind: Pod
+metadata:
+  name: getting-started
+  namespace: default
+spec:
+  containers:
+  - image: gcr.io/k8s-skaffold/skaffold-example:customtag
+    name: getting-started
+`,
+		},
+		{
+			description: "tag flag in a multi module project",
+			projectDir:  "testdata/multi-config-pods",
+			expectedOutput: `apiVersion: v1
+kind: Pod
+metadata:
+  name: module1
+  namespace: default
+spec:
+  containers:
+  - image: gcr.io/k8s-skaffold/multi-config-module1:customtag
+    name: module1
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: module2
+  namespace: default
+spec:
+  containers:
+  - image: gcr.io/k8s-skaffold/multi-config-module2:customtag
+    name: module2
+`,
+		},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			output := skaffold.Render("--tag", "customtag", "--default-repo", "gcr.io/k8s-skaffold").InDir(test.projectDir).RunOrFailOutput(t.T)
+			t.CheckDeepEqual(string(output), test.expectedOutput)
+		})
+	}
+}
diff --git a/integration/run_test.go b/integration/run_test.go
index 2af36151b..99086785f 100644
--- a/integration/run_test.go
+++ b/integration/run_test.go
@@ -318,17 +318,18 @@ func TestRunRenderOnly(t *testing.T) {
 
 func TestRunGCPOnly(t *testing.T) {
 	tests := []struct {
-		description string
-		dir         string
-		args        []string
-		deployments []string
-		pods        []string
+		description       string
+		dir               string
+		args              []string
+		deployments       []string
+		pods              []string
+		skipCrossPlatform bool
 	}{
-		// {
-		//	description: "Google Cloud Build",
-		//	dir:         "examples/google-cloud-build",
-		//	pods:        []string{"getting-started"},
-		// },
+		{
+			description: "Google Cloud Build",
+			dir:         "examples/google-cloud-build",
+			pods:        []string{"getting-started"},
+		},
 		{
 			description: "Google Cloud Build with sub folder",
 			dir:         "testdata/gcb-sub-folder",
@@ -345,31 +346,33 @@ func TestRunGCPOnly(t *testing.T) {
 			args:        []string{"-p", "gcb"},
 			pods:        []string{"module1", "module2"},
 		},
-		// {
-		//	description: "Google Cloud Build with Kaniko",
-		//	dir:         "examples/gcb-kaniko",
-		//	pods:        []string{"getting-started-kaniko"},
-		// },
-		// {
-		//	description: "kaniko",
-		//	dir:         "examples/kaniko",
-		//	pods:        []string{"getting-started-kaniko"},
-		// },
-		// {
-		//	description: "kaniko with target",
-		//	dir:         "testdata/kaniko-target",
-		//	pods:        []string{"getting-started-kaniko"},
-		// },
+		{
+			description: "Google Cloud Build with Kaniko",
+			dir:         "examples/gcb-kaniko",
+			pods:        []string{"getting-started-kaniko"},
+			// building machines on gcb are linux/amd64, kaniko doesn't support cross-platform builds.
+			skipCrossPlatform: true,
+		},
+		{
+			description: "kaniko",
+			dir:         "examples/kaniko",
+			pods:        []string{"getting-started-kaniko"},
+		},
+		{
+			description: "kaniko with target",
+			dir:         "testdata/kaniko-target",
+			pods:        []string{"getting-started-kaniko"},
+		},
 		{
 			description: "kaniko with sub folder",
 			dir:         "testdata/kaniko-sub-folder",
 			pods:        []string{"getting-started-kaniko"},
 		},
-		// {
-		//	description: "kaniko microservices",
-		//	dir:         "testdata/kaniko-microservices",
-		//	deployments: []string{"leeroy-app", "leeroy-web"},
-		// },
+		{
+			description: "kaniko microservices",
+			dir:         "testdata/kaniko-microservices",
+			deployments: []string{"leeroy-app", "leeroy-web"},
+		},
 		{
 			description: "jib in googlecloudbuild",
 			dir:         "testdata/jib",
@@ -387,11 +390,13 @@ func TestRunGCPOnly(t *testing.T) {
 			dir:         "examples/buildpacks",
 			args:        []string{"-p", "gcb"},
 			deployments: []string{"web"},
+			// buildpacks doesn't support arm64 builds.
+			skipCrossPlatform: true,
 		},
 	}
 	for _, test := range tests {
-		if (os.Getenv("GKE_CLUSTER_NAME") == "integration-tests-arm" || os.Getenv("GKE_CLUSTER_NAME") == "integration-tests-hybrid") && strings.Contains(test.description, "buildpacks") {
-			continue // buildpacks doesn't support arm64 builds, so skip run on these clusters
+		if (os.Getenv("GKE_CLUSTER_NAME") == "integration-tests-arm" || os.Getenv("GKE_CLUSTER_NAME") == "integration-tests-hybrid") && test.skipCrossPlatform {
+			continue
 		}
 		t.Run(test.description, func(t *testing.T) {
 			MarkIntegrationTest(t, NeedsGcp)
diff --git a/pkg/skaffold/build/build_problems_test.go b/pkg/skaffold/build/build_problems_test.go
index 256d563c6..0f0804663 100644
--- a/pkg/skaffold/build/build_problems_test.go
+++ b/pkg/skaffold/build/build_problems_test.go
@@ -52,13 +52,15 @@ func TestBuildProblems(t *testing.T) {
 	tests := []struct {
 		description string
 		context     config.ContextConfig
+		mode        config.RunMode
 		optRepo     string
 		err         error
 		expected    string
 		expectedAE  *proto.ActionableErr
 	}{
 		{
-			description: "Push access denied when neither default repo or global config is defined",
+			description: "Push access denied when neither default repo or global config is defined in `build` command",
+			mode:        config.RunModes.Build,
 			err:         fmt.Errorf("skaffold build failed: could not push image: denied: push access to resource"),
 			expected:    "Build Failed. No push access to specified image repository. Try running with `--default-repo` flag.",
 			expectedAE: &proto.ActionableErr{
@@ -70,6 +72,20 @@ func TestBuildProblems(t *testing.T) {
 				},
 				}},
 		},
+		{
+			description: "Push access denied when neither default repo or global config is defined in `dev` command",
+			mode:        config.RunModes.Dev,
+			err:         fmt.Errorf("skaffold build failed: could not push image: denied: push access to resource"),
+			expected:    "Build Failed. No push access to specified image repository. Try running with `--default-repo` flag. Otherwise start a local kubernetes cluster like `minikube`.",
+			expectedAE: &proto.ActionableErr{
+				ErrCode: proto.StatusCode_BUILD_PUSH_ACCESS_DENIED,
+				Message: "skaffold build failed: could not push image: denied: push access to resource",
+				Suggestions: []*proto.Suggestion{{
+					SuggestionCode: proto.SuggestionCode_ADD_DEFAULT_REPO,
+					Action:         "Try running with `--default-repo` flag. Otherwise start a local kubernetes cluster like `minikube`",
+				},
+				}},
+		},
 		{
 			description: "Push access denied when default repo is defined",
 			optRepo:     "gcr.io/test",
@@ -188,7 +204,7 @@ func TestBuildProblems(t *testing.T) {
 				pc.AddPhaseProblems(constants.Build, problems)
 				return pc
 			})
-			cfg := mockConfig{optRepo: test.optRepo}
+			cfg := mockConfig{optRepo: test.optRepo, mode: test.mode}
 			actual := sErrors.ShowAIError(&cfg, test.err)
 			t.CheckDeepEqual(test.expected, actual.Error())
 			actualAE := sErrors.ActionableErr(&cfg, constants.Build, test.err)
diff --git a/pkg/skaffold/build/builder_mux_test.go b/pkg/skaffold/build/builder_mux_test.go
index 05077afe7..fb454c71f 100644
--- a/pkg/skaffold/build/builder_mux_test.go
+++ b/pkg/skaffold/build/builder_mux_test.go
@@ -22,6 +22,7 @@ import (
 	"io"
 	"testing"
 
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
@@ -191,11 +192,13 @@ func TestGetConcurrency(t *testing.T) {
 
 type mockConfig struct {
 	pipelines []latest.Pipeline
+	mode      config.RunMode
 	optRepo   string
 }
 
 func (m *mockConfig) GetPipelines() []latest.Pipeline { return m.pipelines }
 func (m *mockConfig) GlobalConfig() string            { return "" }
+func (m *mockConfig) Mode() config.RunMode            { return m.mode }
 func (m *mockConfig) DefaultRepo() *string {
 	if m.optRepo != "" {
 		return &m.optRepo