Add builder inspect and buildpack inspect command, and hide inspect-builder and inspect-buildpack commands

* Rename inspect-image to inspect, and add alias for inspect-image
* Change behavior for inspect-buildpack to look at default registry name in config, rather than deprecated default registry

Signed-off-by: David Freilich <dfreilich@vmware.com>
This commit is contained in:
David Freilich 2021-02-10 11:53:01 +02:00
parent 124f17d1b7
commit e313c9507d
15 changed files with 1230 additions and 282 deletions

View File

@ -76,13 +76,12 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
rootCmd.AddCommand(commands.NewBuilderCommand(logger, cfg, &packClient))
rootCmd.AddCommand(commands.NewBuildpackCommand(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.NewStackCommand(logger))
rootCmd.AddCommand(commands.Rebase(logger, cfg, &packClient))
rootCmd.AddCommand(commands.InspectImage(logger, imagewriter.NewFactory(), cfg, &packClient))
rootCmd.AddCommand(commands.InspectBuildpack(logger, &cfg, &packClient))
rootCmd.AddCommand(commands.InspectBuildpack(logger, cfg, &packClient))
rootCmd.AddCommand(commands.InspectBuilder(logger, cfg, &packClient, builderwriter.NewFactory()))
rootCmd.AddCommand(commands.SetDefaultBuilder(logger, cfg, cfgPath, &packClient))
rootCmd.AddCommand(commands.SetRunImagesMirrors(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.SuggestBuilders(logger, &packClient))
@ -92,15 +91,12 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, &packClient))
rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, &packClient, buildpackage.NewConfigReader()))
rootCmd.AddCommand(commands.SuggestStacks(logger))
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.SetDefaultRegistry(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.RemoveRegistry(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.YankBuildpack(logger, cfg, &packClient))
}
rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.ListBuildpackRegistries(logger, cfg))
rootCmd.AddCommand(commands.RegisterBuildpack(logger, cfg, &packClient))
rootCmd.AddCommand(commands.SetDefaultRegistry(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.RemoveRegistry(logger, cfg, cfgPath))
rootCmd.AddCommand(commands.YankBuildpack(logger, cfg, &packClient))
packHome, err := config.PackHome()
if err != nil {

View File

@ -3,6 +3,7 @@ package commands
import (
"github.com/spf13/cobra"
builderwriter "github.com/buildpacks/pack/internal/builder/writer"
"github.com/buildpacks/pack/internal/config"
"github.com/buildpacks/pack/logging"
)
@ -16,6 +17,7 @@ func NewBuilderCommand(logger logging.Logger, cfg config.Config, client PackClie
}
cmd.AddCommand(BuilderCreate(logger, cfg, client))
cmd.AddCommand(BuilderInspect(logger, cfg, client, builderwriter.NewFactory()))
cmd.AddCommand(BuilderSuggest(logger, client))
AddHelpFlag(cmd, "builder")
return cmd

View File

@ -0,0 +1,78 @@
package commands
import (
"github.com/spf13/cobra"
"github.com/buildpacks/pack"
"github.com/buildpacks/pack/builder"
"github.com/buildpacks/pack/internal/builder/writer"
"github.com/buildpacks/pack/internal/config"
"github.com/buildpacks/pack/logging"
)
type BuilderInspector interface {
InspectBuilder(name string, daemon bool, modifiers ...pack.BuilderInspectionModifier) (*pack.BuilderInfo, error)
}
type BuilderInspectFlags struct {
Depth int
OutputFormat string
}
func BuilderInspect(logger logging.Logger,
cfg config.Config,
inspector BuilderInspector,
writerFactory writer.BuilderWriterFactory,
) *cobra.Command {
var flags BuilderInspectFlags
cmd := &cobra.Command{
Use: "inspect <builder-image-name>",
Args: cobra.MaximumNArgs(1),
Aliases: []string{"inspect-builder"},
Short: "Show information about a builder",
Example: "pack builder inspect cnbs/sample-builder:bionic",
Long: "MENTION THAT NO ARG INSPECTS DEFAULT IF SET",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
imageName := cfg.DefaultBuilder
if len(args) >= 1 {
imageName = args[0]
}
if imageName == "" {
suggestSettingBuilder(logger, inspector)
return pack.NewSoftError()
}
return inspectBuilder(logger, imageName, flags, cfg, inspector, writerFactory)
}),
}
cmd.Flags().IntVarP(&flags.Depth, "depth", "d", builder.OrderDetectionMaxDepth, "Max depth to display for Detection Order.\nOmission of this flag or values < 0 will display the entire tree.")
cmd.Flags().StringVarP(&flags.OutputFormat, "output", "o", "human-readable", "Output format to display builder detail (json, yaml, toml, human-readable).\nOmission of this flag will display as human-readable.")
AddHelpFlag(cmd, "inspect")
return cmd
}
func inspectBuilder(
logger logging.Logger,
imageName string,
flags BuilderInspectFlags,
cfg config.Config,
inspector BuilderInspector,
writerFactory writer.BuilderWriterFactory,
) error {
builderInfo := writer.SharedBuilderInfo{
Name: imageName,
IsDefault: imageName == cfg.DefaultBuilder,
Trusted: isTrustedBuilder(cfg, imageName),
}
localInfo, localErr := inspector.InspectBuilder(imageName, true, pack.WithDetectionOrderDepth(flags.Depth))
remoteInfo, remoteErr := inspector.InspectBuilder(imageName, false, pack.WithDetectionOrderDepth(flags.Depth))
writer, err := writerFactory.Writer(flags.OutputFormat)
if err != nil {
return err
}
return writer.Print(logger, cfg.RunImages, localInfo, remoteInfo, localErr, remoteErr, builderInfo)
}

View File

@ -0,0 +1,380 @@
package commands_test
import (
"bytes"
"errors"
"regexp"
"testing"
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/pack/internal/builder"
"github.com/buildpacks/pack/internal/builder/writer"
"github.com/heroku/color"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/pack"
"github.com/buildpacks/pack/internal/commands"
"github.com/buildpacks/pack/internal/commands/fakes"
"github.com/buildpacks/pack/internal/config"
ilogging "github.com/buildpacks/pack/internal/logging"
"github.com/buildpacks/pack/logging"
h "github.com/buildpacks/pack/testhelpers"
)
var (
minimalLifecycleDescriptor = builder.LifecycleDescriptor{
Info: builder.LifecycleInfo{Version: builder.VersionMustParse("3.4")},
API: builder.LifecycleAPI{
BuildpackVersion: api.MustParse("1.2"),
PlatformVersion: api.MustParse("2.3"),
},
}
expectedLocalRunImages = []config.RunImage{
{Image: "some/run-image", Mirrors: []string{"first/local", "second/local"}},
}
expectedLocalInfo = &pack.BuilderInfo{
Description: "test-local-builder",
Stack: "local-stack",
RunImage: "local/image",
Lifecycle: minimalLifecycleDescriptor,
}
expectedRemoteInfo = &pack.BuilderInfo{
Description: "test-remote-builder",
Stack: "remote-stack",
RunImage: "remote/image",
Lifecycle: minimalLifecycleDescriptor,
}
expectedLocalDisplay = "Sample output for local builder"
expectedRemoteDisplay = "Sample output for remote builder"
expectedBuilderInfo = writer.SharedBuilderInfo{
Name: "default/builder",
Trusted: false,
IsDefault: true,
}
)
func TestBuilderInspectCommand(t *testing.T) {
color.Disable(true)
defer color.Disable(false)
spec.Run(t, "BuilderInspectCommand", testBuilderInspectCommand, spec.Parallel(), spec.Report(report.Terminal{}))
}
func testBuilderInspectCommand(t *testing.T, when spec.G, it spec.S) {
var (
logger logging.Logger
outBuf bytes.Buffer
cfg config.Config
)
it.Before(func() {
cfg = config.Config{
DefaultBuilder: "default/builder",
RunImages: expectedLocalRunImages,
}
logger = ilogging.NewLogWithWriters(&outBuf, &outBuf)
})
when("BuilderInspect", func() {
var (
assert = h.NewAssertionManager(t)
)
it("passes output of local and remote builders to correct writer", func() {
builderInspector := newDefaultBuilderInspector()
builderWriter := newDefaultBuilderWriter()
builderWriterFactory := newWriterFactory(returnsForWriter(builderWriter))
command := commands.BuilderInspect(logger, cfg, builderInspector, builderWriterFactory)
command.SetArgs([]string{})
err := command.Execute()
assert.Nil(err)
assert.Equal(builderWriter.ReceivedInfoForLocal, expectedLocalInfo)
assert.Equal(builderWriter.ReceivedInfoForRemote, expectedRemoteInfo)
assert.Equal(builderWriter.ReceivedBuilderInfo, expectedBuilderInfo)
assert.Equal(builderWriter.ReceivedLocalRunImages, expectedLocalRunImages)
assert.Equal(builderWriterFactory.ReceivedForKind, "human-readable")
assert.Equal(builderInspector.ReceivedForLocalName, "default/builder")
assert.Equal(builderInspector.ReceivedForRemoteName, "default/builder")
assert.ContainsF(outBuf.String(), "LOCAL:\n%s", expectedLocalDisplay)
assert.ContainsF(outBuf.String(), "REMOTE:\n%s", expectedRemoteDisplay)
})
when("image name is provided as first arg", func() {
it("passes that image name to the inspector", func() {
builderInspector := newDefaultBuilderInspector()
writer := newDefaultBuilderWriter()
command := commands.BuilderInspect(logger, cfg, builderInspector, newWriterFactory(returnsForWriter(writer)))
command.SetArgs([]string{"some/image"})
err := command.Execute()
assert.Nil(err)
assert.Equal(builderInspector.ReceivedForLocalName, "some/image")
assert.Equal(builderInspector.ReceivedForRemoteName, "some/image")
assert.Equal(writer.ReceivedBuilderInfo.IsDefault, false)
})
})
when("depth flag is provided", func() {
it("passes a modifier to the builder inspector", func() {
builderInspector := newDefaultBuilderInspector()
command := commands.BuilderInspect(logger, cfg, builderInspector, newDefaultWriterFactory())
command.SetArgs([]string{"--depth", "5"})
err := command.Execute()
assert.Nil(err)
assert.Equal(builderInspector.CalculatedConfigForLocal.OrderDetectionDepth, 5)
assert.Equal(builderInspector.CalculatedConfigForRemote.OrderDetectionDepth, 5)
})
})
when("output type is set to json", func() {
it("passes json to the writer factory", func() {
writerFactory := newDefaultWriterFactory()
command := commands.BuilderInspect(logger, cfg, newDefaultBuilderInspector(), writerFactory)
command.SetArgs([]string{"--output", "json"})
err := command.Execute()
assert.Nil(err)
assert.Equal(writerFactory.ReceivedForKind, "json")
})
})
when("output type is set to toml using the shorthand flag", func() {
it("passes toml to the writer factory", func() {
writerFactory := newDefaultWriterFactory()
command := commands.BuilderInspect(logger, cfg, newDefaultBuilderInspector(), writerFactory)
command.SetArgs([]string{"-o", "toml"})
err := command.Execute()
assert.Nil(err)
assert.Equal(writerFactory.ReceivedForKind, "toml")
})
})
when("builder inspector returns an error for local builder", func() {
it("passes that error to the writer to handle appropriately", func() {
baseError := errors.New("couldn't inspect local")
builderInspector := newBuilderInspector(errorsForLocal(baseError))
builderWriter := newDefaultBuilderWriter()
builderWriterFactory := newWriterFactory(returnsForWriter(builderWriter))
command := commands.BuilderInspect(logger, cfg, builderInspector, builderWriterFactory)
command.SetArgs([]string{})
err := command.Execute()
assert.Nil(err)
assert.ErrorWithMessage(builderWriter.ReceivedErrorForLocal, "couldn't inspect local")
})
})
when("builder inspector returns an error remote builder", func() {
it("passes that error to the writer to handle appropriately", func() {
baseError := errors.New("couldn't inspect remote")
builderInspector := newBuilderInspector(errorsForRemote(baseError))
builderWriter := newDefaultBuilderWriter()
builderWriterFactory := newWriterFactory(returnsForWriter(builderWriter))
command := commands.BuilderInspect(logger, cfg, builderInspector, builderWriterFactory)
command.SetArgs([]string{})
err := command.Execute()
assert.Nil(err)
assert.ErrorWithMessage(builderWriter.ReceivedErrorForRemote, "couldn't inspect remote")
})
})
when("image is trusted", func() {
it("passes builder info with trusted true to the writer's `Print` method", func() {
cfg.TrustedBuilders = []config.TrustedBuilder{
{Name: "trusted/builder"},
}
writer := newDefaultBuilderWriter()
command := commands.BuilderInspect(
logger,
cfg,
newDefaultBuilderInspector(),
newWriterFactory(returnsForWriter(writer)),
)
command.SetArgs([]string{"trusted/builder"})
err := command.Execute()
assert.Nil(err)
assert.Equal(writer.ReceivedBuilderInfo.Trusted, true)
})
})
when("default builder is configured and is the same as specified by the command", func() {
it("passes builder info with isDefault true to the writer's `Print` method", func() {
cfg.DefaultBuilder = "the/default-builder"
writer := newDefaultBuilderWriter()
command := commands.BuilderInspect(
logger,
cfg,
newDefaultBuilderInspector(),
newWriterFactory(returnsForWriter(writer)),
)
command.SetArgs([]string{"the/default-builder"})
err := command.Execute()
assert.Nil(err)
assert.Equal(writer.ReceivedBuilderInfo.IsDefault, true)
})
})
when("default builder is empty and no builder is specified in command args", func() {
it("suggests builders and returns a soft error", func() {
cfg.DefaultBuilder = ""
command := commands.BuilderInspect(logger, cfg, newDefaultBuilderInspector(), newDefaultWriterFactory())
command.SetArgs([]string{})
err := command.Execute()
assert.Error(err)
if !errors.Is(err, pack.SoftError{}) {
t.Fatalf("expect a pack.SoftError, got: %s", err)
}
assert.Contains(outBuf.String(), `Please select a default builder with:
pack config default-builder <builder-image>`)
assert.Matches(outBuf.String(), regexp.MustCompile(`Paketo Buildpacks:\s+'paketobuildpacks/builder:base'`))
assert.Matches(outBuf.String(), regexp.MustCompile(`Paketo Buildpacks:\s+'paketobuildpacks/builder:full'`))
assert.Matches(outBuf.String(), regexp.MustCompile(`Heroku:\s+'heroku/buildpacks:18'`))
assert.Matches(outBuf.String(), regexp.MustCompile(`Heroku:\s+'heroku/buildpacks:20'`))
})
})
when("print returns an error", func() {
it("returns that error", func() {
baseError := errors.New("couldn't write builder")
builderWriter := newBuilderWriter(errorsForPrint(baseError))
command := commands.BuilderInspect(
logger,
cfg,
newDefaultBuilderInspector(),
newWriterFactory(returnsForWriter(builderWriter)),
)
command.SetArgs([]string{})
err := command.Execute()
assert.ErrorWithMessage(err, "couldn't write builder")
})
})
when("writer factory returns an error", func() {
it("returns that error", func() {
baseError := errors.New("invalid output format")
writerFactory := newWriterFactory(errorsForWriter(baseError))
command := commands.BuilderInspect(logger, cfg, newDefaultBuilderInspector(), writerFactory)
command.SetArgs([]string{})
err := command.Execute()
assert.ErrorWithMessage(err, "invalid output format")
})
})
})
}
func newDefaultBuilderInspector() *fakes.FakeBuilderInspector {
return &fakes.FakeBuilderInspector{
InfoForLocal: expectedLocalInfo,
InfoForRemote: expectedRemoteInfo,
}
}
func newDefaultBuilderWriter() *fakes.FakeBuilderWriter {
return &fakes.FakeBuilderWriter{
PrintForLocal: expectedLocalDisplay,
PrintForRemote: expectedRemoteDisplay,
}
}
func newDefaultWriterFactory() *fakes.FakeBuilderWriterFactory {
return &fakes.FakeBuilderWriterFactory{
ReturnForWriter: newDefaultBuilderWriter(),
}
}
type BuilderWriterModifier func(w *fakes.FakeBuilderWriter)
func errorsForPrint(err error) BuilderWriterModifier {
return func(w *fakes.FakeBuilderWriter) {
w.ErrorForPrint = err
}
}
func newBuilderWriter(modifiers ...BuilderWriterModifier) *fakes.FakeBuilderWriter {
w := newDefaultBuilderWriter()
for _, mod := range modifiers {
mod(w)
}
return w
}
type WriterFactoryModifier func(f *fakes.FakeBuilderWriterFactory)
func returnsForWriter(writer writer.BuilderWriter) WriterFactoryModifier {
return func(f *fakes.FakeBuilderWriterFactory) {
f.ReturnForWriter = writer
}
}
func errorsForWriter(err error) WriterFactoryModifier {
return func(f *fakes.FakeBuilderWriterFactory) {
f.ErrorForWriter = err
}
}
func newWriterFactory(modifiers ...WriterFactoryModifier) *fakes.FakeBuilderWriterFactory {
f := newDefaultWriterFactory()
for _, mod := range modifiers {
mod(f)
}
return f
}
type BuilderInspectorModifier func(i *fakes.FakeBuilderInspector)
func errorsForLocal(err error) BuilderInspectorModifier {
return func(i *fakes.FakeBuilderInspector) {
i.ErrorForLocal = err
}
}
func errorsForRemote(err error) BuilderInspectorModifier {
return func(i *fakes.FakeBuilderInspector) {
i.ErrorForRemote = err
}
}
func newBuilderInspector(modifiers ...BuilderInspectorModifier) *fakes.FakeBuilderInspector {
i := newDefaultBuilderInspector()
for _, mod := range modifiers {
mod(i)
}
return i
}

View File

@ -43,7 +43,7 @@ func testBuilderCommand(t *testing.T, when spec.G, it spec.S) {
output := outBuf.String()
h.AssertContains(t, output, "Interact with builders")
h.AssertContains(t, output, "Usage:")
for _, command := range []string{"create", "suggest"} {
for _, command := range []string{"create", "suggest", "inspect"} {
h.AssertContains(t, output, command)
h.AssertNotContains(t, output, command+"-builder")
}

View File

@ -15,6 +15,7 @@ func NewBuildpackCommand(logger logging.Logger, cfg config.Config, client PackCl
RunE: nil,
}
cmd.AddCommand(BuildpackInspect(logger, cfg, client))
cmd.AddCommand(BuildpackPackage(logger, cfg, client, packageConfigReader))
cmd.AddCommand(BuildpackNew(logger, client))
cmd.AddCommand(BuildpackPull(logger, cfg, client))

View File

@ -0,0 +1,67 @@
package commands
import (
"fmt"
"github.com/spf13/cobra"
"github.com/buildpacks/pack"
"github.com/buildpacks/pack/internal/config"
"github.com/buildpacks/pack/internal/style"
"github.com/buildpacks/pack/logging"
)
type BuildpackInspectFlags struct {
Depth int
Registry string
Verbose bool
}
func BuildpackInspect(logger logging.Logger, cfg config.Config, client PackClient) *cobra.Command {
var flags BuildpackInspectFlags
cmd := &cobra.Command{
Use: "inspect <image-name>",
Args: cobra.MaximumNArgs(1),
Short: "Show information about a buildpack",
Example: "pack buildpack inspect cnbs/sample-package:hello-universe",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
buildpackName := args[0]
registry := flags.Registry
if registry == "" {
registry = cfg.DefaultRegistryName
}
return buildpackInspect(logger, buildpackName, registry, flags, cfg, client)
}),
}
cmd.Flags().IntVarP(&flags.Depth, "depth", "d", -1, "Max depth to display for Detection Order.\nOmission of this flag or values < 0 will display the entire tree.")
cmd.Flags().StringVarP(&flags.Registry, "registry", "r", "", "buildpack registry that may be searched")
cmd.Flags().BoolVarP(&flags.Verbose, "verbose", "v", false, "show more output")
AddHelpFlag(cmd, "inspect")
return cmd
}
func buildpackInspect(logger logging.Logger, buildpackName, registryName string, flags BuildpackInspectFlags, cfg config.Config, client PackClient) error {
logger.Infof("Inspecting buildpack: %s\n", style.Symbol(buildpackName))
inspectedBuildpacksOutput, err := inspectAllBuildpacks(
client,
flags,
pack.InspectBuildpackOptions{
BuildpackName: buildpackName,
Daemon: true,
Registry: registryName,
},
pack.InspectBuildpackOptions{
BuildpackName: buildpackName,
Daemon: false,
Registry: registryName,
})
if err != nil {
return fmt.Errorf("error writing buildpack output: %q", err)
}
logger.Info(inspectedBuildpacksOutput)
return nil
}

View File

@ -0,0 +1,672 @@
package commands_test
import (
"bytes"
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/buildpacks/pack/internal/image"
"github.com/buildpacks/lifecycle/api"
"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/buildpack"
"github.com/buildpacks/pack/internal/buildpackage"
"github.com/buildpacks/pack/internal/commands"
"github.com/buildpacks/pack/internal/commands/testmocks"
"github.com/buildpacks/pack/internal/config"
"github.com/buildpacks/pack/internal/dist"
ilogging "github.com/buildpacks/pack/internal/logging"
"github.com/buildpacks/pack/logging"
h "github.com/buildpacks/pack/testhelpers"
)
const complexOutputSection = `Stacks:
ID: io.buildpacks.stacks.first-stack
Mixins:
(omitted)
ID: io.buildpacks.stacks.second-stack
Mixins:
(omitted)
Buildpacks:
ID VERSION HOMEPAGE
some/first-inner-buildpack 1.0.0 first-inner-buildpack-homepage
some/second-inner-buildpack 2.0.0 second-inner-buildpack-homepage
some/third-inner-buildpack 3.0.0 third-inner-buildpack-homepage
some/top-buildpack 0.0.1 top-buildpack-homepage
Detection Order:
Group #1:
some/top-buildpack@0.0.1
Group #1:
some/first-inner-buildpack@1.0.0
Group #1:
some/first-inner-buildpack@1.0.0 [cyclic]
some/third-inner-buildpack@3.0.0
Group #2:
some/third-inner-buildpack@3.0.0
some/second-inner-buildpack@2.0.0
Group #2:
some/first-inner-buildpack@1.0.0
Group #1:
some/first-inner-buildpack@1.0.0 [cyclic]
some/third-inner-buildpack@3.0.0
Group #2:
some/third-inner-buildpack@3.0.0`
const simpleOutputSection = `Stacks:
ID: io.buildpacks.stacks.first-stack
Mixins:
(omitted)
ID: io.buildpacks.stacks.second-stack
Mixins:
(omitted)
Buildpacks:
ID VERSION HOMEPAGE
some/single-buildpack 0.0.1 single-buildpack-homepage
Detection Order:
Group #1:
some/single-buildpack@0.0.1`
const inspectOutputTemplate = `Inspecting buildpack: '%s'
%s
%s
`
const depthOutputSection = `
Detection Order:
Group #1:
some/top-buildpack@0.0.1
Group #1:
some/first-inner-buildpack@1.0.0
some/second-inner-buildpack@2.0.0
Group #2:
some/first-inner-buildpack@1.0.0`
const simpleMixinOutputSection = `
ID: io.buildpacks.stacks.first-stack
Mixins:
mixin1
mixin2
build:mixin3
build:mixin4
ID: io.buildpacks.stacks.second-stack
Mixins:
mixin1
mixin2`
func TestBuildpackInspectCommand(t *testing.T) {
color.Disable(true)
defer color.Disable(false)
spec.Run(t, "BuildpackInspectCommand", testBuildpackInspectCommand, spec.Sequential(), spec.Report(report.Terminal{}))
}
func testBuildpackInspectCommand(t *testing.T, when spec.G, it spec.S) {
apiVersion, err := api.NewVersion("0.2")
if err != nil {
t.Fail()
}
var (
command *cobra.Command
logger logging.Logger
outBuf bytes.Buffer
mockController *gomock.Controller
mockClient *testmocks.MockPackClient
cfg config.Config
complexInfo *pack.BuildpackInfo
simpleInfo *pack.BuildpackInfo
assert = h.NewAssertionManager(t)
)
it.Before(func() {
mockController = gomock.NewController(t)
mockClient = testmocks.NewMockPackClient(mockController)
logger = ilogging.NewLogWithWriters(&outBuf, &outBuf)
cfg = config.Config{
DefaultRegistryName: "default-registry",
}
complexInfo = &pack.BuildpackInfo{
BuildpackMetadata: buildpackage.Metadata{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/top-buildpack",
Version: "0.0.1",
Homepage: "top-buildpack-homepage",
},
Stacks: []dist.Stack{
{ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}},
{ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}},
},
},
Buildpacks: []dist.BuildpackInfo{
{
ID: "some/first-inner-buildpack",
Version: "1.0.0",
Homepage: "first-inner-buildpack-homepage",
},
{
ID: "some/second-inner-buildpack",
Version: "2.0.0",
Homepage: "second-inner-buildpack-homepage",
},
{
ID: "some/third-inner-buildpack",
Version: "3.0.0",
Homepage: "third-inner-buildpack-homepage",
},
{
ID: "some/top-buildpack",
Version: "0.0.1",
Homepage: "top-buildpack-homepage",
},
},
Order: dist.Order{
{
Group: []dist.BuildpackRef{
{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/top-buildpack",
Version: "0.0.1",
Homepage: "top-buildpack-homepage",
},
Optional: false,
},
},
},
},
BuildpackLayers: dist.BuildpackLayers{
"some/first-inner-buildpack": {
"1.0.0": {
API: apiVersion,
Stacks: []dist.Stack{
{ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}},
{ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}},
},
Order: dist.Order{
{
Group: []dist.BuildpackRef{
{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/first-inner-buildpack",
Version: "1.0.0",
},
Optional: false,
},
{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/third-inner-buildpack",
Version: "3.0.0",
},
Optional: false,
},
},
},
{
Group: []dist.BuildpackRef{
{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/third-inner-buildpack",
Version: "3.0.0",
},
Optional: false,
},
},
},
},
LayerDiffID: "sha256:first-inner-buildpack-diff-id",
Homepage: "first-inner-buildpack-homepage",
},
},
"some/second-inner-buildpack": {
"2.0.0": {
API: apiVersion,
Stacks: []dist.Stack{
{ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}},
{ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}},
},
LayerDiffID: "sha256:second-inner-buildpack-diff-id",
Homepage: "second-inner-buildpack-homepage",
},
},
"some/third-inner-buildpack": {
"3.0.0": {
API: apiVersion,
Stacks: []dist.Stack{
{ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}},
{ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}},
},
LayerDiffID: "sha256:third-inner-buildpack-diff-id",
Homepage: "third-inner-buildpack-homepage",
},
},
"some/top-buildpack": {
"0.0.1": {
API: apiVersion,
Order: dist.Order{
{
Group: []dist.BuildpackRef{
{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/first-inner-buildpack",
Version: "1.0.0",
},
Optional: false,
},
{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/second-inner-buildpack",
Version: "2.0.0",
},
Optional: false,
},
},
},
{
Group: []dist.BuildpackRef{
{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/first-inner-buildpack",
Version: "1.0.0",
},
Optional: false,
},
},
},
},
LayerDiffID: "sha256:top-buildpack-diff-id",
Homepage: "top-buildpack-homepage",
},
},
},
}
simpleInfo = &pack.BuildpackInfo{
BuildpackMetadata: buildpackage.Metadata{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/single-buildpack",
Version: "0.0.1",
Homepage: "single-homepage-homepace",
},
Stacks: []dist.Stack{
{ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}},
{ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}},
},
},
Buildpacks: []dist.BuildpackInfo{
{
ID: "some/single-buildpack",
Version: "0.0.1",
Homepage: "single-buildpack-homepage",
},
},
Order: dist.Order{
{
Group: []dist.BuildpackRef{
{
BuildpackInfo: dist.BuildpackInfo{
ID: "some/single-buildpack",
Version: "0.0.1",
Homepage: "single-buildpack-homepage",
},
Optional: false,
},
},
},
},
BuildpackLayers: dist.BuildpackLayers{
"some/single-buildpack": {
"0.0.1": {
API: apiVersion,
Stacks: []dist.Stack{
{ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}},
{ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}},
},
LayerDiffID: "sha256:single-buildpack-diff-id",
Homepage: "single-buildpack-homepage",
},
},
},
}
command = commands.BuildpackInspect(logger, cfg, mockClient)
})
when("BuildpackInspect", func() {
when("inspecting an image", func() {
when("both remote and local image are present", func() {
it.Before(func() {
complexInfo.Location = buildpack.PackageLocator
simpleInfo.Location = buildpack.PackageLocator
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "test/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(complexInfo, nil)
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "test/buildpack",
Daemon: false,
Registry: "default-registry",
}).Return(simpleInfo, nil)
})
it("succeeds", func() {
command.SetArgs([]string{"test/buildpack"})
assert.Nil(command.Execute())
localOutputSection := fmt.Sprintf(inspectOutputTemplate,
"test/buildpack",
"LOCAL IMAGE:",
complexOutputSection)
remoteOutputSection := fmt.Sprintf("%s\n\n%s",
"REMOTE IMAGE:",
simpleOutputSection)
assert.AssertTrimmedContains(outBuf.String(), localOutputSection)
assert.AssertTrimmedContains(outBuf.String(), remoteOutputSection)
})
})
when("only a local image is present", func() {
it.Before(func() {
complexInfo.Location = buildpack.PackageLocator
simpleInfo.Location = buildpack.PackageLocator
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "only-local-test/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(complexInfo, nil)
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "only-local-test/buildpack",
Daemon: false,
Registry: "default-registry",
}).Return(nil, errors.Wrap(image.ErrNotFound, "remote image not found!"))
})
it("displays output for local image", func() {
command.SetArgs([]string{"only-local-test/buildpack"})
assert.Nil(command.Execute())
expectedOutput := fmt.Sprintf(inspectOutputTemplate,
"only-local-test/buildpack",
"LOCAL IMAGE:",
complexOutputSection)
assert.AssertTrimmedContains(outBuf.String(), expectedOutput)
})
})
when("only a remote image is present", func() {
it.Before(func() {
complexInfo.Location = buildpack.PackageLocator
simpleInfo.Location = buildpack.PackageLocator
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "only-remote-test/buildpack",
Daemon: false,
Registry: "default-registry",
}).Return(complexInfo, nil)
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "only-remote-test/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(nil, errors.Wrap(image.ErrNotFound, "remote image not found!"))
})
it("displays output for remote image", func() {
command.SetArgs([]string{"only-remote-test/buildpack"})
assert.Nil(command.Execute())
expectedOutput := fmt.Sprintf(inspectOutputTemplate,
"only-remote-test/buildpack",
"REMOTE IMAGE:",
complexOutputSection)
assert.AssertTrimmedContains(outBuf.String(), expectedOutput)
})
})
})
when("inspecting a buildpack uri", func() {
it.Before(func() {
simpleInfo.Location = buildpack.URILocator
})
when("uri is a local path", func() {
it.Before(func() {
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "/path/to/test/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(simpleInfo, nil)
})
it("succeeds", func() {
command.SetArgs([]string{"/path/to/test/buildpack"})
assert.Nil(command.Execute())
expectedOutput := fmt.Sprintf(inspectOutputTemplate,
"/path/to/test/buildpack",
"LOCAL ARCHIVE:",
simpleOutputSection)
assert.TrimmedEq(outBuf.String(), expectedOutput)
})
})
when("uri is a http or https location", func() {
it.Before(func() {
simpleInfo.Location = buildpack.URILocator
})
when("uri is a local path", func() {
it.Before(func() {
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "https://path/to/test/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(simpleInfo, nil)
})
it("succeeds", func() {
command.SetArgs([]string{"https://path/to/test/buildpack"})
assert.Nil(command.Execute())
expectedOutput := fmt.Sprintf(inspectOutputTemplate,
"https://path/to/test/buildpack",
"REMOTE ARCHIVE:",
simpleOutputSection)
assert.TrimmedEq(outBuf.String(), expectedOutput)
})
})
})
})
when("inspecting a buildpack on the registry", func() {
it.Before(func() {
simpleInfo.Location = buildpack.RegistryLocator
})
when("using the default registry", func() {
it.Before(func() {
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "urn:cnb:registry:test/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(simpleInfo, nil)
})
it("succeeds", func() {
command.SetArgs([]string{"urn:cnb:registry:test/buildpack"})
assert.Nil(command.Execute())
expectedOutput := fmt.Sprintf(inspectOutputTemplate,
"urn:cnb:registry:test/buildpack",
"REGISTRY IMAGE:",
simpleOutputSection)
assert.TrimmedEq(outBuf.String(), expectedOutput)
})
})
when("using a user provided registry", func() {
it.Before(func() {
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "urn:cnb:registry:test/buildpack",
Daemon: true,
Registry: "some-registry",
}).Return(simpleInfo, nil)
})
it("succeeds", func() {
command.SetArgs([]string{"urn:cnb:registry:test/buildpack", "-r", "some-registry"})
assert.Nil(command.Execute())
expectedOutput := fmt.Sprintf(inspectOutputTemplate,
"urn:cnb:registry:test/buildpack",
"REGISTRY IMAGE:",
simpleOutputSection)
assert.TrimmedEq(outBuf.String(), expectedOutput)
})
})
})
when("a depth flag is passed", func() {
it.Before(func() {
complexInfo.Location = buildpack.URILocator
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "/other/path/to/test/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(complexInfo, nil)
})
it("displays detection order to specified depth", func() {
command.SetArgs([]string{"/other/path/to/test/buildpack", "-d", "2"})
assert.Nil(command.Execute())
assert.AssertTrimmedContains(outBuf.String(), depthOutputSection)
})
})
})
when("verbose flag is passed", func() {
it.Before(func() {
simpleInfo.Location = buildpack.URILocator
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "/another/path/to/test/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(simpleInfo, nil)
})
it("displays all mixins", func() {
command.SetArgs([]string{"/another/path/to/test/buildpack", "-v"})
assert.Nil(command.Execute())
assert.AssertTrimmedContains(outBuf.String(), simpleMixinOutputSection)
})
})
when("failure cases", func() {
when("unable to inspect buildpack image", func() {
it.Before(func() {
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "failure-case/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(&pack.BuildpackInfo{}, errors.Wrap(image.ErrNotFound, "unable to inspect local failure-case/buildpack"))
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "failure-case/buildpack",
Daemon: false,
Registry: "default-registry",
}).Return(&pack.BuildpackInfo{}, errors.Wrap(image.ErrNotFound, "unable to inspect remote failure-case/buildpack"))
})
it("errors", func() {
command.SetArgs([]string{"failure-case/buildpack"})
err := command.Execute()
assert.Error(err)
})
})
when("unable to inspect buildpack archive", func() {
it.Before(func() {
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "http://path/to/failure-case/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(&pack.BuildpackInfo{}, errors.New("error inspecting local archive"))
it("errors", func() {
command.SetArgs([]string{"http://path/to/failure-case/buildpack"})
err := command.Execute()
assert.Error(err)
assert.Contains(err.Error(), "error inspecting local archive")
})
})
})
when("unable to inspect both remote and local images", func() {
it.Before(func() {
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "image-failure-case/buildpack",
Daemon: true,
Registry: "default-registry",
}).Return(&pack.BuildpackInfo{}, errors.Wrap(image.ErrNotFound, "error inspecting local archive"))
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "image-failure-case/buildpack",
Daemon: false,
Registry: "default-registry",
}).Return(&pack.BuildpackInfo{}, errors.Wrap(image.ErrNotFound, "error inspecting remote archive"))
})
it("errors", func() {
command.SetArgs([]string{"image-failure-case/buildpack"})
err := command.Execute()
assert.Error(err)
assert.Contains(err.Error(), "error writing buildpack output: \"no buildpacks found\"")
})
})
when("unable to inspect buildpack on registry", func() {
it.Before(func() {
mockClient.EXPECT().InspectBuildpack(pack.InspectBuildpackOptions{
BuildpackName: "urn:cnb:registry:registry-failure/buildpack",
Daemon: true,
Registry: "some-registry",
}).Return(&pack.BuildpackInfo{}, errors.New("error inspecting registry image"))
})
it("errors", func() {
command.SetArgs([]string{"urn:cnb:registry:registry-failure/buildpack", "-r", "some-registry"})
err := command.Execute()
assert.Error(err)
assert.Contains(err.Error(), "error inspecting registry image")
})
})
})
}

View File

@ -44,7 +44,7 @@ func testBuildpackCommand(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, cmd.Execute())
output := outBuf.String()
h.AssertContains(t, output, "Interact with buildpacks")
for _, command := range []string{"Usage", "package", "register", "yank", "pull"} {
for _, command := range []string{"Usage", "package", "register", "yank", "pull", "inspect"} {
h.AssertContains(t, output, command)
}
})

View File

@ -11,25 +11,18 @@ import (
"github.com/buildpacks/pack/logging"
)
type BuilderInspector interface {
InspectBuilder(name string, daemon bool, modifiers ...pack.BuilderInspectionModifier) (*pack.BuilderInfo, error)
}
type InspectBuilderFlags struct {
Depth int
OutputFormat string
}
// Deprecated: Use builder inspect instead.
func InspectBuilder(
logger logging.Logger,
cfg config.Config,
inspector BuilderInspector,
writerFactory writer.BuilderWriterFactory,
) *cobra.Command {
var flags InspectBuilderFlags
var flags BuilderInspectFlags
cmd := &cobra.Command{
Use: "inspect-builder <builder-image-name>",
Args: cobra.MaximumNArgs(2),
Hidden: true,
Short: "Show information about a builder",
Example: "pack inspect-builder cnbs/sample-builder:bionic",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
@ -43,20 +36,7 @@ func InspectBuilder(
return pack.NewSoftError()
}
builderInfo := writer.SharedBuilderInfo{
Name: imageName,
IsDefault: imageName == cfg.DefaultBuilder,
Trusted: isTrustedBuilder(cfg, imageName),
}
localInfo, localErr := inspector.InspectBuilder(imageName, true, pack.WithDetectionOrderDepth(flags.Depth))
remoteInfo, remoteErr := inspector.InspectBuilder(imageName, false, pack.WithDetectionOrderDepth(flags.Depth))
writer, err := writerFactory.Writer(flags.OutputFormat)
if err != nil {
return err
}
return writer.Print(logger, cfg.RunImages, localInfo, remoteInfo, localErr, remoteErr, builderInfo)
return inspectBuilder(logger, imageName, flags, cfg, inspector, writerFactory)
}),
}
cmd.Flags().IntVarP(&flags.Depth, "depth", "d", builder.OrderDetectionMaxDepth, "Max depth to display for Detection Order.\nOmission of this flag or values < 0 will display the entire tree.")

View File

@ -6,61 +6,22 @@ import (
"regexp"
"testing"
"github.com/buildpacks/pack/internal/builder/writer"
"github.com/heroku/color"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/api"
"github.com/buildpacks/pack"
"github.com/buildpacks/pack/internal/builder"
"github.com/buildpacks/pack/internal/commands"
"github.com/buildpacks/pack/internal/commands/fakes"
"github.com/buildpacks/pack/internal/config"
ilogging "github.com/buildpacks/pack/internal/logging"
"github.com/buildpacks/pack/logging"
h "github.com/buildpacks/pack/testhelpers"
)
var (
minimalLifecycleDescriptor = builder.LifecycleDescriptor{
Info: builder.LifecycleInfo{Version: builder.VersionMustParse("3.4")},
API: builder.LifecycleAPI{
BuildpackVersion: api.MustParse("1.2"),
PlatformVersion: api.MustParse("2.3"),
},
}
expectedLocalRunImages = []config.RunImage{
{Image: "some/run-image", Mirrors: []string{"first/local", "second/local"}},
}
expectedLocalInfo = &pack.BuilderInfo{
Description: "test-local-builder",
Stack: "local-stack",
RunImage: "local/image",
Lifecycle: minimalLifecycleDescriptor,
}
expectedRemoteInfo = &pack.BuilderInfo{
Description: "test-remote-builder",
Stack: "remote-stack",
RunImage: "remote/image",
Lifecycle: minimalLifecycleDescriptor,
}
expectedLocalDisplay = "Sample output for local builder"
expectedRemoteDisplay = "Sample output for remote builder"
expectedBuilderInfo = writer.SharedBuilderInfo{
Name: "default/builder",
Trusted: false,
IsDefault: true,
}
)
func TestInspectBuilderCommand(t *testing.T) {
color.Disable(true)
defer color.Disable(false)
spec.Run(t, "Commands", testInspectBuilderCommand, spec.Parallel(), spec.Report(report.Terminal{}))
spec.Run(t, "InspectBuilderCommand", testInspectBuilderCommand, spec.Parallel(), spec.Report(report.Terminal{}))
}
func testInspectBuilderCommand(t *testing.T, when spec.G, it spec.S) {
@ -292,89 +253,3 @@ func testInspectBuilderCommand(t *testing.T, when spec.G, it spec.S) {
})
})
}
func newDefaultBuilderInspector() *fakes.FakeBuilderInspector {
return &fakes.FakeBuilderInspector{
InfoForLocal: expectedLocalInfo,
InfoForRemote: expectedRemoteInfo,
}
}
func newDefaultBuilderWriter() *fakes.FakeBuilderWriter {
return &fakes.FakeBuilderWriter{
PrintForLocal: expectedLocalDisplay,
PrintForRemote: expectedRemoteDisplay,
}
}
func newDefaultWriterFactory() *fakes.FakeBuilderWriterFactory {
return &fakes.FakeBuilderWriterFactory{
ReturnForWriter: newDefaultBuilderWriter(),
}
}
type BuilderWriterModifier func(w *fakes.FakeBuilderWriter)
func errorsForPrint(err error) BuilderWriterModifier {
return func(w *fakes.FakeBuilderWriter) {
w.ErrorForPrint = err
}
}
func newBuilderWriter(modifiers ...BuilderWriterModifier) *fakes.FakeBuilderWriter {
w := newDefaultBuilderWriter()
for _, mod := range modifiers {
mod(w)
}
return w
}
type WriterFactoryModifier func(f *fakes.FakeBuilderWriterFactory)
func returnsForWriter(writer writer.BuilderWriter) WriterFactoryModifier {
return func(f *fakes.FakeBuilderWriterFactory) {
f.ReturnForWriter = writer
}
}
func errorsForWriter(err error) WriterFactoryModifier {
return func(f *fakes.FakeBuilderWriterFactory) {
f.ErrorForWriter = err
}
}
func newWriterFactory(modifiers ...WriterFactoryModifier) *fakes.FakeBuilderWriterFactory {
f := newDefaultWriterFactory()
for _, mod := range modifiers {
mod(f)
}
return f
}
type BuilderInspectorModifier func(i *fakes.FakeBuilderInspector)
func errorsForLocal(err error) BuilderInspectorModifier {
return func(i *fakes.FakeBuilderInspector) {
i.ErrorForLocal = err
}
}
func errorsForRemote(err error) BuilderInspectorModifier {
return func(i *fakes.FakeBuilderInspector) {
i.ErrorForRemote = err
}
}
func newBuilderInspector(modifiers ...BuilderInspectorModifier) *fakes.FakeBuilderInspector {
i := newDefaultBuilderInspector()
for _, mod := range modifiers {
mod(i)
}
return i
}

View File

@ -22,7 +22,6 @@ import (
"github.com/buildpacks/pack/internal/buildpackage"
"github.com/buildpacks/pack/internal/config"
"github.com/buildpacks/pack/internal/style"
"github.com/buildpacks/pack/logging"
)
@ -66,17 +65,13 @@ const (
writerFlags = 0
)
type InspectBuildpackFlags struct {
Depth int
Registry string
Verbose bool
}
func InspectBuildpack(logger logging.Logger, cfg *config.Config, client PackClient) *cobra.Command {
var flags InspectBuildpackFlags
// Deprecated: Use buildpack inspect instead.
func InspectBuildpack(logger logging.Logger, cfg config.Config, client PackClient) *cobra.Command {
var flags BuildpackInspectFlags
cmd := &cobra.Command{
Use: "inspect-buildpack <image-name>",
Args: cobra.RangeArgs(1, 4),
Hidden: true,
Short: "Show information about a buildpack",
Example: "pack inspect-buildpack cnbs/sample-package:hello-universe",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
@ -87,27 +82,7 @@ func InspectBuildpack(logger logging.Logger, cfg *config.Config, client PackClie
registry = cfg.DefaultRegistry
}
logger.Infof("Inspecting buildpack: %s\n", style.Symbol(buildpackName))
inspectedBuildpacksOutput, err := inspectAllBuildpacks(
client,
flags,
pack.InspectBuildpackOptions{
BuildpackName: buildpackName,
Daemon: true,
Registry: registry,
},
pack.InspectBuildpackOptions{
BuildpackName: buildpackName,
Daemon: false,
Registry: registry,
})
if err != nil {
return fmt.Errorf("error writing buildpack output: %q", err)
}
logger.Info(inspectedBuildpacksOutput)
return nil
return buildpackInspect(logger, buildpackName, registry, flags, cfg, client)
}),
}
cmd.Flags().IntVarP(&flags.Depth, "depth", "d", -1, "Max depth to display for Detection Order.\nOmission of this flag or values < 0 will display the entire tree.")
@ -117,7 +92,7 @@ func InspectBuildpack(logger logging.Logger, cfg *config.Config, client PackClie
return cmd
}
func inspectAllBuildpacks(client PackClient, flags InspectBuildpackFlags, options ...pack.InspectBuildpackOptions) (string, error) {
func inspectAllBuildpacks(client PackClient, flags BuildpackInspectFlags, options ...pack.InspectBuildpackOptions) (string, error) {
buf := bytes.NewBuffer(nil)
skipCount := 0
for _, option := range options {
@ -151,7 +126,7 @@ func inspectAllBuildpacks(client PackClient, flags InspectBuildpackFlags, option
return buf.String(), nil
}
func inspectBuildpackOutput(info *pack.BuildpackInfo, prefix string, flags InspectBuildpackFlags) (output []byte, err error) {
func inspectBuildpackOutput(info *pack.BuildpackInfo, prefix string, flags BuildpackInspectFlags) (output []byte, err error) {
tpl := template.Must(template.New("inspect-buildpack").Parse(inspectBuildpackTemplate))
bpOutput, err := buildpacksOutput(info.Buildpacks)
if err != nil {

View File

@ -28,89 +28,10 @@ import (
h "github.com/buildpacks/pack/testhelpers"
)
const complexOutputSection = `Stacks:
ID: io.buildpacks.stacks.first-stack
Mixins:
(omitted)
ID: io.buildpacks.stacks.second-stack
Mixins:
(omitted)
Buildpacks:
ID VERSION HOMEPAGE
some/first-inner-buildpack 1.0.0 first-inner-buildpack-homepage
some/second-inner-buildpack 2.0.0 second-inner-buildpack-homepage
some/third-inner-buildpack 3.0.0 third-inner-buildpack-homepage
some/top-buildpack 0.0.1 top-buildpack-homepage
Detection Order:
Group #1:
some/top-buildpack@0.0.1
Group #1:
some/first-inner-buildpack@1.0.0
Group #1:
some/first-inner-buildpack@1.0.0 [cyclic]
some/third-inner-buildpack@3.0.0
Group #2:
some/third-inner-buildpack@3.0.0
some/second-inner-buildpack@2.0.0
Group #2:
some/first-inner-buildpack@1.0.0
Group #1:
some/first-inner-buildpack@1.0.0 [cyclic]
some/third-inner-buildpack@3.0.0
Group #2:
some/third-inner-buildpack@3.0.0`
const simpleOutputSection = `Stacks:
ID: io.buildpacks.stacks.first-stack
Mixins:
(omitted)
ID: io.buildpacks.stacks.second-stack
Mixins:
(omitted)
Buildpacks:
ID VERSION HOMEPAGE
some/single-buildpack 0.0.1 single-buildpack-homepage
Detection Order:
Group #1:
some/single-buildpack@0.0.1`
const inspectOutputTemplate = `Inspecting buildpack: '%s'
%s
%s
`
const depthOutputSection = `
Detection Order:
Group #1:
some/top-buildpack@0.0.1
Group #1:
some/first-inner-buildpack@1.0.0
some/second-inner-buildpack@2.0.0
Group #2:
some/first-inner-buildpack@1.0.0`
const simpleMixinOutputSection = `
ID: io.buildpacks.stacks.first-stack
Mixins:
mixin1
mixin2
build:mixin3
build:mixin4
ID: io.buildpacks.stacks.second-stack
Mixins:
mixin1
mixin2`
func TestInspectBuildpackCommand(t *testing.T) {
color.Disable(true)
defer color.Disable(false)
spec.Run(t, "Commands", testInspectBuildpackCommand, spec.Sequential(), spec.Report(report.Terminal{}))
spec.Run(t, "InspectBuildpackCommand", testInspectBuildpackCommand, spec.Sequential(), spec.Report(report.Terminal{}))
}
func testInspectBuildpackCommand(t *testing.T, when spec.G, it spec.S) {
@ -342,7 +263,7 @@ func testInspectBuildpackCommand(t *testing.T, when spec.G, it spec.S) {
},
}
command = commands.InspectBuildpack(logger, &cfg, mockClient)
command = commands.InspectBuildpack(logger, cfg, mockClient)
})
when("InpectBuildpack", func() {

View File

@ -29,10 +29,11 @@ func InspectImage(
) *cobra.Command {
var flags InspectImageFlags
cmd := &cobra.Command{
Use: "inspect-image <image-name>",
Use: "inspect <image-name>",
Args: cobra.ExactArgs(1),
Short: "Show information about a built image",
Example: "pack inspect-image buildpacksio/pack",
Aliases: []string{"inspect-image"},
Short: "Show information about a built app image",
Example: "pack inspect buildpacksio/pack",
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
img := args[0]
@ -55,7 +56,7 @@ func InspectImage(
return nil
}),
}
AddHelpFlag(cmd, "inspect-image")
AddHelpFlag(cmd, "inspect")
cmd.Flags().BoolVar(&flags.BOM, "bom", false, "print bill of materials")
cmd.Flags().StringVarP(&flags.OutputFormat, "output", "o", "human-readable", "Output format to display builder detail (json, yaml, toml, human-readable).\nOmission of this flag will display as human-readable.")
return cmd

View File

@ -113,7 +113,7 @@ func WriteSuggestedBuilder(logger logging.Logger, inspector BuilderInspector, bu
fmt.Fprintln(tw)
logging.Tip(logger, "Learn more about a specific builder with:")
logger.Info("\tpack inspect-builder <builder-image>")
logger.Info("\tpack builder inspect <builder-image>")
}
func getBuilderDescription(builder SuggestedBuilder, inspector BuilderInspector) string {