github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/cloud/listcredentials.go (about)

     1  // Copyright 2016 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  
    16  	jujucloud "github.com/juju/juju/cloud"
    17  	"github.com/juju/juju/cmd/juju/common"
    18  	"github.com/juju/juju/cmd/output"
    19  	"github.com/juju/juju/environs"
    20  	"github.com/juju/juju/jujuclient"
    21  )
    22  
    23  var usageListCredentialsSummary = `
    24  Lists credentials for a cloud.`[1:]
    25  
    26  var usageListCredentialsDetails = `
    27  Credentials are used with `[1:] + "`juju bootstrap`" + `  and ` + "`juju add-model`" + `.
    28  An arbitrary "credential name" is used to represent credentials, which are 
    29  added either via ` + "`juju add-credential` or `juju autoload-credentials`" + `.
    30  Note that there can be multiple sets of credentials and thus multiple 
    31  names.
    32  Actual authentication material is exposed with the '--show-secrets' 
    33  option.
    34  A controller and subsequently created models can be created with a 
    35  different set of credentials but any action taken within the model (e.g.:
    36  ` + "`juju deploy`; `juju add-unit`" + `) applies the set used to create the model. 
    37  Recall that when a controller is created a 'default' model is also 
    38  created.
    39  Credentials denoted with an asterisk '*' are currently set as the default
    40  for the given cloud.
    41  
    42  Examples:
    43      juju credentials
    44      juju credentials aws
    45      juju credentials --format yaml --show-secrets
    46  
    47  See also: 
    48      add-credential
    49      remove-credential
    50      set-default-credential
    51      autoload-credentials`
    52  
    53  type listCredentialsCommand struct {
    54  	cmd.CommandBase
    55  	out         cmd.Output
    56  	cloudName   string
    57  	showSecrets bool
    58  
    59  	store              jujuclient.CredentialGetter
    60  	personalCloudsFunc func() (map[string]jujucloud.Cloud, error)
    61  	cloudByNameFunc    func(string) (*jujucloud.Cloud, error)
    62  }
    63  
    64  // CloudCredential contains attributes used to define credentials for a cloud.
    65  type CloudCredential struct {
    66  	// DefaultCredential is the named credential to use by default.
    67  	DefaultCredential string `json:"default-credential,omitempty" yaml:"default-credential,omitempty"`
    68  
    69  	// DefaultRegion is the cloud region to use by default.
    70  	DefaultRegion string `json:"default-region,omitempty" yaml:"default-region,omitempty"`
    71  
    72  	// Credentials is the collection of all credentials registered by the user for a cloud, keyed on a cloud name.
    73  	Credentials map[string]Credential `json:"cloud-credentials,omitempty" yaml:",omitempty,inline"`
    74  }
    75  
    76  // Credential instances represent cloud credentials.
    77  type Credential struct {
    78  	// AuthType determines authentication type for the credential.
    79  	AuthType string `json:"auth-type" yaml:"auth-type"`
    80  
    81  	// Attributes define details for individual credential.
    82  	// This collection is provider-specific: each provider is interested in different credential details.
    83  	Attributes map[string]string `json:"details,omitempty" yaml:",omitempty,inline"`
    84  
    85  	// Revoked is true if the credential has been revoked.
    86  	Revoked bool `json:"revoked,omitempty" yaml:"revoked,omitempty"`
    87  
    88  	// Label is optionally set to describe the credentials to a user.
    89  	Label string `json:"label,omitempty" yaml:"label,omitempty"`
    90  }
    91  
    92  type credentialsMap struct {
    93  	Credentials map[string]CloudCredential `yaml:"credentials" json:"credentials"`
    94  }
    95  
    96  // NewListCredentialsCommand returns a command to list cloud credentials.
    97  func NewListCredentialsCommand() cmd.Command {
    98  	return &listCredentialsCommand{
    99  		store:           jujuclient.NewFileCredentialStore(),
   100  		cloudByNameFunc: jujucloud.CloudByName,
   101  	}
   102  }
   103  
   104  func (c *listCredentialsCommand) Info() *cmd.Info {
   105  	return &cmd.Info{
   106  		Name:    "credentials",
   107  		Args:    "[<cloud name>]",
   108  		Purpose: usageListCredentialsSummary,
   109  		Doc:     usageListCredentialsDetails,
   110  		Aliases: []string{"list-credentials"},
   111  	}
   112  }
   113  
   114  func (c *listCredentialsCommand) SetFlags(f *gnuflag.FlagSet) {
   115  	c.CommandBase.SetFlags(f)
   116  	f.BoolVar(&c.showSecrets, "show-secrets", false, "Show secrets")
   117  	c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{
   118  		"yaml":    cmd.FormatYaml,
   119  		"json":    cmd.FormatJson,
   120  		"tabular": formatCredentialsTabular,
   121  	})
   122  }
   123  
   124  func (c *listCredentialsCommand) Init(args []string) error {
   125  	cloudName, err := cmd.ZeroOrOneArgs(args)
   126  	if err != nil {
   127  		return errors.Trace(err)
   128  	}
   129  	c.cloudName = cloudName
   130  	return nil
   131  }
   132  
   133  func (c *listCredentialsCommand) personalClouds() (map[string]jujucloud.Cloud, error) {
   134  	if c.personalCloudsFunc == nil {
   135  		return jujucloud.PersonalCloudMetadata()
   136  	}
   137  	return c.personalCloudsFunc()
   138  }
   139  
   140  func (c *listCredentialsCommand) Run(ctxt *cmd.Context) error {
   141  	var credentials map[string]jujucloud.CloudCredential
   142  	credentials, err := c.store.AllCredentials()
   143  	if err != nil && !errors.IsNotFound(err) {
   144  		return err
   145  	}
   146  	if c.cloudName != "" {
   147  		for cloudName := range credentials {
   148  			if cloudName != c.cloudName {
   149  				delete(credentials, cloudName)
   150  			}
   151  		}
   152  	}
   153  
   154  	// Find local cloud names.
   155  	personalClouds, err := c.personalClouds()
   156  	if err != nil {
   157  		return err
   158  	}
   159  	var personalCloudNames []string
   160  	for name := range personalClouds {
   161  		personalCloudNames = append(personalCloudNames, name)
   162  	}
   163  
   164  	displayCredentials := make(map[string]CloudCredential)
   165  	for cloudName, cred := range credentials {
   166  		if !c.showSecrets {
   167  			if err := c.removeSecrets(cloudName, &cred); err != nil {
   168  				return errors.Annotatef(err, "removing secrets from credentials for cloud %v", cloudName)
   169  			}
   170  		}
   171  		displayCredential := CloudCredential{
   172  			DefaultCredential: cred.DefaultCredential,
   173  			DefaultRegion:     cred.DefaultRegion,
   174  		}
   175  		if len(cred.AuthCredentials) != 0 {
   176  			displayCredential.Credentials = make(map[string]Credential, len(cred.AuthCredentials))
   177  			for credName, credDetails := range cred.AuthCredentials {
   178  				displayCredential.Credentials[credName] = Credential{
   179  					string(credDetails.AuthType()),
   180  					credDetails.Attributes(),
   181  					credDetails.Revoked,
   182  					credDetails.Label,
   183  				}
   184  			}
   185  		}
   186  		displayCredentials[cloudName] = displayCredential
   187  	}
   188  	return c.out.Write(ctxt, credentialsMap{displayCredentials})
   189  }
   190  
   191  func (c *listCredentialsCommand) removeSecrets(cloudName string, cloudCred *jujucloud.CloudCredential) error {
   192  	cloud, err := common.CloudOrProvider(cloudName, c.cloudByNameFunc)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	provider, err := environs.Provider(cloud.Type)
   197  	if err != nil {
   198  		return err
   199  	}
   200  	schemas := provider.CredentialSchemas()
   201  	for name, cred := range cloudCred.AuthCredentials {
   202  		sanitisedCred, err := jujucloud.RemoveSecrets(cred, schemas)
   203  		if err != nil {
   204  			return err
   205  		}
   206  		cloudCred.AuthCredentials[name] = *sanitisedCred
   207  	}
   208  	return nil
   209  }
   210  
   211  // formatCredentialsTabular writes a tabular summary of cloud information.
   212  func formatCredentialsTabular(writer io.Writer, value interface{}) error {
   213  	credentials, ok := value.(credentialsMap)
   214  	if !ok {
   215  		return errors.Errorf("expected value of type %T, got %T", credentials, value)
   216  	}
   217  
   218  	if len(credentials.Credentials) == 0 {
   219  		fmt.Fprintln(writer, "No credentials to display.")
   220  		return nil
   221  	}
   222  
   223  	// For tabular we'll sort alphabetically by cloud, and then by credential name.
   224  	var cloudNames []string
   225  	for name := range credentials.Credentials {
   226  		cloudNames = append(cloudNames, name)
   227  	}
   228  	sort.Strings(cloudNames)
   229  
   230  	tw := output.TabWriter(writer)
   231  	p := func(values ...string) {
   232  		text := strings.Join(values, "\t")
   233  		fmt.Fprintln(tw, text)
   234  	}
   235  	p("CLOUD\tCREDENTIALS")
   236  	for _, cloudName := range cloudNames {
   237  		var haveDefault bool
   238  		var credentialNames []string
   239  		credentials := credentials.Credentials[cloudName]
   240  		for credentialName := range credentials.Credentials {
   241  			if credentialName == credentials.DefaultCredential {
   242  				credentialNames = append([]string{credentialName + "*"}, credentialNames...)
   243  				haveDefault = true
   244  			} else {
   245  				credentialNames = append(credentialNames, credentialName)
   246  			}
   247  		}
   248  		if haveDefault {
   249  			sort.Strings(credentialNames[1:])
   250  		} else {
   251  			sort.Strings(credentialNames)
   252  		}
   253  		p(cloudName, strings.Join(credentialNames, ", "))
   254  	}
   255  	tw.Flush()
   256  
   257  	return nil
   258  }