github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/cloud/list.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cloud 5 6 import ( 7 "fmt" 8 "io" 9 "sort" 10 "strings" 11 12 "github.com/juju/cmd" 13 "github.com/juju/errors" 14 "github.com/juju/gnuflag" 15 "github.com/juju/loggo" 16 17 jujucloud "github.com/juju/juju/cloud" 18 "github.com/juju/juju/cmd/juju/common" 19 "github.com/juju/juju/cmd/output" 20 ) 21 22 var logger = loggo.GetLogger("juju.cmd.juju.cloud") 23 24 type listCloudsCommand struct { 25 cmd.CommandBase 26 out cmd.Output 27 } 28 29 // listCloudsDoc is multi-line since we need to use ` to denote 30 // commands for ease in markdown. 31 var listCloudsDoc = "" + 32 "Provided information includes 'cloud' (as understood by Juju), cloud\n" + 33 "'type', and cloud 'regions'.\n" + 34 "The listing will consist of public clouds and any custom clouds made\n" + 35 "available through the `juju add-cloud` command. The former can be updated\n" + 36 "via the `juju update-cloud` command.\n" + 37 "By default, the tabular format is used.\n" + listCloudsDocExamples 38 39 var listCloudsDocExamples = ` 40 Examples: 41 42 juju clouds 43 44 See also: 45 add-cloud 46 show-cloud 47 update-clouds 48 ` 49 50 // NewListCloudsCommand returns a command to list cloud information. 51 func NewListCloudsCommand() cmd.Command { 52 return &listCloudsCommand{} 53 } 54 55 func (c *listCloudsCommand) Info() *cmd.Info { 56 return &cmd.Info{ 57 Name: "clouds", 58 Purpose: "Lists all clouds available to Juju.", 59 Doc: listCloudsDoc, 60 Aliases: []string{"list-clouds"}, 61 } 62 } 63 64 func (c *listCloudsCommand) SetFlags(f *gnuflag.FlagSet) { 65 c.CommandBase.SetFlags(f) 66 c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{ 67 "yaml": cmd.FormatYaml, 68 "json": cmd.FormatJson, 69 "tabular": formatCloudsTabular, 70 }) 71 } 72 73 func (c *listCloudsCommand) Run(ctxt *cmd.Context) error { 74 details, err := listCloudDetails() 75 if err != nil { 76 return err 77 } 78 79 var output interface{} 80 switch c.out.Name() { 81 case "yaml", "json": 82 output = details.all() 83 default: 84 output = details 85 } 86 err = c.out.Write(ctxt, output) 87 if err != nil { 88 return err 89 } 90 return nil 91 } 92 93 type cloudList struct { 94 public map[string]*cloudDetails 95 builtin map[string]*cloudDetails 96 personal map[string]*cloudDetails 97 } 98 99 func newCloudList() *cloudList { 100 return &cloudList{ 101 make(map[string]*cloudDetails), 102 make(map[string]*cloudDetails), 103 make(map[string]*cloudDetails), 104 } 105 } 106 107 func (c *cloudList) all() map[string]*cloudDetails { 108 if len(c.personal) == 0 && len(c.builtin) == 0 && len(c.personal) == 0 { 109 return nil 110 } 111 112 result := make(map[string]*cloudDetails) 113 addAll := func(someClouds map[string]*cloudDetails) { 114 for name, cloud := range someClouds { 115 result[name] = cloud 116 } 117 } 118 119 addAll(c.public) 120 addAll(c.builtin) 121 addAll(c.personal) 122 return result 123 } 124 125 func listCloudDetails() (*cloudList, error) { 126 clouds, _, err := jujucloud.PublicCloudMetadata(jujucloud.JujuPublicCloudsPath()) 127 if err != nil { 128 return nil, err 129 } 130 details := newCloudList() 131 for name, cloud := range clouds { 132 cloudDetails := makeCloudDetails(cloud) 133 details.public[name] = cloudDetails 134 } 135 136 // Add in built in clouds like localhost (lxd). 137 for name, cloud := range common.BuiltInClouds() { 138 cloudDetails := makeCloudDetails(cloud) 139 cloudDetails.Source = "built-in" 140 details.builtin[name] = cloudDetails 141 } 142 143 personalClouds, err := jujucloud.PersonalCloudMetadata() 144 if err != nil { 145 return nil, err 146 } 147 for name, cloud := range personalClouds { 148 cloudDetails := makeCloudDetails(cloud) 149 cloudDetails.Source = "local" 150 details.personal[name] = cloudDetails 151 // Delete any built-in or public clouds with same name. 152 delete(details.builtin, name) 153 delete(details.public, name) 154 } 155 156 return details, nil 157 } 158 159 // formatCloudsTabular writes a tabular summary of cloud information. 160 func formatCloudsTabular(writer io.Writer, value interface{}) error { 161 clouds, ok := value.(*cloudList) 162 if !ok { 163 return errors.Errorf("expected value of type %T, got %T", clouds, value) 164 } 165 166 tw := output.TabWriter(writer) 167 p := func(values ...string) { 168 text := strings.Join(values, "\t") 169 fmt.Fprintln(tw, text) 170 } 171 p("CLOUD\tTYPE\tREGIONS") 172 173 cloudNamesSorted := func(someClouds map[string]*cloudDetails) []string { 174 // For tabular we'll sort alphabetically, user clouds last. 175 var names []string 176 for name, _ := range someClouds { 177 names = append(names, name) 178 } 179 sort.Strings(names) 180 return names 181 } 182 183 printClouds := func(someClouds map[string]*cloudDetails) { 184 cloudNames := cloudNamesSorted(someClouds) 185 186 for _, name := range cloudNames { 187 info := someClouds[name] 188 var regions []string 189 for _, region := range info.Regions { 190 regions = append(regions, fmt.Sprint(region.Key)) 191 } 192 // TODO(wallyworld) - we should be smarter about handling 193 // long region text, for now we'll display the first 7 as 194 // that covers all clouds except AWS and Azure and will 195 // prevent wrapping on a reasonable terminal width. 196 regionCount := len(regions) 197 if regionCount > 7 { 198 regionCount = 7 199 } 200 regionText := strings.Join(regions[:regionCount], ", ") 201 if len(regions) > 7 { 202 regionText = regionText + " ..." 203 } 204 p(name, info.CloudType, regionText) 205 } 206 } 207 printClouds(clouds.public) 208 printClouds(clouds.builtin) 209 printClouds(clouds.personal) 210 211 tw.Flush() 212 return nil 213 }