github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/inspectimage/writer/human_readable.go (about)

     1  package writer
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  	"text/tabwriter"
     8  	"text/template"
     9  
    10  	"github.com/buildpacks/pack/internal/inspectimage"
    11  	"github.com/buildpacks/pack/pkg/client"
    12  
    13  	strs "github.com/buildpacks/pack/internal/strings"
    14  	"github.com/buildpacks/pack/internal/style"
    15  	"github.com/buildpacks/pack/pkg/logging"
    16  )
    17  
    18  type HumanReadable struct{}
    19  
    20  func NewHumanReadable() *HumanReadable {
    21  	return &HumanReadable{}
    22  }
    23  
    24  func (h *HumanReadable) Print(
    25  	logger logging.Logger,
    26  	generalInfo inspectimage.GeneralInfo,
    27  	local, remote *client.ImageInfo,
    28  	localErr, remoteErr error,
    29  ) error {
    30  	if local == nil && remote == nil {
    31  		return fmt.Errorf("unable to find image '%s' locally or remotely", generalInfo.Name)
    32  	}
    33  
    34  	logger.Infof("Inspecting image: %s\n", style.Symbol(generalInfo.Name))
    35  
    36  	if err := writeRemoteImageInfo(logger, generalInfo, remote, remoteErr); err != nil {
    37  		return err
    38  	}
    39  
    40  	if err := writeLocalImageInfo(logger, generalInfo, local, localErr); err != nil {
    41  		return err
    42  	}
    43  
    44  	return nil
    45  }
    46  
    47  func writeLocalImageInfo(
    48  	logger logging.Logger,
    49  	generalInfo inspectimage.GeneralInfo,
    50  	local *client.ImageInfo,
    51  	localErr error) error {
    52  	logger.Info("\nLOCAL:\n")
    53  
    54  	if localErr != nil {
    55  		logger.Errorf("%s\n", localErr)
    56  		return nil
    57  	}
    58  
    59  	localDisplay := inspectimage.NewInfoDisplay(local, generalInfo)
    60  	if localDisplay == nil {
    61  		logger.Info("(not present)\n")
    62  		return nil
    63  	}
    64  
    65  	err := writeImageInfo(logger, localDisplay)
    66  	if err != nil {
    67  		return fmt.Errorf("writing local builder info: %w", err)
    68  	}
    69  
    70  	return nil
    71  }
    72  
    73  func writeRemoteImageInfo(
    74  	logger logging.Logger,
    75  	generalInfo inspectimage.GeneralInfo,
    76  	remote *client.ImageInfo,
    77  	remoteErr error) error {
    78  	logger.Info("\nREMOTE:\n")
    79  
    80  	if remoteErr != nil {
    81  		logger.Errorf("%s\n", remoteErr)
    82  		return nil
    83  	}
    84  
    85  	remoteDisplay := inspectimage.NewInfoDisplay(remote, generalInfo)
    86  	if remoteDisplay == nil {
    87  		logger.Info("(not present)\n")
    88  		return nil
    89  	}
    90  
    91  	err := writeImageInfo(logger, remoteDisplay)
    92  	if err != nil {
    93  		return fmt.Errorf("writing remote builder info: %w", err)
    94  	}
    95  
    96  	return nil
    97  }
    98  
    99  func writeImageInfo(
   100  	logger logging.Logger,
   101  	info *inspectimage.InfoDisplay,
   102  ) error {
   103  	imgTpl := getImageTemplate(info)
   104  	remoteOutput, err := getInspectImageOutput(imgTpl, info)
   105  	if err != nil {
   106  		logger.Error(err.Error())
   107  		return err
   108  	} else {
   109  		logger.Info(remoteOutput.String())
   110  		return nil
   111  	}
   112  }
   113  
   114  func getImageTemplate(info *inspectimage.InfoDisplay) *template.Template {
   115  	imgTpl := template.Must(template.New("runImages").
   116  		Funcs(template.FuncMap{"StringsJoin": strings.Join}).
   117  		Funcs(template.FuncMap{"StringsValueOrDefault": strs.ValueOrDefault}).
   118  		Parse(runImagesTemplate))
   119  	imgTpl = template.Must(imgTpl.New("buildpacks").Parse(buildpacksTemplate))
   120  
   121  	imgTpl = template.Must(imgTpl.New("processes").Parse(processesTemplate))
   122  
   123  	imgTpl = template.Must(imgTpl.New("rebasable").Parse(rebasableTemplate))
   124  
   125  	if info != nil && info.Extensions != nil {
   126  		imgTpl = template.Must(imgTpl.New("extensions").Parse(extensionsTemplate))
   127  		imgTpl = template.Must(imgTpl.New("image").Parse(imageWithExtensionTemplate))
   128  	} else {
   129  		imgTpl = template.Must(imgTpl.New("image").Parse(imageTemplate))
   130  	}
   131  	return imgTpl
   132  }
   133  
   134  func getInspectImageOutput(
   135  	tpl *template.Template,
   136  	info *inspectimage.InfoDisplay) (*bytes.Buffer, error) {
   137  	if info == nil {
   138  		return bytes.NewBuffer([]byte("(not present)")), nil
   139  	}
   140  	buf := bytes.NewBuffer(nil)
   141  	tw := tabwriter.NewWriter(buf, 0, 0, 8, ' ', 0)
   142  	defer func() {
   143  		tw.Flush()
   144  	}()
   145  	if err := tpl.Execute(tw, &struct {
   146  		Info *inspectimage.InfoDisplay
   147  	}{
   148  		info,
   149  	}); err != nil {
   150  		return bytes.NewBuffer(nil), err
   151  	}
   152  	return buf, nil
   153  }
   154  
   155  var runImagesTemplate = `
   156  Run Images:
   157  {{- range $_, $m := .Info.RunImageMirrors }}
   158    {{- if $m.UserConfigured }}
   159    {{$m.Name}}	(user-configured)
   160    {{- else }}
   161    {{$m.Name}}
   162    {{- end }}  
   163  {{- end }}
   164  {{- if not .Info.RunImageMirrors }}
   165    (none)
   166  {{- end }}`
   167  
   168  var buildpacksTemplate = `
   169  Buildpacks:
   170  {{- if .Info.Buildpacks }}
   171    ID	VERSION	HOMEPAGE
   172  {{- range $_, $b := .Info.Buildpacks }}
   173    {{ $b.ID }}	{{ $b.Version }}	{{ StringsValueOrDefault $b.Homepage "-" }}
   174  {{- end }}
   175  {{- else }}
   176    (buildpack metadata not present)
   177  {{- end }}`
   178  
   179  var extensionsTemplate = `
   180  Extensions:
   181  {{- if .Info.Extensions }}
   182    ID	VERSION	HOMEPAGE
   183  {{- range $_, $b := .Info.Extensions }}
   184    {{ $b.ID }}	{{ $b.Version }}	{{ StringsValueOrDefault $b.Homepage "-" }}
   185  {{- end }}
   186  {{- else }}
   187    (extension metadata not present)
   188  {{- end }}`
   189  
   190  var processesTemplate = `
   191  {{- if .Info.Processes }}
   192  
   193  Processes:
   194    TYPE	SHELL	COMMAND	ARGS	WORK DIR
   195    {{- range $_, $p := .Info.Processes }}
   196      {{- if $p.Default }}
   197    {{ (printf "%s %s" $p.Type "(default)") }}	{{ $p.Shell }}	{{ $p.Command }}	{{ StringsJoin $p.Args " "  }}	{{ $p.WorkDir }}
   198      {{- else }}
   199    {{ $p.Type }}	{{ $p.Shell }}	{{ $p.Command }}	{{ StringsJoin $p.Args " " }}	{{ $p.WorkDir }}
   200      {{- end }}
   201    {{- end }}
   202  {{- end }}`
   203  
   204  var rebasableTemplate = `
   205  
   206  Rebasable: 
   207  {{- if or .Info.Rebasable (eq .Info.Rebasable true)  }} true 
   208  {{- else }} false 
   209  {{- end }}`
   210  
   211  var imageTemplate = `
   212  Stack: {{ .Info.StackID }}
   213  
   214  Base Image:
   215  {{- if .Info.Base.Reference}}
   216    Reference: {{ .Info.Base.Reference }}
   217  {{- end}}
   218    Top Layer: {{ .Info.Base.TopLayer }}
   219  {{ template "runImages" . }}
   220  {{- template "rebasable" . }}
   221  {{ template "buildpacks" . }}{{ template "processes" . }}`
   222  
   223  var imageWithExtensionTemplate = `
   224  Stack: {{ .Info.StackID }}
   225  
   226  Base Image:
   227  {{- if .Info.Base.Reference}}
   228    Reference: {{ .Info.Base.Reference }}
   229  {{- end}}
   230    Top Layer: {{ .Info.Base.TopLayer }}
   231  {{ template "runImages" . }}
   232  {{- template "rebasable" . }}
   233  {{ template "buildpacks" . }}
   234  {{ template "extensions" . -}}
   235  {{ template "processes" . }}`