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 }