github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/rkt/image_list.go (about)

     1  // Copyright 2015 The rkt Authors
     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 main
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema"
    24  	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema/lastditch"
    25  	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/dustin/go-humanize"
    26  	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra"
    27  	"github.com/coreos/rkt/store"
    28  )
    29  
    30  const (
    31  	defaultTimeLayout = "2006-01-02 15:04:05.999 -0700 MST"
    32  
    33  	idField         = "id"
    34  	nameField       = "name"
    35  	importTimeField = "importtime"
    36  	latestField     = "latest"
    37  )
    38  
    39  var (
    40  	// map of valid fields and related flag value
    41  	imagesAllFields = map[string]struct{}{
    42  		idField:         struct{}{},
    43  		nameField:       struct{}{},
    44  		importTimeField: struct{}{},
    45  		latestField:     struct{}{},
    46  	}
    47  
    48  	// map of valid fields and related header name
    49  	ImagesFieldHeaderMap = map[string]string{
    50  		idField:         "ID",
    51  		nameField:       "NAME",
    52  		importTimeField: "IMPORT TIME",
    53  		latestField:     "LATEST",
    54  	}
    55  
    56  	// map of valid sort fields containing the mapping between the provided field name
    57  	// and the related aciinfo's field name.
    58  	ImagesFieldAciInfoMap = map[string]string{
    59  		idField:         "blobkey",
    60  		nameField:       "name",
    61  		importTimeField: "importtime",
    62  		latestField:     "latest",
    63  	}
    64  
    65  	ImagesSortableFields = map[string]struct{}{
    66  		nameField:       struct{}{},
    67  		importTimeField: struct{}{},
    68  	}
    69  )
    70  
    71  type ImagesFields []string
    72  
    73  func (ifs *ImagesFields) Set(s string) error {
    74  	*ifs = []string{}
    75  	fields := strings.Split(s, ",")
    76  	seen := map[string]struct{}{}
    77  	for _, f := range fields {
    78  		// accept any case
    79  		f = strings.ToLower(f)
    80  		_, ok := imagesAllFields[f]
    81  		if !ok {
    82  			return fmt.Errorf("unknown field %q", f)
    83  		}
    84  		if _, ok := seen[f]; ok {
    85  			return fmt.Errorf("duplicated field %q", f)
    86  		}
    87  		*ifs = append(*ifs, f)
    88  		seen[f] = struct{}{}
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  func (ifs *ImagesFields) String() string {
    95  	return strings.Join(*ifs, ",")
    96  }
    97  
    98  func (ifs *ImagesFields) Type() string {
    99  	return "imagesFields"
   100  }
   101  
   102  type ImagesSortFields []string
   103  
   104  func (isf *ImagesSortFields) Set(s string) error {
   105  	*isf = []string{}
   106  	fields := strings.Split(s, ",")
   107  	seen := map[string]struct{}{}
   108  	for _, f := range fields {
   109  		// accept any case
   110  		f = strings.ToLower(f)
   111  		_, ok := ImagesSortableFields[f]
   112  		if !ok {
   113  			return fmt.Errorf("unknown field %q", f)
   114  		}
   115  		if _, ok := seen[f]; ok {
   116  			return fmt.Errorf("duplicated field %q", f)
   117  		}
   118  		*isf = append(*isf, f)
   119  		seen[f] = struct{}{}
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  func (isf *ImagesSortFields) String() string {
   126  	return strings.Join(*isf, ",")
   127  }
   128  
   129  func (isf *ImagesSortFields) Type() string {
   130  	return "imagesSortFields"
   131  }
   132  
   133  type ImagesSortAsc bool
   134  
   135  func (isa *ImagesSortAsc) Set(s string) error {
   136  	switch strings.ToLower(s) {
   137  	case "asc":
   138  		*isa = true
   139  	case "desc":
   140  		*isa = false
   141  	default:
   142  		return fmt.Errorf("wrong sort order")
   143  	}
   144  	return nil
   145  }
   146  
   147  func (isa *ImagesSortAsc) String() string {
   148  	if *isa {
   149  		return "asc"
   150  	}
   151  	return "desc"
   152  }
   153  
   154  func (isa *ImagesSortAsc) Type() string {
   155  	return "imagesSortAsc"
   156  }
   157  
   158  var (
   159  	cmdImageList = &cobra.Command{
   160  		Use:   "list",
   161  		Short: "List images in the local store",
   162  		Run:   runWrapper(runImages),
   163  	}
   164  	flagImagesFields     ImagesFields
   165  	flagImagesSortFields ImagesSortFields
   166  	flagImagesSortAsc    ImagesSortAsc
   167  )
   168  
   169  func init() {
   170  	// Set defaults
   171  	flagImagesFields = []string{idField, nameField, importTimeField, latestField}
   172  	flagImagesSortFields = []string{importTimeField}
   173  	flagImagesSortAsc = true
   174  
   175  	cmdImage.AddCommand(cmdImageList)
   176  	cmdImageList.Flags().Var(&flagImagesFields, "fields", `comma separated list of fields to display. Accepted values: "id", "name", "importtime", "latest"`)
   177  	cmdImageList.Flags().Var(&flagImagesSortFields, "sort", `sort the output according to the provided comma separated list of fields. Accepted values: "name", "importtime"`)
   178  	cmdImageList.Flags().Var(&flagImagesSortAsc, "order", `choose the sorting order if at least one sort field is provided (--sort). Accepted values: "asc", "desc"`)
   179  	cmdImageList.Flags().BoolVar(&flagNoLegend, "no-legend", false, "suppress a legend with the list")
   180  	cmdImageList.Flags().BoolVar(&flagFullOutput, "full", false, "use long output format")
   181  }
   182  
   183  func runImages(cmd *cobra.Command, args []string) int {
   184  	var errors []error
   185  	tabBuffer := new(bytes.Buffer)
   186  	tabOut := getTabOutWithWriter(tabBuffer)
   187  
   188  	if !flagNoLegend {
   189  		var headerFields []string
   190  		for _, f := range flagImagesFields {
   191  			headerFields = append(headerFields, ImagesFieldHeaderMap[f])
   192  		}
   193  		fmt.Fprintf(tabOut, "%s\n", strings.Join(headerFields, "\t"))
   194  	}
   195  
   196  	s, err := store.NewStore(globalFlags.Dir)
   197  	if err != nil {
   198  		stderr("images: cannot open store: %v\n", err)
   199  		return 1
   200  	}
   201  
   202  	var sortAciinfoFields []string
   203  	for _, f := range flagImagesSortFields {
   204  		sortAciinfoFields = append(sortAciinfoFields, ImagesFieldAciInfoMap[f])
   205  	}
   206  	aciInfos, err := s.GetAllACIInfos(sortAciinfoFields, bool(flagImagesSortAsc))
   207  	if err != nil {
   208  		stderr("images: unable to get aci infos: %v", err)
   209  		return 1
   210  	}
   211  
   212  	for _, aciInfo := range aciInfos {
   213  		imj, err := s.GetImageManifestJSON(aciInfo.BlobKey)
   214  		if err != nil {
   215  			// ignore aciInfo with missing image manifest as it can be deleted in the meantime
   216  			continue
   217  		}
   218  		var im *schema.ImageManifest
   219  		if err = json.Unmarshal(imj, &im); err != nil {
   220  			errors = append(errors, newImgListLoadError(err, imj, aciInfo.BlobKey))
   221  			continue
   222  		}
   223  		version, ok := im.Labels.Get("version")
   224  		var fieldValues []string
   225  		for _, f := range flagImagesFields {
   226  			fieldValue := ""
   227  			switch f {
   228  			case idField:
   229  				hashKey := aciInfo.BlobKey
   230  				if !flagFullOutput {
   231  					// The short hash form is [HASH_ALGO]-[FIRST 12 CHAR]
   232  					// For example, sha512-123456789012
   233  					pos := strings.Index(hashKey, "-")
   234  					trimLength := pos + 13
   235  					if pos > 0 && trimLength < len(hashKey) {
   236  						hashKey = hashKey[:trimLength]
   237  					}
   238  				}
   239  				fieldValue = hashKey
   240  			case nameField:
   241  				fieldValue = aciInfo.Name
   242  				if ok {
   243  					fieldValue = fmt.Sprintf("%s:%s", fieldValue, version)
   244  				}
   245  			case importTimeField:
   246  				if flagFullOutput {
   247  					fieldValue = aciInfo.ImportTime.Format(defaultTimeLayout)
   248  				} else {
   249  					fieldValue = humanize.Time(aciInfo.ImportTime)
   250  				}
   251  			case latestField:
   252  				fieldValue = fmt.Sprintf("%t", aciInfo.Latest)
   253  			}
   254  			fieldValues = append(fieldValues, fieldValue)
   255  
   256  		}
   257  		fmt.Fprintf(tabOut, "%s\n", strings.Join(fieldValues, "\t"))
   258  	}
   259  
   260  	if len(errors) > 0 {
   261  		sep := "----------------------------------------"
   262  		stderr("%d error(s) encountered when listing images:", len(errors))
   263  		stderr("%s", sep)
   264  		for _, err := range errors {
   265  			stderr("%s", err.Error())
   266  			stderr("%s", sep)
   267  		}
   268  		stderr("Misc:")
   269  		stderr("  rkt's appc version: %s", schema.AppContainerVersion)
   270  		// make a visible break between errors and the listing
   271  		stderr("")
   272  	}
   273  	tabOut.Flush()
   274  	stdout("%s", tabBuffer.String())
   275  	return 0
   276  }
   277  
   278  func newImgListLoadError(err error, imj []byte, blobKey string) error {
   279  	var lines []string
   280  	im := lastditch.ImageManifest{}
   281  	imErr := im.UnmarshalJSON(imj)
   282  	if imErr == nil {
   283  		lines = append(lines, fmt.Sprintf("Unable to load manifest of image %s (spec version %s) because it is invalid:", im.Name, im.ACVersion))
   284  		lines = append(lines, fmt.Sprintf("  %v", err))
   285  	} else {
   286  		lines = append(lines, "Unable to load manifest of an image because it is invalid:")
   287  		lines = append(lines, fmt.Sprintf("  %v", err))
   288  		lines = append(lines, "  Also, failed to get any information about invalid image manifest:")
   289  		lines = append(lines, fmt.Sprintf("    %v", imErr))
   290  	}
   291  	lines = append(lines, "ID of the invalid image:")
   292  	lines = append(lines, fmt.Sprintf("  %s", blobKey))
   293  	return fmt.Errorf("%s", strings.Join(lines, "\n"))
   294  }