github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "bytes" 8 "fmt" 9 "sort" 10 "strings" 11 "text/tabwriter" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "launchpad.net/gnuflag" 17 18 jujucloud "github.com/juju/juju/cloud" 19 "github.com/juju/juju/cmd/juju/common" 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 list-clouds 43 44 See also: show-cloud 45 update-clouds 46 add-cloud 47 ` 48 49 // NewListCloudsCommand returns a command to list cloud information. 50 func NewListCloudsCommand() cmd.Command { 51 return &listCloudsCommand{} 52 } 53 54 func (c *listCloudsCommand) Info() *cmd.Info { 55 return &cmd.Info{ 56 Name: "list-clouds", 57 Purpose: "Lists all clouds available to Juju.", 58 Doc: listCloudsDoc, 59 } 60 } 61 62 func (c *listCloudsCommand) SetFlags(f *gnuflag.FlagSet) { 63 c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{ 64 "yaml": cmd.FormatYaml, 65 "json": cmd.FormatJson, 66 "tabular": formatCloudsTabular, 67 }) 68 } 69 70 const localPrefix = "local:" 71 72 func (c *listCloudsCommand) Run(ctxt *cmd.Context) error { 73 details, err := getCloudDetails() 74 if err != nil { 75 return err 76 } 77 return c.out.Write(ctxt, details) 78 } 79 80 func getCloudDetails() (map[string]*cloudDetails, error) { 81 clouds, _, err := jujucloud.PublicCloudMetadata(jujucloud.JujuPublicCloudsPath()) 82 if err != nil { 83 return nil, err 84 } 85 details := make(map[string]*cloudDetails) 86 for name, cloud := range clouds { 87 cloudDetails := makeCloudDetails(cloud) 88 details[name] = cloudDetails 89 } 90 91 // Add in built in providers like "lxd" and "manual". 92 for name, cloud := range common.BuiltInProviders() { 93 cloudDetails := makeCloudDetails(cloud) 94 cloudDetails.Source = "built-in" 95 details[name] = cloudDetails 96 } 97 98 personalClouds, err := jujucloud.PersonalCloudMetadata() 99 if err != nil { 100 return nil, err 101 } 102 for name, cloud := range personalClouds { 103 // Add to result with "local:" prefix. 104 cloudDetails := makeCloudDetails(cloud) 105 cloudDetails.Source = "local" 106 details[localPrefix+name] = cloudDetails 107 } 108 return details, nil 109 } 110 111 // Public clouds sorted first, then personal ie has a prefix of "local:". 112 type cloudSourceOrder []string 113 114 func (a cloudSourceOrder) Len() int { return len(a) } 115 func (a cloudSourceOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 116 func (a cloudSourceOrder) Less(i, j int) bool { 117 isLeftLocal := strings.HasPrefix(a[i], localPrefix) 118 isRightLocal := strings.HasPrefix(a[j], localPrefix) 119 if isLeftLocal == isRightLocal { 120 return a[i] < a[j] 121 } 122 return isRightLocal 123 } 124 125 // formatCloudsTabular returns a tabular summary of cloud information. 126 func formatCloudsTabular(value interface{}) ([]byte, error) { 127 clouds, ok := value.(map[string]*cloudDetails) 128 if !ok { 129 return nil, errors.Errorf("expected value of type %T, got %T", clouds, value) 130 } 131 132 // For tabular we'll sort alphabetically, user clouds last. 133 var cloudNames []string 134 for name, _ := range clouds { 135 cloudNames = append(cloudNames, name) 136 } 137 sort.Sort(cloudSourceOrder(cloudNames)) 138 139 var out bytes.Buffer 140 const ( 141 // To format things into columns. 142 minwidth = 0 143 tabwidth = 1 144 padding = 2 145 padchar = ' ' 146 flags = 0 147 ) 148 tw := tabwriter.NewWriter(&out, minwidth, tabwidth, padding, padchar, flags) 149 p := func(values ...string) { 150 text := strings.Join(values, "\t") 151 fmt.Fprintln(tw, text) 152 } 153 p("CLOUD\tTYPE\tREGIONS") 154 for _, name := range cloudNames { 155 info := clouds[name] 156 var regions []string 157 for _, region := range info.Regions { 158 regions = append(regions, fmt.Sprint(region.Key)) 159 } 160 // TODO(wallyworld) - we should be smarter about handling 161 // long region text, for now we'll display the first 7 as 162 // that covers all clouds except AWS and Azure and will 163 // prevent wrapping on a reasonable terminal width. 164 regionCount := len(regions) 165 if regionCount > 7 { 166 regionCount = 7 167 } 168 regionText := strings.Join(regions[:regionCount], ", ") 169 if len(regions) > 7 { 170 regionText = regionText + " ..." 171 } 172 p(name, info.CloudType, regionText) 173 } 174 tw.Flush() 175 176 return out.Bytes(), nil 177 }