Skip to content

Commit

Permalink
Merge pull request #982 from buildpacks/924-default-builder
Browse files Browse the repository at this point in the history
Add pack config default-builder subcommand
Signed-off-by: David Freilich <dfreilich@vmware.com>
  • Loading branch information
dfreilich authored Jan 4, 2021
2 parents 06704f6 + a68edce commit 6033196
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 87 deletions.
32 changes: 22 additions & 10 deletions acceptance/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,16 @@ func testWithoutSpecificBuilderRequirement(
})
})

when("set-default-builder", func() {
it("sets the default-stack-id in ~/.pack/config.toml", func() {
builderName := "paketobuildpacks/builder:base"
output := pack.RunSuccessfully("set-default-builder", builderName)
when("pack config", func() {
when("default-builder", func() {
it("sets the default builder in ~/.pack/config.toml", func() {
builderName := "paketobuildpacks/builder:base"
output := pack.RunSuccessfully("config", "default-builder", builderName)

assertions.NewOutputAssertionManager(t, output).ReportsSettingDefaultBuilder(builderName)
assertions.NewOutputAssertionManager(t, output).ReportsSettingDefaultBuilder(builderName)
})
})
})

when("pack config", func() {
when("trusted-builders", func() {
it("prints list of trusted builders", func() {
output := pack.RunSuccessfully("config", "trusted-builders")
Expand Down Expand Up @@ -573,7 +573,11 @@ func testWithoutSpecificBuilderRequirement(

when("default builder is set", func() {
it("redacts default builder", func() {
pack.RunSuccessfully("set-default-builder", "paketobuildpacks/builder:base")
if pack.Supports("config default-builder") {
pack.RunSuccessfully("config", "default-builder", "paketobuildpacks/builder:base")
} else {
pack.RunSuccessfully("set-default-builder", "paketobuildpacks/builder:base")
}

output := pack.RunSuccessfully("report")

Expand All @@ -592,7 +596,11 @@ func testWithoutSpecificBuilderRequirement(
})

it("explicit mode doesn't redact", func() {
pack.RunSuccessfully("set-default-builder", "paketobuildpacks/builder:base")
if pack.Supports("config default-builder") {
pack.RunSuccessfully("config", "default-builder", "paketobuildpacks/builder:base")
} else {
pack.RunSuccessfully("set-default-builder", "paketobuildpacks/builder:base")
}

output := pack.RunSuccessfully("report", "--explicit")

Expand Down Expand Up @@ -913,7 +921,11 @@ func testAcceptance(
var usingCreator bool

it.Before(func() {
pack.JustRunSuccessfully("set-default-builder", builderName)
if pack.Supports("config default-builder") {
pack.RunSuccessfully("config", "default-builder", builderName)
} else {
pack.RunSuccessfully("set-default-builder", builderName)
}

if pack.Supports("config trusted-builders add") {
pack.JustRunSuccessfully("config", "trusted-builders", "add", builderName)
Expand Down
4 changes: 2 additions & 2 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
rootCmd.AddCommand(commands.SuggestStacks(logger))

rootCmd.AddCommand(commands.Version(logger, pack.Version))
rootCmd.AddCommand(commands.Report(logger, pack.Version))
rootCmd.AddCommand(commands.Report(logger, pack.Version, cfgPath))

if cfg.Experimental {
rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath))
Expand All @@ -102,7 +102,7 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {

rootCmd.AddCommand(commands.CompletionCommand(logger, packHome))

rootCmd.AddCommand(commands.NewConfigCommand(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.NewConfigCommand(logger, cfg, cfgPath, &packClient))
rootCmd.AddCommand(commands.NewStackCommand(logger))
rootCmd.AddCommand(commands.NewBuilderCommand(logger, cfg, &packClient))

Expand Down
7 changes: 4 additions & 3 deletions internal/commands/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ import (
"github.com/buildpacks/pack/logging"
)

func NewConfigCommand(logger logging.Logger, cfg config.Config, cfgPath string) *cobra.Command {
func NewConfigCommand(logger logging.Logger, cfg config.Config, cfgPath string, client PackClient) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Interact with Pack's configuration",
RunE: nil,
}

cmd.AddCommand(trustedBuilder(logger, cfg, cfgPath))
cmd.AddCommand(ConfigRunImagesMirrors(logger, cfg, cfgPath))
cmd.AddCommand(ConfigDefaultBuilder(logger, cfg, cfgPath, client))
cmd.AddCommand(ConfigExperimental(logger, cfg, cfgPath))
cmd.AddCommand(ConfigTrustedBuilder(logger, cfg, cfgPath))
cmd.AddCommand(ConfigRunImagesMirrors(logger, cfg, cfgPath))

if cfg.Experimental {
cmd.AddCommand(ConfigRegistries(logger, cfg, cfgPath))
Expand Down
91 changes: 91 additions & 0 deletions internal/commands/config_default_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package commands

import (
"fmt"

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

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

var suggestedBuilderString = "For suggested builders, run `pack builder suggest`."

func ConfigDefaultBuilder(logger logging.Logger, cfg config.Config, cfgPath string, client PackClient) *cobra.Command {
var unset bool

cmd := &cobra.Command{
Use: "default-builder",
Args: cobra.MaximumNArgs(1),
Short: "List, set and unset the default builder used by other commands",
Long: "List, set, and unset the default builder used by other commands.\n\n" +
"* To list your default builder, run `pack config default-builder`.\n" +
"* To set your default builder, run `pack config default-builder <builder-name>`.\n" +
"* To unset your default builder, run `pack config default-builder --unset`.\n\n" +
suggestedBuilderString,
Example: "pack config default-builder cnbs/sample-builder:bionic",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
switch {
case unset:
if cfg.DefaultBuilder == "" {
logger.Info("No default builder was set")
} else {
oldBuilder := cfg.DefaultBuilder
cfg.DefaultBuilder = ""
if err := config.Write(cfg, cfgPath); err != nil {
return errors.Wrapf(err, "failed to write to config at %s", cfgPath)
}
logger.Infof("Successfully unset default builder %s", style.Symbol(oldBuilder))
}
case len(args) == 0:
if cfg.DefaultBuilder != "" {
logger.Infof("The current default builder is %s", style.Symbol(cfg.DefaultBuilder))
} else {
logger.Infof("No default builder is set. \n\n%s", suggestedBuilderString)
}
return nil
default:
imageName := args[0]
if err := validateBuilderExists(logger, imageName, client); err != nil {
return errors.Wrapf(err, "validating that builder %s exists", style.Symbol(imageName))
}

cfg.DefaultBuilder = imageName
if err := config.Write(cfg, cfgPath); err != nil {
return errors.Wrapf(err, "failed to write to config at %s", cfgPath)
}
logger.Infof("Builder %s is now the default builder", style.Symbol(imageName))
}

return nil
}),
}

cmd.Flags().BoolVarP(&unset, "unset", "u", false, "Unset the current default builder")
AddHelpFlag(cmd, "config default-builder")
return cmd
}

func validateBuilderExists(logger logging.Logger, imageName string, client PackClient) error {
logger.Debug("Verifying local image...")
info, err := client.InspectBuilder(imageName, true)
if err != nil {
return err
}

if info == nil {
logger.Debug("Verifying remote image...")
info, err := client.InspectBuilder(imageName, false)
if err != nil {
return errors.Wrapf(err, "failed to inspect remote image %s", style.Symbol(imageName))
}

if info == nil {
return fmt.Errorf("builder %s not found", style.Symbol(imageName))
}
}

return nil
}
204 changes: 204 additions & 0 deletions internal/commands/config_default_builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package commands_test

import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"

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

"github.com/buildpacks/pack"
"github.com/buildpacks/pack/internal/commands"
"github.com/buildpacks/pack/internal/commands/testmocks"
"github.com/buildpacks/pack/internal/config"
ilogging "github.com/buildpacks/pack/internal/logging"
"github.com/buildpacks/pack/internal/style"
"github.com/buildpacks/pack/logging"
h "github.com/buildpacks/pack/testhelpers"
)

func TestConfigDefaultBuilder(t *testing.T) {
color.Disable(true)
defer color.Disable(false)
spec.Run(t, "ConfigDefaultBuilderCommand", testConfigDefaultBuilder, spec.Random(), spec.Report(report.Terminal{}))
}

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

it.Before(func() {
var err error

mockController = gomock.NewController(t)
mockClient = testmocks.NewMockPackClient(mockController)
logger = ilogging.NewLogWithWriters(&outBuf, &outBuf)
tempPackHome, err = ioutil.TempDir("", "pack-home")
h.AssertNil(t, err)
configPath = filepath.Join(tempPackHome, "config.toml")
cmd = commands.ConfigDefaultBuilder(logger, config.Config{}, configPath, mockClient)
})

it.After(func() {
mockController.Finish()
h.AssertNil(t, os.RemoveAll(tempPackHome))
})

when("#ConfigDefaultBuilder", func() {
when("no args", func() {
it("lists current default builder if one is set", func() {
cmd = commands.ConfigDefaultBuilder(logger, config.Config{DefaultBuilder: "some/builder"}, configPath, mockClient)
cmd.SetArgs([]string{})
h.AssertNil(t, cmd.Execute())
h.AssertContains(t, outBuf.String(), "some/builder")
})

it("suggests setting a builder if none is set", func() {
cmd.SetArgs([]string{})
h.AssertNil(t, cmd.Execute())
h.AssertContains(t, outBuf.String(), "No default builder is set.")
h.AssertContains(t, outBuf.String(), "run `pack builder suggest`")
})
})

when("unset", func() {
it("unsets current default builder", func() {
cfg := config.Config{DefaultBuilder: "some/builder"}
h.AssertNil(t, config.Write(cfg, configPath))
cmd = commands.ConfigDefaultBuilder(logger, cfg, configPath, mockClient)
cmd.SetArgs([]string{"--unset"})
err := cmd.Execute()
h.AssertNil(t, err)
cfg, err = config.Read(configPath)
h.AssertNil(t, err)
h.AssertEq(t, cfg.DefaultBuilder, "")
h.AssertContains(t, outBuf.String(), fmt.Sprintf("Successfully unset default builder %s", style.Symbol("some/builder")))
})

it("clarifies if no builder was set", func() {
cmd.SetArgs([]string{"--unset"})
h.AssertNil(t, cmd.Execute())
h.AssertContains(t, outBuf.String(), "No default builder was set")
})

it("gives clear error if unable to write to config", func() {
h.AssertNil(t, ioutil.WriteFile(configPath, []byte("some-data"), 0001))
cmd = commands.ConfigDefaultBuilder(logger, config.Config{DefaultBuilder: "some/builder"}, configPath, mockClient)
cmd.SetArgs([]string{"--unset"})
err := cmd.Execute()
h.AssertError(t, err, "failed to write to config at "+configPath)
})
})

when("set", func() {
when("valid builder is provider", func() {
when("in local", func() {
var imageName = "some/image"

it("sets default builder", func() {
mockClient.EXPECT().InspectBuilder(imageName, true).Return(&pack.BuilderInfo{
Stack: "test.stack.id",
}, nil)

cmd.SetArgs([]string{imageName})
h.AssertNil(t, cmd.Execute())
h.AssertContains(t, outBuf.String(), fmt.Sprintf("Builder '%s' is now the default builder", imageName))

cfg, err := config.Read(configPath)
h.AssertNil(t, err)
h.AssertEq(t, cfg.DefaultBuilder, "some/image")
})

it("gives clear error if unable to write to config", func() {
h.AssertNil(t, ioutil.WriteFile(configPath, []byte("some-data"), 0001))
mockClient.EXPECT().InspectBuilder(imageName, true).Return(&pack.BuilderInfo{
Stack: "test.stack.id",
}, nil)
cmd = commands.ConfigDefaultBuilder(logger, config.Config{}, configPath, mockClient)
cmd.SetArgs([]string{imageName})
err := cmd.Execute()
h.AssertError(t, err, "failed to write to config at "+configPath)
})
})

when("in remote", func() {
it("sets default builder", func() {
imageName := "some/image"

localCall := mockClient.EXPECT().InspectBuilder(imageName, true).Return(nil, nil)

mockClient.EXPECT().InspectBuilder(imageName, false).Return(&pack.BuilderInfo{
Stack: "test.stack.id",
}, nil).After(localCall)

cmd.SetArgs([]string{imageName})
h.AssertNil(t, cmd.Execute())
h.AssertContains(t, outBuf.String(), fmt.Sprintf("Builder '%s' is now the default builder", imageName))
})

it("gives clear error if unable to inspect remote image", func() {
imageName := "some/image"

localCall := mockClient.EXPECT().InspectBuilder(imageName, true).Return(nil, nil)

mockClient.EXPECT().InspectBuilder(imageName, false).Return(&pack.BuilderInfo{
Stack: "test.stack.id",
}, pack.SoftError{}).After(localCall)

cmd.SetArgs([]string{imageName})
err := cmd.Execute()
h.AssertError(t, err, fmt.Sprintf("failed to inspect remote image %s", style.Symbol(imageName)))
})
})
})

when("invalid builder is provided", func() {
it("error is presented", func() {
imageName := "nonbuilder/image"

mockClient.EXPECT().InspectBuilder(imageName, true).Return(
nil,
fmt.Errorf("failed to inspect image %s", imageName))

cmd.SetArgs([]string{imageName})

h.AssertNotNil(t, cmd.Execute())
h.AssertContains(t, outBuf.String(), fmt.Sprintf("validating that builder %s exists", style.Symbol("nonbuilder/image")))
})
})

when("non-existent builder is provided", func() {
it("error is present", func() {
imageName := "nonexisting/image"

localCall := mockClient.EXPECT().InspectBuilder(imageName, true).Return(
nil,
nil)

mockClient.EXPECT().InspectBuilder(imageName, false).Return(
nil,
nil).After(localCall)

cmd.SetArgs([]string{imageName})

h.AssertNotNil(t, cmd.Execute())
h.AssertContains(t, outBuf.String(), "builder 'nonexisting/image' not found")
})
})
})
})
}
Loading

0 comments on commit 6033196

Please sign in to comment.