Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new command: pack sbom download #1351

Merged
merged 2 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
rootCmd.AddCommand(commands.InspectImage(logger, imagewriter.NewFactory(), cfg, packClient))
rootCmd.AddCommand(commands.NewStackCommand(logger))
rootCmd.AddCommand(commands.Rebase(logger, cfg, packClient))
rootCmd.AddCommand(commands.NewSBOMCommand(logger, cfg, packClient))

rootCmd.AddCommand(commands.InspectBuildpack(logger, cfg, packClient))
rootCmd.AddCommand(commands.InspectBuilder(logger, cfg, packClient, builderwriter.NewFactory()))
Expand Down
1 change: 1 addition & 0 deletions internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type PackClient interface {
YankBuildpack(client.YankBuildpackOptions) error
InspectBuildpack(client.InspectBuildpackOptions) (*client.BuildpackInfo, error)
PullBuildpack(context.Context, client.PullBuildpackOptions) error
DownloadSBOM(name string, options client.DownloadSBOMOptions) error
}

func AddHelpFlag(cmd *cobra.Command, commandName string) {
Expand Down
40 changes: 40 additions & 0 deletions internal/commands/download_sbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package commands

import (
"github.com/spf13/cobra"

cpkg "github.com/buildpacks/pack/pkg/client"
"github.com/buildpacks/pack/pkg/logging"
)

type DownloadSBOMFlags struct {
Remote bool
DestinationDir string
}

func DownloadSBOM(
logger logging.Logger,
client PackClient,
) *cobra.Command {
var flags DownloadSBOMFlags
cmd := &cobra.Command{
Use: "download <image-name>",
Args: cobra.ExactArgs(1),
Short: "Download SBoM from specified image",
Long: "Download layer containing Structured Bill of Materials (SBoM) from specified image",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this sbom downloading only work for "app" images? Not run images?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again, are you referring to buildpacks/rfcs#186? As of today, this RFC hasn't been approved and it might be a pre-optimization to implement in light of this.

Example: "pack sbom download buildpacksio/pack",
aemengo marked this conversation as resolved.
Show resolved Hide resolved
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
img := args[0]
options := cpkg.DownloadSBOMOptions{
Daemon: !flags.Remote,
DestinationDir: flags.DestinationDir,
}

aemengo marked this conversation as resolved.
Show resolved Hide resolved
return client.DownloadSBOM(img, options)
}),
}
AddHelpFlag(cmd, "download")
cmd.Flags().BoolVar(&flags.Remote, "remote", false, "Download SBoM of image in remote registry (without pulling image)")
cmd.Flags().StringVarP(&flags.DestinationDir, "output-dir", "o", ".", "Path to export SBoM contents.\nIt defaults export to the current working directory.")
return cmd
}
101 changes: 101 additions & 0 deletions internal/commands/download_sbom_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package commands_test

import (
"bytes"
"testing"

"github.com/golang/mock/gomock"
"github.com/heroku/color"
"github.com/pkg/errors"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/spf13/cobra"

"github.com/buildpacks/pack/internal/commands"
"github.com/buildpacks/pack/internal/commands/testmocks"
cpkg "github.com/buildpacks/pack/pkg/client"
"github.com/buildpacks/pack/pkg/logging"
h "github.com/buildpacks/pack/testhelpers"
)

func TestDownloadSBOMCommand(t *testing.T) {
color.Disable(true)
defer color.Disable(false)
spec.Run(t, "DownloadSBOMCommand", testDownloadSBOMCommand, spec.Parallel(), spec.Report(report.Terminal{}))
}

func testDownloadSBOMCommand(t *testing.T, when spec.G, it spec.S) {
var (
command *cobra.Command
logger logging.Logger
outBuf bytes.Buffer
mockController *gomock.Controller
mockClient *testmocks.MockPackClient
)

it.Before(func() {
mockController = gomock.NewController(t)
mockClient = testmocks.NewMockPackClient(mockController)
logger = logging.NewLogWithWriters(&outBuf, &outBuf)
command = commands.DownloadSBOM(logger, mockClient)
})

it.After(func() {
mockController.Finish()
})

when("#DownloadSBOM", func() {
when("happy path", func() {
it("returns no error", func() {
mockClient.EXPECT().DownloadSBOM("some/image", cpkg.DownloadSBOMOptions{
Daemon: true,
DestinationDir: ".",
})
command.SetArgs([]string{"some/image"})

err := command.Execute()
h.AssertNil(t, err)
})
})

when("the remote flag is specified", func() {
it("respects the remote flag", func() {
mockClient.EXPECT().DownloadSBOM("some/image", cpkg.DownloadSBOMOptions{
Daemon: false,
DestinationDir: ".",
})
command.SetArgs([]string{"some/image", "--remote"})

err := command.Execute()
h.AssertNil(t, err)
})
})

when("the output-dir flag is specified", func() {
it("respects the output-dir flag", func() {
mockClient.EXPECT().DownloadSBOM("some/image", cpkg.DownloadSBOMOptions{
Daemon: true,
DestinationDir: "some-destination-dir",
})
command.SetArgs([]string{"some/image", "--output-dir", "some-destination-dir"})

err := command.Execute()
h.AssertNil(t, err)
})
})

when("the client returns an error", func() {
it("returns the error", func() {
mockClient.EXPECT().DownloadSBOM("some/image", cpkg.DownloadSBOMOptions{
Daemon: true,
DestinationDir: ".",
}).Return(errors.New("some-error"))

command.SetArgs([]string{"some/image"})

err := command.Execute()
h.AssertError(t, err, "some-error")
})
})
})
}
4 changes: 4 additions & 0 deletions internal/commands/inspect_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func InspectImage(
remote, remoteErr := client.InspectImage(img, false)
local, localErr := client.InspectImage(img, true)

if flags.BOM {
logger.Warn("Using the '--bom' flag with 'pack inspect-image <image-name>' is deprecated. Users are encouraged to use 'pack sbom download <image-name>'.")
}

if err := w.Print(logger, sharedImageInfo, local, remote, localErr, remoteErr); err != nil {
return err
}
Expand Down
20 changes: 20 additions & 0 deletions internal/commands/sbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package commands

import (
"github.com/spf13/cobra"

"github.com/buildpacks/pack/internal/config"
"github.com/buildpacks/pack/pkg/logging"
)

func NewSBOMCommand(logger logging.Logger, cfg config.Config, client PackClient) *cobra.Command {
cmd := &cobra.Command{
Use: "sbom",
Short: "Interact with SBoM",
RunE: nil,
}

cmd.AddCommand(DownloadSBOM(logger, client))
AddHelpFlag(cmd, "sbom")
return cmd
}
Loading