Skip to content
Open
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
75 changes: 50 additions & 25 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cmd

import (
"os"

"github.com/heroku/color"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand All @@ -23,6 +25,12 @@ type ConfigurableLogger interface {
WantVerbose(f bool)
}

// clientHolder defers client initialization until PersistentPreRunE,
// allowing root persistent flags
type clientHolder struct {
commands.PackClient
}

// NewPackCommand generates a Pack command
//
//nolint:staticcheck
Expand All @@ -33,15 +41,23 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
return nil, err
}

packClient, err := initClient(logger, cfg)
if err != nil {
return nil, err
}
holder := &clientHolder{}
var dockerHost string

rootCmd := &cobra.Command{
Use: "pack",
Short: "CLI for building apps using Cloud Native Buildpacks",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if dockerHost != "" && dockerHost != "inherit" {
os.Setenv("DOCKER_HOST", dockerHost)
}

packClient, err := initClient(logger, cfg)
if err != nil {
return err
}
holder.PackClient = packClient

if fs := cmd.Flags(); fs != nil {
if forceColor, err := fs.GetBool("force-color"); err == nil && !forceColor {
if flag, err := fs.GetBool("no-color"); err == nil && flag {
Expand All @@ -63,6 +79,8 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
logger.WantTime(flag)
}
}

return nil
},
}

Expand All @@ -71,40 +89,47 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
rootCmd.PersistentFlags().Bool("timestamps", false, "Enable timestamps in output")
rootCmd.PersistentFlags().BoolP("quiet", "q", false, "Show less output")
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Show more output")
rootCmd.PersistentFlags().StringVar(&dockerHost, "docker-host", "",
`Address to docker daemon to connect to.
If not set (or set to empty string) the standard socket location will be used.
Special value 'inherit' may be used in which case DOCKER_HOST environment variable will be used.
This flag is available on all subcommands; for 'build', it controls which daemon is
exposed to the build container's lifecycle phases.
`)
rootCmd.Flags().Bool("version", false, "Show current 'pack' version")

commands.AddHelpFlag(rootCmd, "pack")

rootCmd.AddCommand(commands.Build(logger, cfg, packClient))
rootCmd.AddCommand(commands.NewBuilderCommand(logger, cfg, packClient))
rootCmd.AddCommand(commands.NewBuildpackCommand(logger, cfg, packClient, buildpackage.NewConfigReader()))
rootCmd.AddCommand(commands.NewExtensionCommand(logger, cfg, packClient, buildpackage.NewConfigReader()))
rootCmd.AddCommand(commands.NewConfigCommand(logger, cfg, cfgPath, packClient))
rootCmd.AddCommand(commands.InspectImage(logger, imagewriter.NewFactory(), cfg, packClient))
rootCmd.AddCommand(commands.Build(logger, cfg, holder))
rootCmd.AddCommand(commands.NewBuilderCommand(logger, cfg, holder))
rootCmd.AddCommand(commands.NewBuildpackCommand(logger, cfg, holder, buildpackage.NewConfigReader()))
rootCmd.AddCommand(commands.NewExtensionCommand(logger, cfg, holder, buildpackage.NewConfigReader()))
rootCmd.AddCommand(commands.NewConfigCommand(logger, cfg, cfgPath, holder))
rootCmd.AddCommand(commands.InspectImage(logger, imagewriter.NewFactory(), cfg, holder))
rootCmd.AddCommand(commands.NewStackCommand(logger))
rootCmd.AddCommand(commands.Rebase(logger, cfg, packClient))
rootCmd.AddCommand(commands.NewSBOMCommand(logger, cfg, packClient))
rootCmd.AddCommand(commands.Rebase(logger, cfg, holder))
rootCmd.AddCommand(commands.NewSBOMCommand(logger, cfg, holder))

rootCmd.AddCommand(commands.InspectBuildpack(logger, cfg, packClient))
rootCmd.AddCommand(commands.InspectBuilder(logger, cfg, packClient, builderwriter.NewFactory()))
rootCmd.AddCommand(commands.InspectBuildpack(logger, cfg, holder))
rootCmd.AddCommand(commands.InspectBuilder(logger, cfg, holder, builderwriter.NewFactory()))

rootCmd.AddCommand(commands.SetDefaultBuilder(logger, cfg, cfgPath, packClient))
rootCmd.AddCommand(commands.SetDefaultBuilder(logger, cfg, cfgPath, holder))
rootCmd.AddCommand(commands.SetRunImagesMirrors(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.SuggestBuilders(logger, packClient))
rootCmd.AddCommand(commands.SuggestBuilders(logger, holder))
rootCmd.AddCommand(commands.TrustBuilder(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.UntrustBuilder(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.ListTrustedBuilders(logger, cfg))
rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, packClient))
rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, packClient, buildpackage.NewConfigReader()))
rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, holder))
rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, holder, buildpackage.NewConfigReader()))

if cfg.Experimental {
rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.ListBuildpackRegistries(logger, cfg))
rootCmd.AddCommand(commands.RegisterBuildpack(logger, cfg, packClient))
rootCmd.AddCommand(commands.RegisterBuildpack(logger, cfg, holder))
rootCmd.AddCommand(commands.SetDefaultRegistry(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.RemoveRegistry(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.YankBuildpack(logger, cfg, packClient))
rootCmd.AddCommand(commands.NewManifestCommand(logger, packClient))
rootCmd.AddCommand(commands.YankBuildpack(logger, cfg, holder))
rootCmd.AddCommand(commands.NewManifestCommand(logger, holder))
}

packHome, err := config.PackHome()
Expand All @@ -113,10 +138,10 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
}

rootCmd.AddCommand(commands.CompletionCommand(logger, packHome))
rootCmd.AddCommand(commands.Report(logger, packClient.Version(), cfgPath))
rootCmd.AddCommand(commands.Version(logger, packClient.Version()))
rootCmd.AddCommand(commands.Report(logger, client.Version, cfgPath))
rootCmd.AddCommand(commands.Version(logger, client.Version))

rootCmd.Version = packClient.Version()
rootCmd.Version = client.Version
rootCmd.SetVersionTemplate(`{{.Version}}{{"\n"}}`)
rootCmd.SetOut(logging.GetWriterForLevel(logger, logging.InfoLevel))
rootCmd.SetErr(logging.GetWriterForLevel(logger, logging.ErrorLevel))
Expand Down
135 changes: 135 additions & 0 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package cmd

import (
"io"
"os"
"testing"

h "github.com/buildpacks/pack/testhelpers"
)

// saveAndRestoreEnv saves the current value (or unset state) of an environment
func saveAndRestoreEnv(t *testing.T, key string) {
t.Helper()
orig, wasSet := os.LookupEnv(key)
t.Cleanup(func() {
if wasSet {
os.Setenv(key, orig)
} else {
os.Unsetenv(key)
}
})
}

func TestNewPackCommand_DockerHostPersistentFlag(t *testing.T) {
saveAndRestoreEnv(t, "DOCKER_HOST")

logger := newTestLogger()
rootCmd, err := NewPackCommand(logger)
h.AssertNil(t, err)

flag := rootCmd.PersistentFlags().Lookup("docker-host")
h.AssertNotNil(t, flag)
h.AssertEq(t, flag.DefValue, "")
}

func TestNewPackCommand_DockerHostInheritedBySubcommands(t *testing.T) {
saveAndRestoreEnv(t, "DOCKER_HOST")

logger := newTestLogger()
rootCmd, err := NewPackCommand(logger)
h.AssertNil(t, err)

for _, childName := range []string{"builder", "buildpack", "rebase"} {
child, _, err := rootCmd.Find([]string{childName})
h.AssertNil(t, err)
flag := child.InheritedFlags().Lookup("docker-host")
h.AssertNotNil(t, flag)
}
}

func TestNewPackCommand_DockerHostInheritedByNestedSubcommands(t *testing.T) {
saveAndRestoreEnv(t, "DOCKER_HOST")

logger := newTestLogger()
rootCmd, err := NewPackCommand(logger)
h.AssertNil(t, err)

for _, path := range [][]string{
{"builder", "create"},
{"builder", "inspect"},
{"buildpack", "package"},
} {
child, _, err := rootCmd.Find(path)
h.AssertNil(t, err)
flag := child.InheritedFlags().Lookup("docker-host")
h.AssertNotNil(t, flag)
}
}

func TestNewPackCommand_BuildCommandHasLocalDockerHost(t *testing.T) {
saveAndRestoreEnv(t, "DOCKER_HOST")

logger := newTestLogger()
rootCmd, err := NewPackCommand(logger)
h.AssertNil(t, err)

buildCmd, _, err := rootCmd.Find([]string{"build"})
h.AssertNil(t, err)

localFlag := buildCmd.Flags().Lookup("docker-host")
h.AssertNotNil(t, localFlag)

localFlags := buildCmd.LocalFlags()
h.AssertNotNil(t, localFlags.Lookup("docker-host"))
}

func TestNewPackCommand_DockerHostSetsEnv(t *testing.T) {
saveAndRestoreEnv(t, "DOCKER_HOST")
os.Unsetenv("DOCKER_HOST")

logger := newTestLogger()
rootCmd, err := NewPackCommand(logger)
h.AssertNil(t, err)

rootCmd.SetArgs([]string{"version", "--docker-host", "unix:///custom/docker.sock"})
err = rootCmd.Execute()
h.AssertNil(t, err)

h.AssertEq(t, os.Getenv("DOCKER_HOST"), "unix:///custom/docker.sock")
}

func TestNewPackCommand_DockerHostInheritDoesNotOverrideEnv(t *testing.T) {
saveAndRestoreEnv(t, "DOCKER_HOST")
os.Setenv("DOCKER_HOST", "unix:///original/socket.sock")

logger := newTestLogger()
rootCmd, err := NewPackCommand(logger)
h.AssertNil(t, err)

rootCmd.SetArgs([]string{"version", "--docker-host", "inherit"})
err = rootCmd.Execute()
h.AssertNil(t, err)

h.AssertEq(t, os.Getenv("DOCKER_HOST"), "unix:///original/socket.sock")
}

func newTestLogger() ConfigurableLogger {
return &testLogger{}
}

type testLogger struct{}

func (l *testLogger) Debug(msg string) {}
func (l *testLogger) Debugf(fmt string, v ...interface{}) {}
func (l *testLogger) Info(msg string) {}
func (l *testLogger) Infof(fmt string, v ...interface{}) {}
func (l *testLogger) Warn(msg string) {}
func (l *testLogger) Warnf(fmt string, v ...interface{}) {}
func (l *testLogger) Error(msg string) {}
func (l *testLogger) Errorf(fmt string, v ...interface{}) {}
func (l *testLogger) Writer() io.Writer { return os.Stderr }
func (l *testLogger) IsVerbose() bool { return false }
func (l *testLogger) WantTime(f bool) {}
func (l *testLogger) WantQuiet(f bool) {}
func (l *testLogger) WantVerbose(f bool) {}
Loading