github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "launchpad.net/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 }