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  }