github.com/kobeld/docker@v1.12.0-rc1/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 (ctx ImageContext) Write() { 159 switch ctx.Format { 160 case tableFormatKey: 161 ctx.Format = defaultImageTableFormat 162 if ctx.Digest { 163 ctx.Format = defaultImageTableFormatWithDigest 164 } 165 if ctx.Quiet { 166 ctx.Format = defaultQuietFormat 167 } 168 case rawFormatKey: 169 if ctx.Quiet { 170 ctx.Format = `image_id: {{.ID}}` 171 } else { 172 if ctx.Digest { 173 ctx.Format = `repository: {{ .Repository }} 174 tag: {{.Tag}} 175 digest: {{.Digest}} 176 image_id: {{.ID}} 177 created_at: {{.CreatedAt}} 178 virtual_size: {{.Size}} 179 ` 180 } else { 181 ctx.Format = `repository: {{ .Repository }} 182 tag: {{.Tag}} 183 image_id: {{.ID}} 184 created_at: {{.CreatedAt}} 185 virtual_size: {{.Size}} 186 ` 187 } 188 } 189 } 190 191 ctx.buffer = bytes.NewBufferString("") 192 ctx.preformat() 193 if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") { 194 ctx.finalFormat += "\t{{.Digest}}" 195 } 196 197 tmpl, err := ctx.parseFormat() 198 if err != nil { 199 return 200 } 201 202 for _, image := range ctx.Images { 203 204 repoTags := image.RepoTags 205 repoDigests := image.RepoDigests 206 207 if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" { 208 // dangling image - clear out either repoTags or repoDigests so we only show it once below 209 repoDigests = []string{} 210 } 211 // combine the tags and digests lists 212 tagsAndDigests := append(repoTags, repoDigests...) 213 for _, repoAndRef := range tagsAndDigests { 214 repo := "<none>" 215 tag := "<none>" 216 digest := "<none>" 217 218 if !strings.HasPrefix(repoAndRef, "<none>") { 219 ref, err := reference.ParseNamed(repoAndRef) 220 if err != nil { 221 continue 222 } 223 repo = ref.Name() 224 225 switch x := ref.(type) { 226 case reference.Canonical: 227 digest = x.Digest().String() 228 case reference.NamedTagged: 229 tag = x.Tag() 230 } 231 } 232 imageCtx := &imageContext{ 233 trunc: ctx.Trunc, 234 i: image, 235 repo: repo, 236 tag: tag, 237 digest: digest, 238 } 239 err = ctx.contextFormat(tmpl, imageCtx) 240 if err != nil { 241 return 242 } 243 } 244 } 245 246 ctx.postformat(tmpl, &imageContext{}) 247 }