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 }