github.com/buildpack/pack@v0.5.0/commands/inspect_builder.go (about)

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"html/template"
     7  	"io"
     8  	"strings"
     9  	"text/tabwriter"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/spf13/cobra"
    13  
    14  	"github.com/buildpack/pack"
    15  	"github.com/buildpack/pack/api"
    16  	"github.com/buildpack/pack/builder"
    17  	"github.com/buildpack/pack/config"
    18  	"github.com/buildpack/pack/dist"
    19  	"github.com/buildpack/pack/logging"
    20  	"github.com/buildpack/pack/style"
    21  )
    22  
    23  func InspectBuilder(logger logging.Logger, cfg config.Config, client PackClient) *cobra.Command {
    24  	cmd := &cobra.Command{
    25  		Use:   "inspect-builder <builder-image-name>",
    26  		Short: "Show information about a builder",
    27  		Args:  cobra.MaximumNArgs(1),
    28  		RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
    29  			if cfg.DefaultBuilder == "" && len(args) == 0 {
    30  				suggestSettingBuilder(logger, client)
    31  				return MakeSoftError()
    32  			}
    33  
    34  			imageName := cfg.DefaultBuilder
    35  			if len(args) >= 1 {
    36  				imageName = args[0]
    37  			}
    38  
    39  			if imageName == cfg.DefaultBuilder {
    40  				logger.Infof("Inspecting default builder: %s\n", style.Symbol(imageName))
    41  			} else {
    42  				logger.Infof("Inspecting builder: %s\n", style.Symbol(imageName))
    43  			}
    44  
    45  			remoteOutput, warnings, err := inspectBuilderOutput(client, cfg, imageName, false)
    46  			if err != nil {
    47  				logger.Error(err.Error())
    48  			} else {
    49  				logger.Infof("\nREMOTE:\n%s\n", remoteOutput)
    50  				for _, w := range warnings {
    51  					logger.Warn(w)
    52  				}
    53  			}
    54  
    55  			localOutput, warnings, err := inspectBuilderOutput(client, cfg, imageName, true)
    56  			if err != nil {
    57  				logger.Error(err.Error())
    58  			} else {
    59  				logger.Infof("\nLOCAL:\n%s\n", localOutput)
    60  				for _, w := range warnings {
    61  					logger.Warn(w)
    62  				}
    63  			}
    64  
    65  			return nil
    66  		}),
    67  	}
    68  	AddHelpFlag(cmd, "inspect-builder")
    69  	return cmd
    70  }
    71  
    72  func inspectBuilderOutput(client PackClient, cfg config.Config, imageName string, local bool) (output string, warning []string, err error) {
    73  	source := "remote"
    74  	if local {
    75  		source = "local"
    76  	}
    77  
    78  	info, err := client.InspectBuilder(imageName, local)
    79  	if err != nil {
    80  		return "", nil, errors.Wrapf(err, "inspecting %s image '%s'", source, imageName)
    81  	}
    82  
    83  	if info == nil {
    84  		return "(not present)", nil, nil
    85  	}
    86  
    87  	var buf bytes.Buffer
    88  	warnings, err := generateBuilderOutput(&buf, imageName, cfg, *info)
    89  	if err != nil {
    90  		return "", nil, errors.Wrapf(err, "writing output for %s image '%s'", source, imageName)
    91  	}
    92  
    93  	return buf.String(), warnings, nil
    94  }
    95  
    96  func generateBuilderOutput(writer io.Writer, imageName string, cfg config.Config, info pack.BuilderInfo) (warnings []string, err error) {
    97  	tpl := template.Must(template.New("").Parse(`
    98  {{ if ne .Info.Description "" -}}
    99  Description: {{ .Info.Description }}
   100  
   101  {{ end -}}
   102  
   103  {{- if ne .Info.CreatedBy.Name "" -}}
   104  Created By:
   105    Name: {{ .Info.CreatedBy.Name }}
   106    Version: {{ .Info.CreatedBy.Version }}
   107  
   108  {{ end -}}
   109  
   110  Stack: {{ .Info.Stack }}
   111  
   112  Lifecycle:
   113    Version: {{ .Info.Lifecycle.Info.Version }}
   114    Buildpack API: {{ .Info.Lifecycle.API.BuildpackVersion }}
   115    Platform API: {{ .Info.Lifecycle.API.PlatformVersion }}
   116  
   117  Run Images:
   118  {{- if ne .RunImages "" }}
   119  {{ .RunImages }}
   120  {{- else }}
   121    (none) 
   122  {{- end }}
   123  
   124  Buildpacks:
   125  {{- if .Info.Buildpacks }}
   126  {{ .Buildpacks }}
   127  {{- else }}
   128    (none) 
   129  {{- end }}
   130  
   131  Detection Order:
   132  {{- if ne .Order "" }}
   133  {{ .Order }}
   134  {{- else }}
   135    (none)
   136  {{ end }}`,
   137  	))
   138  
   139  	bps, err := buildpacksOutput(info.Buildpacks)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	if len(info.Buildpacks) == 0 {
   145  		warnings = append(warnings, fmt.Sprintf("%s has no buildpacks", style.Symbol(imageName)))
   146  		warnings = append(warnings, "Users must supply buildpacks from the host machine")
   147  	}
   148  
   149  	order, err := detectionOrderOutput(info.Order)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	if len(info.Order) == 0 {
   155  		warnings = append(warnings, fmt.Sprintf("%s does not specify detection order", style.Symbol(imageName)))
   156  		warnings = append(warnings, "Users must build with explicitly specified buildpacks")
   157  	}
   158  
   159  	runImgs, err := runImagesOutput(info.RunImage, info.RunImageMirrors, cfg)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	if info.RunImage == "" {
   165  		warnings = append(warnings, fmt.Sprintf("%s does not specify a run image", style.Symbol(imageName)))
   166  		warnings = append(warnings, "Users must build with an explicitly specified run image")
   167  	}
   168  
   169  	lcDescriptor := &info.Lifecycle
   170  	if lcDescriptor.Info.Version == nil {
   171  		lcDescriptor.Info.Version = builder.VersionMustParse(builder.AssumedLifecycleVersion)
   172  	}
   173  
   174  	if lcDescriptor.API.BuildpackVersion == nil {
   175  		lcDescriptor.API.BuildpackVersion = api.MustParse(dist.AssumedBuildpackAPIVersion)
   176  	}
   177  
   178  	if lcDescriptor.API.PlatformVersion == nil {
   179  		lcDescriptor.API.PlatformVersion = api.MustParse(builder.AssumedPlatformAPIVersion)
   180  	}
   181  
   182  	return warnings, tpl.Execute(writer, &struct {
   183  		Info       pack.BuilderInfo
   184  		Buildpacks string
   185  		RunImages  string
   186  		Order      string
   187  	}{
   188  		info,
   189  		bps,
   190  		runImgs,
   191  		order,
   192  	})
   193  }
   194  
   195  // TODO: present buildpack order (inc. nested) [https://github.com/buildpack/pack/issues/253].
   196  func buildpacksOutput(bps []builder.BuildpackMetadata) (string, error) {
   197  	buf := &bytes.Buffer{}
   198  	tabWriter := new(tabwriter.Writer).Init(buf, 0, 0, 8, ' ', 0)
   199  	if _, err := fmt.Fprint(tabWriter, "  ID\tVERSION\n"); err != nil {
   200  		return "", err
   201  	}
   202  
   203  	for _, bp := range bps {
   204  		if _, err := fmt.Fprint(tabWriter, fmt.Sprintf("  %s\t%s\n", bp.ID, bp.Version)); err != nil {
   205  			return "", err
   206  		}
   207  	}
   208  
   209  	if err := tabWriter.Flush(); err != nil {
   210  		return "", err
   211  	}
   212  
   213  	return strings.TrimSuffix(buf.String(), "\n"), nil
   214  }
   215  
   216  func runImagesOutput(runImage string, mirrors []string, cfg config.Config) (string, error) {
   217  	buf := &bytes.Buffer{}
   218  	tabWriter := new(tabwriter.Writer).Init(buf, 0, 0, 4, ' ', 0)
   219  
   220  	for _, r := range getLocalMirrors(runImage, cfg) {
   221  		if _, err := fmt.Fprintf(tabWriter, "  %s\t(user-configured)\n", r); err != nil {
   222  			return "", err
   223  		}
   224  	}
   225  
   226  	if runImage != "" {
   227  		if _, err := fmt.Fprintf(tabWriter, "  %s\n", runImage); err != nil {
   228  			return "", err
   229  		}
   230  	}
   231  
   232  	for _, r := range mirrors {
   233  		if _, err := fmt.Fprintf(tabWriter, "  %s\n", r); err != nil {
   234  			return "", err
   235  		}
   236  	}
   237  
   238  	if err := tabWriter.Flush(); err != nil {
   239  		return "", err
   240  	}
   241  
   242  	return strings.TrimSuffix(buf.String(), "\n"), nil
   243  }
   244  
   245  func detectionOrderOutput(order dist.Order) (string, error) {
   246  	buf := strings.Builder{}
   247  	for i, group := range order {
   248  		buf.WriteString(fmt.Sprintf("  Group #%d:\n", i+1))
   249  
   250  		tabWriter := new(tabwriter.Writer).Init(&buf, 0, 0, 4, ' ', 0)
   251  		for _, bp := range group.Group {
   252  			var optional string
   253  			if bp.Optional {
   254  				optional = "(optional)"
   255  			}
   256  
   257  			bpRef := bp.ID
   258  			if bp.Version != "" {
   259  				bpRef += "@" + bp.Version
   260  			}
   261  
   262  			if _, err := fmt.Fprintf(tabWriter, "    %s\t%s\n", bpRef, optional); err != nil {
   263  				return "", err
   264  			}
   265  		}
   266  		if err := tabWriter.Flush(); err != nil {
   267  			return "", err
   268  		}
   269  	}
   270  
   271  	return strings.TrimSuffix(buf.String(), "\n"), nil
   272  }
   273  
   274  func getLocalMirrors(runImage string, cfg config.Config) []string {
   275  	for _, ri := range cfg.RunImages {
   276  		if ri.Image == runImage {
   277  			return ri.Mirrors
   278  		}
   279  	}
   280  	return nil
   281  }