github.com/portworx/docker@v1.12.1/api/client/formatter/formatter.go (about) 1 package formatter 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "strings" 8 "text/tabwriter" 9 "text/template" 10 11 "github.com/docker/docker/reference" 12 "github.com/docker/docker/utils/templates" 13 "github.com/docker/engine-api/types" 14 ) 15 16 const ( 17 tableFormatKey = "table" 18 rawFormatKey = "raw" 19 20 defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}" 21 defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}" 22 defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}" 23 defaultQuietFormat = "{{.ID}}" 24 ) 25 26 // Context contains information required by the formatter to print the output as desired. 27 type Context struct { 28 // Output is the output stream to which the formatted string is written. 29 Output io.Writer 30 // Format is used to choose raw, table or custom format for the output. 31 Format string 32 // Quiet when set to true will simply print minimal information. 33 Quiet bool 34 // Trunc when set to true will truncate the output of certain fields such as Container ID. 35 Trunc bool 36 37 // internal element 38 table bool 39 finalFormat string 40 header string 41 buffer *bytes.Buffer 42 } 43 44 func (c *Context) preformat() { 45 c.finalFormat = c.Format 46 47 if strings.HasPrefix(c.Format, tableKey) { 48 c.table = true 49 c.finalFormat = c.finalFormat[len(tableKey):] 50 } 51 52 c.finalFormat = strings.Trim(c.finalFormat, " ") 53 r := strings.NewReplacer(`\t`, "\t", `\n`, "\n") 54 c.finalFormat = r.Replace(c.finalFormat) 55 } 56 57 func (c *Context) parseFormat() (*template.Template, error) { 58 tmpl, err := templates.Parse(c.finalFormat) 59 if err != nil { 60 c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err)) 61 c.buffer.WriteTo(c.Output) 62 } 63 return tmpl, err 64 } 65 66 func (c *Context) postformat(tmpl *template.Template, subContext subContext) { 67 if c.table { 68 if len(c.header) == 0 { 69 // if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template 70 tmpl.Execute(bytes.NewBufferString(""), subContext) 71 c.header = subContext.fullHeader() 72 } 73 74 t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0) 75 t.Write([]byte(c.header)) 76 t.Write([]byte("\n")) 77 c.buffer.WriteTo(t) 78 t.Flush() 79 } else { 80 c.buffer.WriteTo(c.Output) 81 } 82 } 83 84 func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error { 85 if err := tmpl.Execute(c.buffer, subContext); err != nil { 86 c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err)) 87 c.buffer.WriteTo(c.Output) 88 return err 89 } 90 if c.table && len(c.header) == 0 { 91 c.header = subContext.fullHeader() 92 } 93 c.buffer.WriteString("\n") 94 return nil 95 } 96 97 // ContainerContext contains container specific information required by the formater, encapsulate a Context struct. 98 type ContainerContext struct { 99 Context 100 // Size when set to true will display the size of the output. 101 Size bool 102 // Containers 103 Containers []types.Container 104 } 105 106 // ImageContext contains image specific information required by the formater, encapsulate a Context struct. 107 type ImageContext struct { 108 Context 109 Digest bool 110 // Images 111 Images []types.Image 112 } 113 114 func (ctx ContainerContext) Write() { 115 switch ctx.Format { 116 case tableFormatKey: 117 if ctx.Quiet { 118 ctx.Format = defaultQuietFormat 119 } else { 120 ctx.Format = defaultContainerTableFormat 121 if ctx.Size { 122 ctx.Format += `\t{{.Size}}` 123 } 124 } 125 case rawFormatKey: 126 if ctx.Quiet { 127 ctx.Format = `container_id: {{.ID}}` 128 } else { 129 ctx.Format = `container_id: {{.ID}}\nimage: {{.Image}}\ncommand: {{.Command}}\ncreated_at: {{.CreatedAt}}\nstatus: {{.Status}}\nnames: {{.Names}}\nlabels: {{.Labels}}\nports: {{.Ports}}\n` 130 if ctx.Size { 131 ctx.Format += `size: {{.Size}}\n` 132 } 133 } 134 } 135 136 ctx.buffer = bytes.NewBufferString("") 137 ctx.preformat() 138 139 tmpl, err := ctx.parseFormat() 140 if err != nil { 141 return 142 } 143 144 for _, container := range ctx.Containers { 145 containerCtx := &containerContext{ 146 trunc: ctx.Trunc, 147 c: container, 148 } 149 err = ctx.contextFormat(tmpl, containerCtx) 150 if err != nil { 151 return 152 } 153 } 154 155 ctx.postformat(tmpl, &containerContext{}) 156 } 157 158 func isDangling(image types.Image) bool { 159 return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>" 160 } 161 162 func (ctx ImageContext) Write() { 163 switch ctx.Format { 164 case tableFormatKey: 165 ctx.Format = defaultImageTableFormat 166 if ctx.Digest { 167 ctx.Format = defaultImageTableFormatWithDigest 168 } 169 if ctx.Quiet { 170 ctx.Format = defaultQuietFormat 171 } 172 case rawFormatKey: 173 if ctx.Quiet { 174 ctx.Format = `image_id: {{.ID}}` 175 } else { 176 if ctx.Digest { 177 ctx.Format = `repository: {{ .Repository }} 178 tag: {{.Tag}} 179 digest: {{.Digest}} 180 image_id: {{.ID}} 181 created_at: {{.CreatedAt}} 182 virtual_size: {{.Size}} 183 ` 184 } else { 185 ctx.Format = `repository: {{ .Repository }} 186 tag: {{.Tag}} 187 image_id: {{.ID}} 188 created_at: {{.CreatedAt}} 189 virtual_size: {{.Size}} 190 ` 191 } 192 } 193 } 194 195 ctx.buffer = bytes.NewBufferString("") 196 ctx.preformat() 197 if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") { 198 ctx.finalFormat += "\t{{.Digest}}" 199 } 200 201 tmpl, err := ctx.parseFormat() 202 if err != nil { 203 return 204 } 205 206 for _, image := range ctx.Images { 207 images := []*imageContext{} 208 if isDangling(image) { 209 images = append(images, &imageContext{ 210 trunc: ctx.Trunc, 211 i: image, 212 repo: "<none>", 213 tag: "<none>", 214 digest: "<none>", 215 }) 216 } else { 217 repoTags := map[string][]string{} 218 repoDigests := map[string][]string{} 219 220 for _, refString := range append(image.RepoTags) { 221 ref, err := reference.ParseNamed(refString) 222 if err != nil { 223 continue 224 } 225 if nt, ok := ref.(reference.NamedTagged); ok { 226 repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag()) 227 } 228 } 229 for _, refString := range append(image.RepoDigests) { 230 ref, err := reference.ParseNamed(refString) 231 if err != nil { 232 continue 233 } 234 if c, ok := ref.(reference.Canonical); ok { 235 repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String()) 236 } 237 } 238 239 for repo, tags := range repoTags { 240 digests := repoDigests[repo] 241 242 // Do not display digests as their own row 243 delete(repoDigests, repo) 244 245 if !ctx.Digest { 246 // Ignore digest references, just show tag once 247 digests = nil 248 } 249 250 for _, tag := range tags { 251 if len(digests) == 0 { 252 images = append(images, &imageContext{ 253 trunc: ctx.Trunc, 254 i: image, 255 repo: repo, 256 tag: tag, 257 digest: "<none>", 258 }) 259 continue 260 } 261 // Display the digests for each tag 262 for _, dgst := range digests { 263 images = append(images, &imageContext{ 264 trunc: ctx.Trunc, 265 i: image, 266 repo: repo, 267 tag: tag, 268 digest: dgst, 269 }) 270 } 271 272 } 273 } 274 275 // Show rows for remaining digest only references 276 for repo, digests := range repoDigests { 277 // If digests are displayed, show row per digest 278 if ctx.Digest { 279 for _, dgst := range digests { 280 images = append(images, &imageContext{ 281 trunc: ctx.Trunc, 282 i: image, 283 repo: repo, 284 tag: "<none>", 285 digest: dgst, 286 }) 287 } 288 } else { 289 images = append(images, &imageContext{ 290 trunc: ctx.Trunc, 291 i: image, 292 repo: repo, 293 tag: "<none>", 294 }) 295 } 296 } 297 } 298 for _, imageCtx := range images { 299 err = ctx.contextFormat(tmpl, imageCtx) 300 if err != nil { 301 return 302 } 303 } 304 } 305 306 ctx.postformat(tmpl, &imageContext{}) 307 }