github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/plugins/juju-metadata/listimages.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/gnuflag"
    13  
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/cmd/modelcmd"
    16  )
    17  
    18  func newListImagesCommand() cmd.Command {
    19  	return modelcmd.Wrap(&listImagesCommand{})
    20  }
    21  
    22  const listCommandDoc = `
    23  List information about image metadata stored in Juju model.
    24  This list can be filtered using various filters as described below.
    25  
    26  More than one filter can be specified. Result will contain metadata that matches all filters in combination.
    27  
    28  If no filters are supplied, all stored image metadata will be listed.
    29  
    30  options:
    31  -m, --model (= "")
    32     juju model to operate in
    33  -o, --output (= "")
    34     specify an output file
    35  --format (= tabular)
    36     specify output format (json|tabular|yaml)
    37  --stream
    38     image stream
    39  --region
    40     cloud region
    41  --series
    42     comma separated list of series
    43  --arch
    44     comma separated list of architectures
    45  --virt-type
    46     virtualisation type [provider specific], e.g. hvm
    47  --storage-type
    48     root storage type [provider specific], e.g. ebs
    49  `
    50  
    51  // listImagesCommand returns stored image metadata.
    52  type listImagesCommand struct {
    53  	cloudImageMetadataCommandBase
    54  
    55  	out cmd.Output
    56  
    57  	Stream          string
    58  	Region          string
    59  	Series          []string
    60  	Arches          []string
    61  	VirtType        string
    62  	RootStorageType string
    63  }
    64  
    65  // Init implements Command.Init.
    66  func (c *listImagesCommand) Init(args []string) (err error) {
    67  	if len(c.Series) > 0 {
    68  		result := []string{}
    69  		for _, one := range c.Series {
    70  			result = append(result, strings.Split(one, ",")...)
    71  		}
    72  		c.Series = result
    73  	}
    74  	if len(c.Arches) > 0 {
    75  		result := []string{}
    76  		for _, one := range c.Arches {
    77  			result = append(result, strings.Split(one, ",")...)
    78  		}
    79  		c.Arches = result
    80  	}
    81  	return nil
    82  }
    83  
    84  // Info implements Command.Info.
    85  func (c *listImagesCommand) Info() *cmd.Info {
    86  	return &cmd.Info{
    87  		Name:    "list-images",
    88  		Purpose: "lists cloud image metadata used when choosing an image to start",
    89  		Doc:     listCommandDoc,
    90  	}
    91  }
    92  
    93  // SetFlags implements Command.SetFlags.
    94  func (c *listImagesCommand) SetFlags(f *gnuflag.FlagSet) {
    95  	c.cloudImageMetadataCommandBase.SetFlags(f)
    96  
    97  	f.StringVar(&c.Stream, "stream", "", "image metadata stream")
    98  	f.StringVar(&c.Region, "region", "", "image metadata cloud region")
    99  
   100  	f.Var(cmd.NewAppendStringsValue(&c.Series), "series", "only show cloud image metadata for these series")
   101  	f.Var(cmd.NewAppendStringsValue(&c.Arches), "arch", "only show cloud image metadata for these architectures")
   102  
   103  	f.StringVar(&c.VirtType, "virt-type", "", "image metadata virtualisation type")
   104  	f.StringVar(&c.RootStorageType, "storage-type", "", "image metadata root storage type")
   105  
   106  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
   107  		"yaml":    cmd.FormatYaml,
   108  		"json":    cmd.FormatJson,
   109  		"tabular": formatMetadataListTabular,
   110  	})
   111  }
   112  
   113  // Run implements Command.Run.
   114  func (c *listImagesCommand) Run(ctx *cmd.Context) (err error) {
   115  	api, err := getImageMetadataListAPI(c)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	defer api.Close()
   120  
   121  	found, err := api.List(c.Stream, c.Region, c.Series, c.Arches, c.VirtType, c.RootStorageType)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	if len(found) == 0 {
   126  		return nil
   127  	}
   128  
   129  	info, errs := convertDetailsToInfo(found)
   130  	if len(errs) > 0 {
   131  		// display individual error
   132  		fmt.Fprintf(ctx.Stderr, strings.Join(errs, "\n"))
   133  	}
   134  
   135  	var output interface{}
   136  	switch c.out.Name() {
   137  	case "yaml", "json":
   138  		output = groupMetadata(info)
   139  	default:
   140  		{
   141  			sort.Sort(metadataInfos(info))
   142  			output = info
   143  		}
   144  	}
   145  	return c.out.Write(ctx, output)
   146  }
   147  
   148  var getImageMetadataListAPI = (*listImagesCommand).getImageMetadataListAPI
   149  
   150  // MetadataListAPI defines the API methods that list image metadata command uses.
   151  type MetadataListAPI interface {
   152  	Close() error
   153  	List(stream, region string, series, arches []string, virtType, rootStorageType string) ([]params.CloudImageMetadata, error)
   154  }
   155  
   156  func (c *listImagesCommand) getImageMetadataListAPI() (MetadataListAPI, error) {
   157  	return c.NewImageMetadataAPI()
   158  }
   159  
   160  // convertDetailsToInfo converts cloud image metadata received from api to
   161  // structure native to CLI.
   162  // We also return a list of errors for versions we could not convert to series for user friendly read.
   163  func convertDetailsToInfo(details []params.CloudImageMetadata) ([]MetadataInfo, []string) {
   164  	if len(details) == 0 {
   165  		return nil, nil
   166  	}
   167  
   168  	info := make([]MetadataInfo, len(details))
   169  	errs := []string{}
   170  	for i, one := range details {
   171  		info[i] = MetadataInfo{
   172  			Source:          one.Source,
   173  			Series:          one.Series,
   174  			Arch:            one.Arch,
   175  			Region:          one.Region,
   176  			ImageId:         one.ImageId,
   177  			Stream:          one.Stream,
   178  			VirtType:        one.VirtType,
   179  			RootStorageType: one.RootStorageType,
   180  		}
   181  	}
   182  	return info, errs
   183  }
   184  
   185  // metadataInfos is a convenience type enabling to sort
   186  // a collection of MetadataInfo
   187  type metadataInfos []MetadataInfo
   188  
   189  // Implements sort.Interface
   190  func (m metadataInfos) Len() int {
   191  	return len(m)
   192  }
   193  
   194  // Implements sort.Interface and sort image metadata
   195  // by source, series, arch and region.
   196  // All properties are sorted in alphabetical order
   197  // except for series which is reversed -
   198  // latest series are at the beginning of the collection.
   199  func (m metadataInfos) Less(i, j int) bool {
   200  	if m[i].Source != m[j].Source {
   201  		// Alphabetical order here is incidentally does what we want:
   202  		// we want "custom" metadata to precede
   203  		// "public" metadata.
   204  		// This may need to b revisited if more meatadata sources will be discovered.
   205  		return m[i].Source < m[j].Source
   206  	}
   207  	if m[i].Series != m[j].Series {
   208  		// reverse order
   209  		return m[i].Series > m[j].Series
   210  	}
   211  	if m[i].Arch != m[j].Arch {
   212  		// alphabetical order
   213  		return m[i].Arch < m[j].Arch
   214  	}
   215  	// alphabetical order
   216  	return m[i].Region < m[j].Region
   217  }
   218  
   219  // Implements sort.Interface
   220  func (m metadataInfos) Swap(i, j int) {
   221  	m[i], m[j] = m[j], m[i]
   222  }
   223  
   224  type minMetadataInfo struct {
   225  	ImageId         string `yaml:"image-id" json:"image-id"`
   226  	Stream          string `yaml:"stream" json:"stream"`
   227  	VirtType        string `yaml:"virt-type,omitempty" json:"virt-type,omitempty"`
   228  	RootStorageType string `yaml:"storage-type,omitempty" json:"storage-type,omitempty"`
   229  }
   230  
   231  // groupMetadata constructs map representation of metadata
   232  // grouping individual items by source, series, arch and region
   233  // to be served to Yaml and JSON output for readability.
   234  func groupMetadata(metadata []MetadataInfo) map[string]map[string]map[string]map[string][]minMetadataInfo {
   235  	result := map[string]map[string]map[string]map[string][]minMetadataInfo{}
   236  
   237  	for _, m := range metadata {
   238  		sourceMap, ok := result[m.Source]
   239  		if !ok {
   240  			sourceMap = map[string]map[string]map[string][]minMetadataInfo{}
   241  			result[m.Source] = sourceMap
   242  		}
   243  
   244  		seriesMap, ok := sourceMap[m.Series]
   245  		if !ok {
   246  			seriesMap = map[string]map[string][]minMetadataInfo{}
   247  			sourceMap[m.Series] = seriesMap
   248  		}
   249  
   250  		archMap, ok := seriesMap[m.Arch]
   251  		if !ok {
   252  			archMap = map[string][]minMetadataInfo{}
   253  			seriesMap[m.Arch] = archMap
   254  		}
   255  
   256  		archMap[m.Region] = append(archMap[m.Region], minMetadataInfo{m.ImageId, m.Stream, m.VirtType, m.RootStorageType})
   257  	}
   258  
   259  	return result
   260  }