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 }