From 5096ef5c25bf4d7713deb071b207304248acea1a Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Mon, 29 Dec 2025 15:57:42 +0000 Subject: [PATCH 1/2] feat: auto-build images for skaffold verify When no --build-artifacts flag is provided, skaffold verify now automatically builds the artifacts referenced in verify test cases. This improves UX by allowing `skaffold verify` to work out of the box without requiring a separate build step. Fixes #9506 --- cmd/skaffold/app/cmd/verify.go | 39 +++++- cmd/skaffold/app/cmd/verify_test.go | 204 ++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 cmd/skaffold/app/cmd/verify_test.go diff --git a/cmd/skaffold/app/cmd/verify.go b/cmd/skaffold/app/cmd/verify.go index f6b4c9fb4c7..0806b7258bc 100644 --- a/cmd/skaffold/app/cmd/verify.go +++ b/cmd/skaffold/app/cmd/verify.go @@ -18,6 +18,7 @@ package cmd import ( "context" + "fmt" "io" "github.com/spf13/cobra" @@ -43,11 +44,24 @@ func NewCmdVerify() *cobra.Command { func doVerify(ctx context.Context, out io.Writer) error { return withRunner(ctx, out, func(r runner.Runner, configs []util.VersionedConfig) error { - buildArtifacts, err := getBuildArtifactsAndSetTagsForVerify(configs, r.ApplyDefaultRepo) - if err != nil { - tips.PrintUseRunVsDeploy(out) - return err + var buildArtifacts []graph.Artifact + var err error + + // If pre-built artifacts are provided via --build-artifacts flag, use them; otherwise build + if fromBuildOutputFile.String() != "" { + buildArtifacts, err = getBuildArtifactsAndSetTagsForVerify(configs, r.ApplyDefaultRepo) + if err != nil { + tips.PrintUseRunVsDeploy(out) + return err + } + } else { + // Build artifacts that are referenced in verify test cases + buildArtifacts, err = r.Build(ctx, out, targetArtifactsForVerify(configs)) + if err != nil { + return fmt.Errorf("failed to build: %w", err) + } } + defer func() { if err := r.Cleanup(context.Background(), out, false, manifest.NewManifestListByConfig(), opts.Command); err != nil { log.Entry(ctx).Warn("verifier cleanup:", err) @@ -81,3 +95,20 @@ func getVerifyImgs(configs []util.VersionedConfig) map[string]bool { return imgs } + +// targetArtifactsForVerify returns the build artifacts that are referenced by verify test cases. +// Only artifacts whose image names are used in verify containers will be built. +func targetArtifactsForVerify(configs []util.VersionedConfig) []*latest.Artifact { + verifyImgs := getVerifyImgs(configs) + + var artifacts []*latest.Artifact + for _, cfg := range configs { + for _, artifact := range cfg.(*latest.SkaffoldConfig).Build.Artifacts { + // Only include artifacts that are referenced in verify test cases + if verifyImgs[artifact.ImageName] { + artifacts = append(artifacts, artifact) + } + } + } + return artifacts +} diff --git a/cmd/skaffold/app/cmd/verify_test.go b/cmd/skaffold/app/cmd/verify_test.go new file mode 100644 index 00000000000..b4a6712284e --- /dev/null +++ b/cmd/skaffold/app/cmd/verify_test.go @@ -0,0 +1,204 @@ +/* +Copyright 2024 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 cmd + +import ( + "testing" + + "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/util" + "github.com/GoogleContainerTools/skaffold/v2/testutil" +) + +func TestGetVerifyImgs(t *testing.T) { + tests := []struct { + description string + configs []util.VersionedConfig + expected map[string]bool + }{ + { + description: "no verify config", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{}, + }, + expected: map[string]bool{}, + }, + { + description: "single verify test case", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "image1"}}, + }, + }, + }, + }, + expected: map[string]bool{"image1": true}, + }, + { + description: "multiple verify test cases", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "image1"}}, + {Container: latest.VerifyContainer{Image: "image2"}}, + }, + }, + }, + }, + expected: map[string]bool{"image1": true, "image2": true}, + }, + { + description: "multiple configs", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "image1"}}, + }, + }, + }, + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "image2"}}, + }, + }, + }, + }, + expected: map[string]bool{"image1": true, "image2": true}, + }, + } + + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + result := getVerifyImgs(test.configs) + t.CheckDeepEqual(test.expected, result) + }) + } +} + +func TestTargetArtifactsForVerify(t *testing.T) { + tests := []struct { + description string + configs []util.VersionedConfig + expected []*latest.Artifact + }{ + { + description: "no artifacts or verify config", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{}, + }, + expected: nil, + }, + { + description: "build artifact matches verify image", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ + {ImageName: "image1"}, + }, + }, + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "image1"}}, + }, + }, + }, + }, + expected: []*latest.Artifact{{ImageName: "image1"}}, + }, + { + description: "build artifact not used in verify", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ + {ImageName: "image1"}, + {ImageName: "image2"}, + }, + }, + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "image1"}}, + }, + }, + }, + }, + expected: []*latest.Artifact{{ImageName: "image1"}}, + }, + { + description: "verify image not in build artifacts (external image)", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ + {ImageName: "image1"}, + }, + }, + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "alpine:latest"}}, + }, + }, + }, + }, + expected: nil, + }, + { + description: "multiple configs with mixed artifacts", + configs: []util.VersionedConfig{ + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ + {ImageName: "image1"}, + {ImageName: "unused"}, + }, + }, + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "image1"}}, + }, + }, + }, + &latest.SkaffoldConfig{ + Pipeline: latest.Pipeline{ + Build: latest.BuildConfig{ + Artifacts: []*latest.Artifact{ + {ImageName: "image2"}, + }, + }, + Verify: []*latest.VerifyTestCase{ + {Container: latest.VerifyContainer{Image: "image2"}}, + }, + }, + }, + }, + expected: []*latest.Artifact{{ImageName: "image1"}, {ImageName: "image2"}}, + }, + } + + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + result := targetArtifactsForVerify(test.configs) + t.CheckDeepEqual(test.expected, result) + }) + } +} From 62d93ffbd87b63abd44cc63c0b4a46cac187374e Mon Sep 17 00:00:00 2001 From: Rophy Tsai Date: Mon, 29 Dec 2025 22:38:10 +0000 Subject: [PATCH 2/2] refactor: add early exit and pre-allocation to targetArtifactsForVerify --- cmd/skaffold/app/cmd/verify.go | 5 ++++- cmd/skaffold/app/cmd/verify_test.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/skaffold/app/cmd/verify.go b/cmd/skaffold/app/cmd/verify.go index 0806b7258bc..9661c43b190 100644 --- a/cmd/skaffold/app/cmd/verify.go +++ b/cmd/skaffold/app/cmd/verify.go @@ -100,8 +100,11 @@ func getVerifyImgs(configs []util.VersionedConfig) map[string]bool { // Only artifacts whose image names are used in verify containers will be built. func targetArtifactsForVerify(configs []util.VersionedConfig) []*latest.Artifact { verifyImgs := getVerifyImgs(configs) + if len(verifyImgs) == 0 { + return nil + } - var artifacts []*latest.Artifact + artifacts := make([]*latest.Artifact, 0, len(verifyImgs)) for _, cfg := range configs { for _, artifact := range cfg.(*latest.SkaffoldConfig).Build.Artifacts { // Only include artifacts that are referenced in verify test cases diff --git a/cmd/skaffold/app/cmd/verify_test.go b/cmd/skaffold/app/cmd/verify_test.go index b4a6712284e..922e0d9d702 100644 --- a/cmd/skaffold/app/cmd/verify_test.go +++ b/cmd/skaffold/app/cmd/verify_test.go @@ -160,7 +160,7 @@ func TestTargetArtifactsForVerify(t *testing.T) { }, }, }, - expected: nil, + expected: []*latest.Artifact{}, }, { description: "multiple configs with mixed artifacts",