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  }