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