From 2da8059f84dacbbcda92a2c4141225e218330c8c Mon Sep 17 00:00:00 2001 From: Radmir Khurum Date: Thu, 16 Apr 2026 06:57:15 +0700 Subject: [PATCH 1/2] refactor(sbom): extract shared SBOM resolution logic to pkg/sbom/image Signed-off-by: Radmir Khurum --- pkg/sbom/image/image.go | 55 ++++++++++++++++++++++++++++++++++++++++- pkg/sbom/merge/merge.go | 52 +++----------------------------------- 2 files changed, 58 insertions(+), 49 deletions(-) diff --git a/pkg/sbom/image/image.go b/pkg/sbom/image/image.go index b8227a5980..db909b5863 100644 --- a/pkg/sbom/image/image.go +++ b/pkg/sbom/image/image.go @@ -52,7 +52,7 @@ func BaseImageName(repo, tag string) string { return ImageName(fmt.Sprintf("%s:%s", repo, tag)) } -func PullCycloneDX16BOM(ctx context.Context, registry docker_registry.Interface, reference string) (*cdx.BOM, error) { +func PullRawSbom(ctx context.Context, registry docker_registry.Interface, reference string) ([]byte, error) { var buf bytes.Buffer if err := registry.PullImageArchive(ctx, &buf, reference); err != nil { return nil, fmt.Errorf("pull image archive: %w", err) @@ -68,6 +68,15 @@ func PullCycloneDX16BOM(ctx context.Context, registry docker_registry.Interface, return nil, fmt.Errorf("extract SBOM from image: %w", err) } + return sbomJSON, nil +} + +func PullCycloneDX16BOM(ctx context.Context, registry docker_registry.Interface, reference string) (*cdx.BOM, error) { + sbomJSON, err := PullRawSbom(ctx, registry, reference) + if err != nil { + return nil, err + } + bom, err := cyclonedxutil.BuildCycloneDX16BOMFromJSON(sbomJSON) if err != nil { return nil, fmt.Errorf("parse CycloneDX BOM: %w", err) @@ -75,3 +84,47 @@ func PullCycloneDX16BOM(ctx context.Context, registry docker_registry.Interface, return bom, nil } + +func BuildDigestToTagIndex(ctx context.Context, registry docker_registry.Interface, repo string) (map[string]string, error) { + tags, err := registry.Tags(ctx, repo) + if err != nil { + return nil, fmt.Errorf("list tags for %s: %w", repo, err) + } + + result := make(map[string]string, len(tags)) + for _, tag := range tags { + if strings.HasSuffix(tag, TagSuffix) { + continue + } + + ref := fmt.Sprintf("%s:%s", repo, tag) + + info, err := registry.TryGetRepoImage(ctx, ref) + if err != nil { + return nil, fmt.Errorf("get image info for %s: %w", ref, err) + } + if info == nil { + continue + } + + d := info.GetDigest() + if d == "" { + continue + } + + if _, exists := result[d]; !exists { + result[d] = tag + } + } + + return result, nil +} + +func ResolveSBOMReference(repo, imageDigest string, digestToTag map[string]string) (string, error) { + tag, ok := digestToTag[imageDigest] + if !ok { + return "", fmt.Errorf("no tag found for digest %s in repo %s", imageDigest, repo) + } + + return BaseImageName(repo, tag), nil +} diff --git a/pkg/sbom/merge/merge.go b/pkg/sbom/merge/merge.go index 6f301aa7b5..16a7ec4930 100644 --- a/pkg/sbom/merge/merge.go +++ b/pkg/sbom/merge/merge.go @@ -15,7 +15,7 @@ import ( "github.com/werf/werf/v2/pkg/docker_registry" "github.com/werf/werf/v2/pkg/sbom/convert" "github.com/werf/werf/v2/pkg/sbom/cyclonedxutil" - "github.com/werf/werf/v2/pkg/sbom/image" + sbomImage "github.com/werf/werf/v2/pkg/sbom/image" ) type Options struct { @@ -179,7 +179,7 @@ func PullAndParseImages(ctx context.Context, registry docker_registry.Interface, var digestToTag map[string]string err := logboek.Context(ctx).Default().LogProcess("Building digest-to-tag index").DoError(func() error { var err error - digestToTag, err = buildDigestToTagIndex(ctx, registry, repo) + digestToTag, err = sbomImage.BuildDigestToTagIndex(ctx, registry, repo) if err != nil { return err } @@ -199,14 +199,14 @@ func PullAndParseImages(ctx context.Context, registry docker_registry.Interface, err := logboek.Context(ctx).Default(). LogProcess("[%d/%d] %s", idx, total, imageName). DoError(func() error { - sbomRef, err := resolveSBOMReference(repo, imageDigest, digestToTag) + sbomRef, err := sbomImage.ResolveSBOMReference(repo, imageDigest, digestToTag) if err != nil { return fmt.Errorf("resolve SBOM reference for %q: %w", imageName, err) } logboek.Context(ctx).Default().LogFDetails("sbom reference: %s\n", sbomRef) - bom, err := image.PullCycloneDX16BOM(ctx, registry, sbomRef) + bom, err := sbomImage.PullCycloneDX16BOM(ctx, registry, sbomRef) if err != nil { return fmt.Errorf("unable to pull SBOM for %q (%s): %w", imageName, sbomRef, err) } @@ -227,50 +227,6 @@ func PullAndParseImages(ctx context.Context, registry docker_registry.Interface, return images, nil } -func resolveSBOMReference(repo, imageDigest string, digestToTag map[string]string) (string, error) { - tag, ok := digestToTag[imageDigest] - if !ok { - return "", fmt.Errorf("no tag found for digest %s in repo %s", imageDigest, repo) - } - - return image.BaseImageName(repo, tag), nil -} - -func buildDigestToTagIndex(ctx context.Context, registry docker_registry.Interface, repo string) (map[string]string, error) { - tags, err := registry.Tags(ctx, repo) - if err != nil { - return nil, fmt.Errorf("list tags for %s: %w", repo, err) - } - - result := make(map[string]string, len(tags)) - for _, tag := range tags { - if strings.HasSuffix(tag, "-sbom") { - continue - } - - ref := fmt.Sprintf("%s:%s", repo, tag) - - info, err := registry.TryGetRepoImage(ctx, ref) - if err != nil { - return nil, fmt.Errorf("get image info for %s: %w", ref, err) - } - if info == nil { - continue - } - - d := info.GetDigest() - if d == "" { - continue - } - - if _, exists := result[d]; !exists { - result[d] = tag - } - } - - return result, nil -} - func WriteOutput(data []byte, outputPath string) error { if outputPath == "" { return logboek.Streams().DoErrorWithoutProxyStreamDataFormatting(func() error { From fde921583de7ef105291ebd641d93d359d916d03 Mon Sep 17 00:00:00 2001 From: Radmir Khurum Date: Thu, 16 Apr 2026 06:57:21 +0700 Subject: [PATCH 2/2] feat(sbom): add --tag and --digest flags to sbom get command Signed-off-by: Radmir Khurum --- cmd/werf/sbom/get/get.go | 142 ++++++++- docs/_includes/reference/cli/werf_sbom_get.md | 4 + test/e2e/sbom/get_test.go | 281 ++++++++++++++---- test/pkg/werf/project.go | 4 +- 4 files changed, 368 insertions(+), 63 deletions(-) diff --git a/cmd/werf/sbom/get/get.go b/cmd/werf/sbom/get/get.go index b6b65c8646..ee27d2eca9 100644 --- a/cmd/werf/sbom/get/get.go +++ b/cmd/werf/sbom/get/get.go @@ -19,6 +19,7 @@ import ( "github.com/werf/werf/v2/pkg/giterminism_manager" "github.com/werf/werf/v2/pkg/sbom/extract" sbomImage "github.com/werf/werf/v2/pkg/sbom/image" + "github.com/werf/werf/v2/pkg/storage" "github.com/werf/werf/v2/pkg/tmp_manager" "github.com/werf/werf/v2/pkg/true_git" "github.com/werf/werf/v2/pkg/werf/global_warnings" @@ -29,6 +30,9 @@ var commonCmdData common.CmdData func NewCmd(ctx context.Context) *cobra.Command { ctx = common.NewContextWithCmdData(ctx, &commonCmdData) + var tagFlag string + var digestFlag string + cmd := common.SetCommandContext(ctx, &cobra.Command{ Use: "get [IMAGE_NAME]", Short: "Get SBOM of an image", @@ -47,9 +51,22 @@ func NewCmd(ctx context.Context) *cobra.Command { return err } + if tagFlag != "" && digestFlag != "" { + common.PrintHelp(cmd) + return fmt.Errorf("--tag and --digest are mutually exclusive") + } + common.LogVersion() return common.LogRunningTime(func() error { + if tagFlag != "" { + return runGetByTag(ctx, tagFlag) + } + + if digestFlag != "" { + return runGetByDigest(ctx, digestFlag) + } + return runGet(ctx, args[0]) }) }, @@ -107,9 +124,127 @@ func NewCmd(ctx context.Context) *cobra.Command { lo.Must0(common.SetupKubeConnectionFlags(&commonCmdData, cmd)) + cmd.Flags().StringVarP(&tagFlag, "tag", "", "", "Content-based tag of the image to get SBOM for (mutually exclusive with --digest)") + cmd.Flags().StringVarP(&digestFlag, "digest", "", "", "Digest of the image to get SBOM for (mutually exclusive with --tag)") + return cmd } +func runGetByTag(ctx context.Context, tag string) error { + global_warnings.PostponeMultiwerfNotUpToDateWarning(ctx) + + commonManager, ctx, err := common.InitCommonComponents(ctx, common.InitCommonComponentsOptions{ + Cmd: &commonCmdData, + InitWerf: true, + InitProcessContainerBackend: true, + InitDockerRegistry: true, + }) + if err != nil { + return fmt.Errorf("component init error: %w", err) + } + + defer func() { + if err := tmp_manager.DelegateCleanup(ctx); err != nil { + logboek.Context(ctx).Warn().LogF("Temporary files cleanup preparation failed: %s\n", err) + } + }() + + repoAddr, err := commonCmdData.Repo.GetAddress() + if err != nil || repoAddr == storage.LocalStorageAddress { + return fmt.Errorf("--repo is required when using --tag") + } + + sbomRef := sbomImage.BaseImageName(repoAddr, tag) + containerBackend := commonManager.ContainerBackend() + + sbomJSON, err := getRawSbom(ctx, containerBackend, repoAddr, sbomRef) + if err != nil { + return err + } + + return writeSbomToStdout(sbomJSON) +} + +func runGetByDigest(ctx context.Context, imageDigest string) error { + global_warnings.PostponeMultiwerfNotUpToDateWarning(ctx) + + _, ctx, err := common.InitCommonComponents(ctx, common.InitCommonComponentsOptions{ + Cmd: &commonCmdData, + InitWerf: true, + InitDockerRegistry: true, + }) + if err != nil { + return fmt.Errorf("component init error: %w", err) + } + + defer func() { + if err := tmp_manager.DelegateCleanup(ctx); err != nil { + logboek.Context(ctx).Warn().LogF("Temporary files cleanup preparation failed: %s\n", err) + } + }() + + repoAddr, err := commonCmdData.Repo.GetAddress() + if err != nil || repoAddr == storage.LocalStorageAddress { + return fmt.Errorf("--repo is required when using --digest") + } + + registry, err := common.CreateDockerRegistry(ctx, repoAddr, *commonCmdData.InsecureRegistry, *commonCmdData.SkipTlsVerifyRegistry) + if err != nil { + return fmt.Errorf("create docker registry: %w", err) + } + + digestToTag, err := sbomImage.BuildDigestToTagIndex(ctx, registry, repoAddr) + if err != nil { + return fmt.Errorf("build digest-to-tag index: %w", err) + } + + sbomRef, err := sbomImage.ResolveSBOMReference(repoAddr, imageDigest, digestToTag) + if err != nil { + return err + } + + sbomJSON, err := sbomImage.PullRawSbom(ctx, registry, sbomRef) + if err != nil { + return fmt.Errorf("pull SBOM image: %w", err) + } + + return writeSbomToStdout(sbomJSON) +} + +func getRawSbom(ctx context.Context, containerBackend container_backend.ContainerBackend, repoAddr, sbomRef string) ([]byte, error) { + opener := func() (io.ReadCloser, error) { + return containerBackend.SaveImageToStream(ctx, sbomRef) + } + + sbomJSON, err := extract.FromImageBytes(opener) + if err == nil { + return sbomJSON, nil + } + + logboek.Context(ctx).Info().LogF("SBOM image not found in local cache, pulling from registry\n") + + registry, err := common.CreateDockerRegistry(ctx, repoAddr, *commonCmdData.InsecureRegistry, *commonCmdData.SkipTlsVerifyRegistry) + if err != nil { + return nil, fmt.Errorf("create docker registry: %w", err) + } + + sbomJSON, err = sbomImage.PullRawSbom(ctx, registry, sbomRef) + if err != nil { + return nil, fmt.Errorf("pull SBOM image: %w", err) + } + + return sbomJSON, nil +} + +func writeSbomToStdout(data []byte) error { + return logboek.Streams().DoErrorWithoutProxyStreamDataFormatting(func() error { + if _, err := io.Copy(os.Stdout, bytes.NewReader(data)); err != nil { + return fmt.Errorf("write SBOM to stdout: %w", err) + } + return nil + }) +} + func runGet(ctx context.Context, requestedImageName string) error { global_warnings.PostponeMultiwerfNotUpToDateWarning(ctx) @@ -234,12 +369,7 @@ func run(ctx context.Context, containerBackend container_backend.ContainerBacken return fmt.Errorf("unable to find artifact file: %w", err) } - return logboek.Streams().DoErrorWithoutProxyStreamDataFormatting(func() error { - if _, err = io.Copy(os.Stdout, bytes.NewReader(artifactContent)); err != nil { - return fmt.Errorf("unable to redirect artifact file content into stdout: %w", err) - } - return nil - }) + return writeSbomToStdout(artifactContent) } func getSbomImageName(exportedImages []*image.Image, requestedImageName string) (string, error) { diff --git a/docs/_includes/reference/cli/werf_sbom_get.md b/docs/_includes/reference/cli/werf_sbom_get.md index 479910141a..7f00cbc8cd 100644 --- a/docs/_includes/reference/cli/werf_sbom_get.md +++ b/docs/_includes/reference/cli/werf_sbom_get.md @@ -68,6 +68,8 @@ werf sbom get [IMAGE_NAME] [options] multiple). Also, can be specified with $WERF_DEV_IGNORE_* (e.g. $WERF_DEV_IGNORE_TESTS=*_test.go, $WERF_DEV_IGNORE_DOCS=path/to/docs) + --digest="" + Digest of the image to get SBOM for (mutually exclusive with --tag) --dir="" Use specified project directory where project’s werf.yaml and other configuration files should reside (default $WERF_DIR or current working directory) @@ -285,6 +287,8 @@ werf sbom get [IMAGE_NAME] [options] The same address should be specified for all werf processes that work with a single repo. :local address allows execution of werf processes from a single host only + --tag="" + Content-based tag of the image to get SBOM for (mutually exclusive with --digest) --tmp-dir="" Use specified dir to store tmp files and dirs (default $WERF_TMP_DIR or system tmp dir) --virtual-merge=false diff --git a/test/e2e/sbom/get_test.go b/test/e2e/sbom/get_test.go index 2d58d41d6d..cfb67ad4d0 100644 --- a/test/e2e/sbom/get_test.go +++ b/test/e2e/sbom/get_test.go @@ -1,77 +1,250 @@ package e2e_build_test import ( + "os" + "path/filepath" + "strings" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/werf/werf/v2/test/pkg/report" "github.com/werf/werf/v2/test/pkg/werf" ) var _ = Describe("Sbom get", Label("e2e", "sbom", "get", "simple"), func() { - DescribeTable("should generate and store SBOM as an image", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) + Describe("default", func() { + DescribeTable("should generate and store SBOM as an image", + func(ctx SpecContext, testOpts simpleTestOptions) { + By("initializing") + setupEnv(testOpts.setupEnvOptions) + + By("state0: case", func() { + repoDirname := "repo0" + fixtureRelPath := "state0" + + By("state0: preparing test repo") + SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) + + By("state0: building images") + werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) + + output := werfProject.SbomGet(ctx, &werf.SbomGetOptions{ + CommonOptions: werf.CommonOptions{ + ExtraArgs: []string{"stapel"}, + }, + }) + + switch testOpts.ContainerBackendMode { + case "vanilla-docker", "buildkit-docker": + // TODO: remove workaround for Docker backend after fixing + // Note: Generation of SBOM returns something like + // `sha256:bee01feb22b978b11472e8bc86065fd88ee370c9782288536ddb58e9904aa584` + // in the first line of output. So, we need to omit this noize. + output = output[(71 + 1):] + } + + Expect(output).To(ContainSubstring(`{"$schema":"http://cyclonedx.org/schema/bom-1.6.schema.json"`)) + }) + }, + Entry("without repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "vanilla-docker", + WithLocalRepo: false, + WithStagedDockerfileBuilder: false, + }}), + Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "vanilla-docker", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + Entry("without repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "buildkit-docker", + WithLocalRepo: false, + WithStagedDockerfileBuilder: false, + }}), + Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "buildkit-docker", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + // TODO (zaytsev): it does not work currently + // https://github.com/werf/werf/actions/runs/15076648086/job/42385521980?pr=6860#step:11:150 + XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "native-rootless", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "native-chroot", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + ) + }) - By("state0: case", func() { - repoDirname := "repo0" - fixtureRelPath := "state0" + Describe("lightweight", Label("tag"), func() { + DescribeTable("should get SBOM by content-based tag", + func(ctx SpecContext, testOpts simpleTestOptions) { + By("initializing") + setupEnv(testOpts.setupEnvOptions) - By("state0: preparing test repo") - SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) + repoDirname := "repo0-tag" + buildReportPath := filepath.Join(SuiteData.TmpDir, "get-tag-build-report.json") - By("state0: building images") + By("preparing test repo") + SuiteData.InitTestRepo(ctx, repoDirname, "state0") + + By("building images with report") werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) + reportProject := report.NewProjectWithReport(werfProject) + _, buildReport := reportProject.BuildWithReport(ctx, buildReportPath, nil) + + record, ok := buildReport.Images["stapel"] + Expect(ok).To(BeTrue(), "build report must contain 'stapel' image") + Expect(record.DockerTag).NotTo(BeEmpty(), "DockerTag must not be empty") + By("getting SBOM by --tag") output := werfProject.SbomGet(ctx, &werf.SbomGetOptions{ CommonOptions: werf.CommonOptions{ - ExtraArgs: []string{"stapel"}, + ExtraArgs: []string{ + "--tag", record.DockerTag, + "--repo", os.Getenv("WERF_REPO"), + }, }, }) - switch testOpts.ContainerBackendMode { - case "vanilla-docker", "buildkit-docker": - // TODO: remove workaround for Docker backend after fixing - // Note: Generation of SBOM returns something like - // `sha256:bee01feb22b978b11472e8bc86065fd88ee370c9782288536ddb58e9904aa584` - // in the first line of output. So, we need to omit this noize. - output = output[(71 + 1):] + Expect(output).To(ContainSubstring(`"$schema":"http://cyclonedx.org/schema/bom-1.6.schema.json"`)) + }, + Entry("without repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "vanilla-docker", + WithLocalRepo: false, + WithStagedDockerfileBuilder: false, + }}), + Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "vanilla-docker", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + Entry("without repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "buildkit-docker", + WithLocalRepo: false, + WithStagedDockerfileBuilder: false, + }}), + Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "buildkit-docker", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "native-rootless", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "native-chroot", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + ) + }) + + Describe("lightweight", Label("digest"), func() { + DescribeTable("should get SBOM by image digest", + func(ctx SpecContext, testOpts simpleTestOptions) { + By("initializing") + setupEnv(testOpts.setupEnvOptions) + + repoDirname := "repo0-digest" + buildReportPath := filepath.Join(SuiteData.TmpDir, "get-digest-build-report.json") + + By("preparing test repo") + SuiteData.InitTestRepo(ctx, repoDirname, "state0") + + By("building images with report") + werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) + reportProject := report.NewProjectWithReport(werfProject) + _, buildReport := reportProject.BuildWithReport(ctx, buildReportPath, nil) + + record, ok := buildReport.Images["stapel"] + Expect(ok).To(BeTrue(), "build report must contain 'stapel' image") + + imageDigest := record.DockerImageDigest + if !strings.HasPrefix(imageDigest, "sha256:") { + imageDigest = "sha256:" + imageDigest } + Expect(imageDigest).NotTo(BeEmpty(), "DockerImageDigest must not be empty") + + By("getting SBOM by --digest") + output := werfProject.SbomGet(ctx, &werf.SbomGetOptions{ + CommonOptions: werf.CommonOptions{ + ExtraArgs: []string{ + "--digest", imageDigest, + "--repo", os.Getenv("WERF_REPO"), + }, + }, + }) + + Expect(output).To(ContainSubstring(`"$schema":"http://cyclonedx.org/schema/bom-1.6.schema.json"`)) + }, + Entry("without repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "vanilla-docker", + WithLocalRepo: false, + WithStagedDockerfileBuilder: false, + }}), + Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "vanilla-docker", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + Entry("without repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "buildkit-docker", + WithLocalRepo: false, + WithStagedDockerfileBuilder: false, + }}), + Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "buildkit-docker", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "native-rootless", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ + ContainerBackendMode: "native-chroot", + WithLocalRepo: true, + WithStagedDockerfileBuilder: false, + }}), + ) + }) - Expect(output).To(ContainSubstring(`{"$schema":"http://cyclonedx.org/schema/bom-1.6.schema.json"`)) - }) - }, - Entry("without repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: false, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("without repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: false, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - // TODO (zaytsev): it does not work currently - // https://github.com/werf/werf/actions/runs/15076648086/job/42385521980?pr=6860#step:11:150 - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) + Describe("negative cases", Label("negative"), func() { + DescribeTable("should fail with mutually exclusive flags", + func(ctx SpecContext, extraArgs []string) { + setupEnv(setupEnvOptions{ + ContainerBackendMode: "vanilla-docker", + WithLocalRepo: true, + }) + + repoDirname := "repo0-neg" + SuiteData.InitTestRepo(ctx, repoDirname, "state0") + werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) + + werfProject.SbomGet(ctx, &werf.SbomGetOptions{ + CommonOptions: werf.CommonOptions{ + ShouldFail: true, + ExtraArgs: extraArgs, + }, + }) + }, + Entry("--tag and --digest together", + []string{ + "--tag", "some-tag", + "--digest", "sha256:abc123", + "--repo", "localhost:5000/test", + }, + ), + ) + }) }) diff --git a/test/pkg/werf/project.go b/test/pkg/werf/project.go index 079745d23e..9e12d4b53d 100644 --- a/test/pkg/werf/project.go +++ b/test/pkg/werf/project.go @@ -256,9 +256,7 @@ func (p *Project) SbomGet(ctx context.Context, opts *SbomGetOptions) (combinedOu } args := append([]string{"sbom", "get"}, opts.ExtraArgs...) - outb := p.RunCommand(ctx, args, CommonOptions{ - ShouldFail: opts.ShouldFail, - }) + outb := p.RunCommand(ctx, args, opts.CommonOptions) return string(outb) }