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  //}