github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/imageengine/buildah/images.go (about) 1 // Copyright © 2022 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package buildah 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "sort" 21 "strings" 22 "time" 23 24 "github.com/containers/buildah/pkg/formats" 25 "github.com/containers/common/libimage" 26 "github.com/docker/go-units" 27 "github.com/sirupsen/logrus" 28 29 "github.com/sealerio/sealer/pkg/define/options" 30 ) 31 32 const none = "<none>" 33 34 type jsonImage struct { 35 ID string `json:"id"` 36 Names []string `json:"names"` 37 Digest string `json:"digest"` 38 CreatedAt string `json:"createdat"` 39 Size string `json:"size"` 40 Created int64 `json:"created"` 41 CreatedAtRaw time.Time `json:"createdatraw"` 42 ReadOnly bool `json:"readonly"` 43 History []string `json:"history"` 44 } 45 46 type imageOutputParams struct { 47 Tag string 48 ID string 49 Name string 50 Digest string 51 Created int64 52 CreatedAt string 53 Size string 54 CreatedAtRaw time.Time 55 ReadOnly bool 56 History string 57 } 58 59 type imageOptions struct { 60 all bool 61 digests bool 62 format string 63 json bool 64 noHeading bool 65 truncate bool 66 quiet bool 67 readOnly bool 68 history bool 69 } 70 71 var imagesHeader = map[string]string{ 72 "Name": "REPOSITORY", 73 "Tag": "TAG", 74 "ID": "IMAGE ID", 75 "CreatedAt": "CREATED", 76 "Size": "SIZE", 77 "ReadOnly": "R/O", 78 "History": "HISTORY", 79 } 80 81 func (engine *Engine) Images(opts *options.ImagesOptions) error { 82 runtime := engine.ImageRuntime() 83 options := &libimage.ListImagesOptions{} 84 if !opts.All { 85 options.Filters = append(options.Filters, "intermediate=false") 86 //options.Filters = append(options.Filters, "label=io.sealer.version") 87 } 88 89 //TODO add some label to identify sealer image and oci image. 90 images, err := runtime.ListImages(getContext(), []string{}, options) 91 if err != nil { 92 return err 93 } 94 95 imageOpts := imageOptions{ 96 all: opts.All, 97 digests: opts.Digests, 98 json: opts.JSON, 99 noHeading: opts.NoHeading, 100 truncate: !opts.NoTrunc, 101 quiet: opts.Quiet, 102 history: opts.History, 103 } 104 105 if opts.JSON { 106 return formatImagesJSON(images, imageOpts) 107 } 108 109 return formatImages(images, imageOpts) 110 } 111 112 func outputHeader(opts imageOptions) string { 113 if opts.format != "" { 114 return strings.Replace(opts.format, `\t`, "\t", -1) 115 } 116 if opts.quiet { 117 return formats.IDString 118 } 119 format := "table {{.Name}}\t{{.Tag}}\t" 120 if opts.noHeading { 121 format = "{{.Name}}\t{{.Tag}}\t" 122 } 123 124 if opts.digests { 125 format += "{{.Digest}}\t" 126 } 127 format += "{{.ID}}\t{{.CreatedAt}}\t{{.Size}}" 128 if opts.readOnly { 129 format += "\t{{.ReadOnly}}" 130 } 131 if opts.history { 132 format += "\t{{.History}}" 133 } 134 return format 135 } 136 137 func formatImagesJSON(images []*libimage.Image, opts imageOptions) error { 138 jsonImages := []jsonImage{} 139 for _, image := range images { 140 // Copy the base data over to the output param. 141 size, err := image.Size() 142 if err != nil { 143 return err 144 } 145 created := image.Created() 146 jsonImages = append(jsonImages, 147 jsonImage{ 148 CreatedAtRaw: created, 149 Created: created.Unix(), 150 CreatedAt: units.HumanDuration(time.Since(created)) + " ago", 151 Digest: image.Digest().String(), 152 ID: TruncateID(image.ID(), opts.truncate), 153 Names: image.Names(), 154 ReadOnly: image.IsReadOnly(), 155 Size: formattedSize(size), 156 }) 157 } 158 159 data, err := json.MarshalIndent(jsonImages, "", " ") 160 if err != nil { 161 return err 162 } 163 logrus.Infof("%s", data) 164 return nil 165 } 166 167 type imagesSorted []imageOutputParams 168 169 func (a imagesSorted) Less(i, j int) bool { 170 return a[i].CreatedAtRaw.After(a[j].CreatedAtRaw) 171 } 172 173 func (a imagesSorted) Len() int { 174 return len(a) 175 } 176 177 func (a imagesSorted) Swap(i, j int) { 178 a[i], a[j] = a[j], a[i] 179 } 180 181 func formatImages(images []*libimage.Image, opts imageOptions) error { 182 var outputData imagesSorted 183 184 for _, image := range images { 185 var outputParam imageOutputParams 186 size, err := image.Size() 187 if err != nil { 188 return err 189 } 190 created := image.Created() 191 outputParam.CreatedAtRaw = created 192 outputParam.Created = created.Unix() 193 outputParam.CreatedAt = units.HumanDuration(time.Since(created)) + " ago" 194 outputParam.Digest = image.Digest().String() 195 outputParam.ID = TruncateID(image.ID(), opts.truncate) 196 outputParam.Size = formattedSize(size) 197 outputParam.ReadOnly = image.IsReadOnly() 198 199 repoTags, err := image.NamedRepoTags() 200 if err != nil { 201 return err 202 } 203 204 nameTagPairs, err := libimage.ToNameTagPairs(repoTags) 205 if err != nil { 206 return err 207 } 208 209 for _, pair := range nameTagPairs { 210 newParam := outputParam 211 newParam.Name = pair.Name 212 newParam.Tag = pair.Tag 213 newParam.History = formatHistory(image.NamesHistory(), pair.Name, pair.Tag) 214 outputData = append(outputData, newParam) 215 // `images -q` should a given ID only once. 216 if opts.quiet { 217 break 218 } 219 } 220 } 221 222 sort.Sort(outputData) 223 out := formats.StdoutTemplateArray{Output: imagesToGeneric(outputData), Template: outputHeader(opts), Fields: imagesHeader} 224 return formats.Writer(out).Out() 225 } 226 227 func formatHistory(history []string, name, tag string) string { 228 if len(history) == 0 { 229 return none 230 } 231 // Skip the first history entry if already existing as name 232 if fmt.Sprintf("%s:%s", name, tag) == history[0] { 233 if len(history) == 1 { 234 return none 235 } 236 return strings.Join(history[1:], ", ") 237 } 238 return strings.Join(history, ", ") 239 } 240 241 func TruncateID(id string, truncate bool) string { 242 if !truncate { 243 return "sha256:" + id 244 } 245 246 if idTruncLength := 12; len(id) > idTruncLength { 247 return id[:idTruncLength] 248 } 249 return id 250 } 251 252 func imagesToGeneric(templParams []imageOutputParams) (genericParams []interface{}) { 253 if len(templParams) > 0 { 254 for _, v := range templParams { 255 genericParams = append(genericParams, interface{}(v)) 256 } 257 } 258 return genericParams 259 } 260 261 func formattedSize(size int64) string { 262 suffixes := [5]string{"B", "KB", "MB", "GB", "TB"} 263 264 count := 0 265 formattedSize := float64(size) 266 for formattedSize >= 1000 && count < 4 { 267 formattedSize /= 1000 268 count++ 269 } 270 return fmt.Sprintf("%.3g %s", formattedSize, suffixes[count]) 271 } 272 273 //func matchesID(imageID, argID string) bool { 274 // return strings.HasPrefix(imageID, argID) 275 //} 276 // 277 //func matchesReference(name, argName string) bool { 278 // if argName == "" { 279 // return true 280 // } 281 // splitName := strings.Split(name, ":") 282 // // If the arg contains a tag, we handle it differently than if it does not 283 // if strings.Contains(argName, ":") { 284 // splitArg := strings.Split(argName, ":") 285 // return strings.HasSuffix(splitName[0], splitArg[0]) && (splitName[1] == splitArg[1]) 286 // } 287 // return strings.HasSuffix(splitName[0], argName) 288 //}