mirror of https://github.com/buildpacks/pack.git
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:
parent
124f17d1b7
commit
e313c9507d
20
cmd/cmd.go
20
cmd/cmd.go
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue