github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"io"
     8  	"sort"
     9  
    10  	"github.com/juju/ansiterm"
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/gnuflag"
    14  	"github.com/juju/loggo"
    15  
    16  	jujucloud "github.com/juju/juju/cloud"
    17  	jujucmd "github.com/juju/juju/cmd"
    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  	"Output includes fundamental properties for each cloud known to the\n" +
    33  	"current Juju client: name, number of regions, default region, type,\n" +
    34  	"and description.\n" +
    35  	"\nThe default output shows public clouds known to Juju out of the box.\n" +
    36  	"These may change between Juju versions. In addition to these public\n" +
    37  	"clouds, the 'localhost' cloud (local LXD) is also listed.\n" +
    38  	"\nThis command's default output format is 'tabular'.\n" +
    39  	"\nCloud metadata sometimes changes, e.g. AWS adds a new region. Use the\n" +
    40  	"`update-clouds` command to update the current Juju client accordingly.\n" +
    41  	"\nUse the `add-cloud` command to add a private cloud to the list of\n" +
    42  	"clouds known to the current Juju client.\n" +
    43  	"\nUse the `regions` command to list a cloud's regions. Use the\n" +
    44  	"`show-cloud` command to get more detail, such as regions and endpoints.\n" +
    45  	"\nFurther reading: https://docs.jujucharms.com/stable/clouds\n" + listCloudsDocExamples
    46  
    47  var listCloudsDocExamples = `
    48  Examples:
    49  
    50      juju clouds
    51      juju clouds --format yaml
    52  
    53  See also:
    54      add-cloud
    55      regions
    56      show-cloud
    57      update-clouds
    58  `
    59  
    60  // NewListCloudsCommand returns a command to list cloud information.
    61  func NewListCloudsCommand() cmd.Command {
    62  	return &listCloudsCommand{}
    63  }
    64  
    65  func (c *listCloudsCommand) Info() *cmd.Info {
    66  	return jujucmd.Info(&cmd.Info{
    67  		Name:    "clouds",
    68  		Purpose: "Lists all clouds available to Juju.",
    69  		Doc:     listCloudsDoc,
    70  		Aliases: []string{"list-clouds"},
    71  	})
    72  }
    73  
    74  func (c *listCloudsCommand) SetFlags(f *gnuflag.FlagSet) {
    75  	c.CommandBase.SetFlags(f)
    76  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
    77  		"yaml":    cmd.FormatYaml,
    78  		"json":    cmd.FormatJson,
    79  		"tabular": formatCloudsTabular,
    80  	})
    81  }
    82  
    83  func (c *listCloudsCommand) Run(ctxt *cmd.Context) error {
    84  	details, err := listCloudDetails()
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	var output interface{}
    90  	switch c.out.Name() {
    91  	case "yaml", "json":
    92  		output = details.all()
    93  	default:
    94  		output = details
    95  	}
    96  	err = c.out.Write(ctxt, output)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	return nil
   101  }
   102  
   103  type cloudList struct {
   104  	public   map[string]*CloudDetails
   105  	builtin  map[string]*CloudDetails
   106  	personal map[string]*CloudDetails
   107  }
   108  
   109  func newCloudList() *cloudList {
   110  	return &cloudList{
   111  		make(map[string]*CloudDetails),
   112  		make(map[string]*CloudDetails),
   113  		make(map[string]*CloudDetails),
   114  	}
   115  }
   116  
   117  func (c *cloudList) all() map[string]*CloudDetails {
   118  	if len(c.personal) == 0 && len(c.builtin) == 0 && len(c.personal) == 0 {
   119  		return nil
   120  	}
   121  
   122  	result := make(map[string]*CloudDetails)
   123  	addAll := func(someClouds map[string]*CloudDetails) {
   124  		for name, cloud := range someClouds {
   125  			result[name] = cloud
   126  		}
   127  	}
   128  
   129  	addAll(c.public)
   130  	addAll(c.builtin)
   131  	addAll(c.personal)
   132  	return result
   133  }
   134  
   135  func listCloudDetails() (*cloudList, error) {
   136  	clouds, _, err := jujucloud.PublicCloudMetadata(jujucloud.JujuPublicCloudsPath())
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	details := newCloudList()
   141  	for name, cloud := range clouds {
   142  		cloudDetails := makeCloudDetails(cloud)
   143  		details.public[name] = cloudDetails
   144  	}
   145  
   146  	// Add in built in clouds like localhost (lxd).
   147  	builtinClouds, err := common.BuiltInClouds()
   148  	if err != nil {
   149  		return nil, errors.Trace(err)
   150  	}
   151  	for name, cloud := range builtinClouds {
   152  		cloudDetails := makeCloudDetails(cloud)
   153  		cloudDetails.Source = "built-in"
   154  		details.builtin[name] = cloudDetails
   155  	}
   156  
   157  	personalClouds, err := jujucloud.PersonalCloudMetadata()
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	for name, cloud := range personalClouds {
   162  		cloudDetails := makeCloudDetails(cloud)
   163  		cloudDetails.Source = "local"
   164  		details.personal[name] = cloudDetails
   165  		// Delete any built-in or public clouds with same name.
   166  		delete(details.builtin, name)
   167  		delete(details.public, name)
   168  	}
   169  
   170  	return details, nil
   171  }
   172  
   173  // formatCloudsTabular writes a tabular summary of cloud information.
   174  func formatCloudsTabular(writer io.Writer, value interface{}) error {
   175  	clouds, ok := value.(*cloudList)
   176  	if !ok {
   177  		return errors.Errorf("expected value of type %T, got %T", clouds, value)
   178  	}
   179  
   180  	tw := output.TabWriter(writer)
   181  	w := output.Wrapper{tw}
   182  	w.Println("Cloud", "Regions", "Default", "Type", "Description")
   183  	w.SetColumnAlignRight(1)
   184  
   185  	cloudNamesSorted := func(someClouds map[string]*CloudDetails) []string {
   186  		// For tabular we'll sort alphabetically, user clouds last.
   187  		var names []string
   188  		for name := range someClouds {
   189  			names = append(names, name)
   190  		}
   191  		sort.Strings(names)
   192  		return names
   193  	}
   194  
   195  	printClouds := func(someClouds map[string]*CloudDetails, color *ansiterm.Context) {
   196  		cloudNames := cloudNamesSorted(someClouds)
   197  
   198  		for _, name := range cloudNames {
   199  			info := someClouds[name]
   200  			defaultRegion := ""
   201  			if len(info.Regions) > 0 {
   202  				defaultRegion = info.RegionsMap[info.Regions[0].Key.(string)].Name
   203  			}
   204  			description := info.CloudDescription
   205  			if len(description) > 40 {
   206  				description = description[:39]
   207  			}
   208  			w.PrintColor(color, name)
   209  			w.Println(len(info.Regions), defaultRegion, info.CloudType, description)
   210  		}
   211  	}
   212  	printClouds(clouds.public, nil)
   213  	printClouds(clouds.builtin, nil)
   214  	printClouds(clouds.personal, ansiterm.Foreground(ansiterm.BrightBlue))
   215  
   216  	tw.Flush()
   217  	return nil
   218  }