Skip to content

Commit

Permalink
Add new command: pack download-sbom
Browse files Browse the repository at this point in the history
Signed-off-by: Anthony Emengo <aemengo@vmware.com>
  • Loading branch information
Anthony Emengo committed Jan 7, 2022
1 parent 384c978 commit 4cf55bb
Show file tree
Hide file tree
Showing 7 changed files with 420 additions and 26 deletions.
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.DownloadSBOM(logger, 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
47 changes: 47 additions & 0 deletions internal/commands/download_sbom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package commands

import (
"github.com/pkg/errors"
"github.com/spf13/cobra"

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

type DownloadSBOMFlags struct {
Local bool
Remote bool
DestinationDir string
}

func DownloadSBOM(
logger logging.Logger,
client PackClient,
) *cobra.Command {
var flags DownloadSBOMFlags
cmd := &cobra.Command{
Use: "download-sbom <image-name>",
Args: cobra.ExactArgs(1),
Short: "Download SBoM from specified image",
Long: "Download layer containing Structured Bill of Materials (SBoM) from specified image",
Example: "pack download-sbom buildpacksio/pack",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
if flags.Local && flags.Remote {
return errors.New("expected either '--local' or '--remote', not both")
}

img := args[0]
options := cpkg.DownloadSBOMOptions{
Daemon: !flags.Remote,
DestinationDir: flags.DestinationDir,
}

return client.DownloadSBOM(img, options)
}),
}
AddHelpFlag(cmd, "download-sbom")
cmd.Flags().BoolVar(&flags.Local, "local", false, "Pull SBoM from local daemon (Default)")
cmd.Flags().BoolVar(&flags.Remote, "remote", false, "Pull SBoM from remote registry")
cmd.Flags().StringVar(&flags.DestinationDir, "output-dir", ".", "Path to export SBoM contents.\nIt defaults export to the current working directory.")
return cmd
}
110 changes: 110 additions & 0 deletions internal/commands/download_sbom_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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("both --local and --remote are specified", func() {
it("returns a user-friendly message", func() {
command.SetArgs([]string{"some/image", "--local", "--remote"})

err := command.Execute()
h.AssertError(t, err, "expected either '--local' or '--remote', not both")
})
})

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")
})
})
})
}
66 changes: 40 additions & 26 deletions internal/commands/testmocks/mock_pack_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 4cf55bb

Please sign in to comment.