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 }