github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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 ctx.Format = defaultContainerTableFormat 118 if ctx.Quiet { 119 ctx.Format = defaultQuietFormat 120 } 121 case rawFormatKey: 122 if ctx.Quiet { 123 ctx.Format = `container_id: {{.ID}}` 124 } else { 125 ctx.Format = `container_id: {{.ID}} 126 image: {{.Image}} 127 command: {{.Command}} 128 created_at: {{.CreatedAt}} 129 status: {{.Status}} 130 names: {{.Names}} 131 labels: {{.Labels}} 132 ports: {{.Ports}} 133 ` 134 if ctx.Size { 135 ctx.Format += `size: {{.Size}} 136 ` 137 } 138 } 139 } 140 141 ctx.buffer = bytes.NewBufferString("") 142 ctx.preformat() 143 if ctx.table && ctx.Size { 144 ctx.finalFormat += "\t{{.Size}}" 145 } 146 147 tmpl, err := ctx.parseFormat() 148 if err != nil { 149 return 150 } 151 152 for _, container := range ctx.Containers { 153 containerCtx := &containerContext{ 154 trunc: ctx.Trunc, 155 c: container, 156 } 157 err = ctx.contextFormat(tmpl, containerCtx) 158 if err != nil { 159 return 160 } 161 } 162 163 ctx.postformat(tmpl, &containerContext{}) 164 } 165 166 func (ctx ImageContext) Write() { 167 switch ctx.Format { 168 case tableFormatKey: 169 ctx.Format = defaultImageTableFormat 170 if ctx.Digest { 171 ctx.Format = defaultImageTableFormatWithDigest 172 } 173 if ctx.Quiet { 174 ctx.Format = defaultQuietFormat 175 } 176 case rawFormatKey: 177 if ctx.Quiet { 178 ctx.Format = `image_id: {{.ID}}` 179 } else { 180 if ctx.Digest { 181 ctx.Format = `repository: {{ .Repository }} 182 tag: {{.Tag}} 183 digest: {{.Digest}} 184 image_id: {{.ID}} 185 created_at: {{.CreatedAt}} 186 virtual_size: {{.Size}} 187 ` 188 } else { 189 ctx.Format = `repository: {{ .Repository }} 190 tag: {{.Tag}} 191 image_id: {{.ID}} 192 created_at: {{.CreatedAt}} 193 virtual_size: {{.Size}} 194 ` 195 } 196 } 197 } 198 199 ctx.buffer = bytes.NewBufferString("") 200 ctx.preformat() 201 if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") { 202 ctx.finalFormat += "\t{{.Digest}}" 203 } 204 205 tmpl, err := ctx.parseFormat() 206 if err != nil { 207 return 208 } 209 210 for _, image := range ctx.Images { 211 212 repoTags := image.RepoTags 213 repoDigests := image.RepoDigests 214 215 if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" { 216 // dangling image - clear out either repoTags or repoDigests so we only show it once below 217 repoDigests = []string{} 218 } 219 // combine the tags and digests lists 220 tagsAndDigests := append(repoTags, repoDigests...) 221 for _, repoAndRef := range tagsAndDigests { 222 repo := "<none>" 223 tag := "<none>" 224 digest := "<none>" 225 226 if !strings.HasPrefix(repoAndRef, "<none>") { 227 ref, err := reference.ParseNamed(repoAndRef) 228 if err != nil { 229 continue 230 } 231 repo = ref.Name() 232 233 switch x := ref.(type) { 234 case reference.Canonical: 235 digest = x.Digest().String() 236 case reference.NamedTagged: 237 tag = x.Tag() 238 } 239 } 240 imageCtx := &imageContext{ 241 trunc: ctx.Trunc, 242 i: image, 243 repo: repo, 244 tag: tag, 245 digest: digest, 246 } 247 err = ctx.contextFormat(tmpl, imageCtx) 248 if err != nil { 249 return 250 } 251 } 252 } 253 254 ctx.postformat(tmpl, &imageContext{}) 255 }