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