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