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" . }}`