github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/modelcmd/credentials.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelcmd 5 6 import ( 7 "io/ioutil" 8 9 "github.com/juju/cmd" 10 "github.com/juju/errors" 11 "github.com/juju/utils" 12 13 "github.com/juju/juju/cloud" 14 "github.com/juju/juju/environs" 15 "github.com/juju/juju/jujuclient" 16 ) 17 18 var ( 19 // ErrMultipleCredentials is the error returned by DetectCredential 20 // if more than one credential is detected. 21 ErrMultipleCredentials = errors.New("more than one credential detected") 22 ) 23 24 // GetCredentialsParams contains parameters for the GetCredentials function. 25 type GetCredentialsParams struct { 26 // Cloud is the cloud definition. 27 Cloud cloud.Cloud 28 29 // CloudRegion is the name of the region that the user has specified. 30 // If this is empty, then GetCredentials will determine the default 31 // region, and return that. The default region is the one set by the 32 // user in credentials.yaml, or if there is none set, the first region 33 // in the cloud's list. 34 CloudRegion string 35 36 // CredentialName is the name of the credential to get. 37 CredentialName string 38 } 39 40 // GetCredentials returns a curated set of credential values for a given cloud. 41 // The credential key values are read from the credentials store and the provider 42 // finalises the values to resolve things like json files. 43 // If region is not specified, the default credential region is used. 44 func GetCredentials( 45 ctx *cmd.Context, 46 store jujuclient.CredentialGetter, 47 args GetCredentialsParams, 48 ) (_ *cloud.Credential, chosenCredentialName, regionName string, _ error) { 49 50 credential, credentialName, defaultRegion, err := credentialByName( 51 store, args.Cloud.Name, args.CredentialName, 52 ) 53 if err != nil { 54 return nil, "", "", errors.Trace(err) 55 } 56 regionName = args.CloudRegion 57 if regionName == "" { 58 regionName = defaultRegion 59 } 60 61 cloudEndpoint := args.Cloud.Endpoint 62 cloudStorageEndpoint := args.Cloud.StorageEndpoint 63 cloudIdentityEndpoint := args.Cloud.IdentityEndpoint 64 if regionName != "" { 65 region, err := cloud.RegionByName(args.Cloud.Regions, regionName) 66 if err != nil { 67 return nil, "", "", errors.Trace(err) 68 } 69 cloudEndpoint = region.Endpoint 70 cloudStorageEndpoint = region.StorageEndpoint 71 cloudIdentityEndpoint = region.IdentityEndpoint 72 } 73 74 // Finalize credential against schemas supported by the provider. 75 provider, err := environs.Provider(args.Cloud.Type) 76 if err != nil { 77 return nil, "", "", errors.Trace(err) 78 } 79 80 credential, err = FinalizeFileContent(credential, provider) 81 if err != nil { 82 return nil, "", "", AnnotateWithFinalizationError(err, credentialName, args.Cloud.Name) 83 } 84 85 credential, err = provider.FinalizeCredential( 86 ctx, environs.FinalizeCredentialParams{ 87 Credential: *credential, 88 CloudEndpoint: cloudEndpoint, 89 CloudStorageEndpoint: cloudStorageEndpoint, 90 CloudIdentityEndpoint: cloudIdentityEndpoint, 91 }, 92 ) 93 if err != nil { 94 return nil, "", "", AnnotateWithFinalizationError(err, credentialName, args.Cloud.Name) 95 } 96 97 return credential, credentialName, regionName, nil 98 } 99 100 // FinalizeFileContent replaces the path content of cloud credentials "file" attribute with its content. 101 func FinalizeFileContent(credential *cloud.Credential, provider environs.EnvironProvider) (*cloud.Credential, error) { 102 readFile := func(f string) ([]byte, error) { 103 f, err := utils.NormalizePath(f) 104 if err != nil { 105 return nil, errors.Trace(err) 106 } 107 return ioutil.ReadFile(f) 108 } 109 110 var err error 111 credential, err = cloud.FinalizeCredential( 112 *credential, provider.CredentialSchemas(), readFile, 113 ) 114 if err != nil { 115 return nil, err 116 } 117 118 return credential, nil 119 } 120 121 func AnnotateWithFinalizationError(err error, credentialName, cloudName string) error { 122 return errors.Annotatef( 123 err, "finalizing %q credential for cloud %q", 124 credentialName, cloudName, 125 ) 126 } 127 128 // credentialByName returns the credential and default region to use for the 129 // specified cloud, optionally specifying a credential name. If no credential 130 // name is specified, then use the default credential for the cloud if one has 131 // been specified. The credential name is returned also, in case the default 132 // credential is used. If there is only one credential, it is implicitly the 133 // default. 134 // 135 // If there exists no matching credentials, an error satisfying 136 // errors.IsNotFound will be returned. 137 func credentialByName( 138 store jujuclient.CredentialGetter, cloudName, credentialName string, 139 ) (_ *cloud.Credential, credentialNameUsed string, defaultRegion string, _ error) { 140 141 cloudCredentials, err := store.CredentialForCloud(cloudName) 142 if err != nil { 143 return nil, "", "", errors.Annotate(err, "loading credentials") 144 } 145 if credentialName == "" { 146 credentialName = cloudCredentials.DefaultCredential 147 if credentialName == "" { 148 // No credential specified, but there's more than one. 149 if len(cloudCredentials.AuthCredentials) > 1 { 150 return nil, "", "", ErrMultipleCredentials 151 } 152 // No credential specified, so use the default for the cloud. 153 for credentialName = range cloudCredentials.AuthCredentials { 154 } 155 } 156 } 157 credential, ok := cloudCredentials.AuthCredentials[credentialName] 158 if !ok { 159 return nil, "", "", errors.NotFoundf( 160 "%q credential for cloud %q", credentialName, cloudName, 161 ) 162 } 163 return &credential, credentialName, cloudCredentials.DefaultRegion, nil 164 } 165 166 // DetectCredential detects credentials for the specified cloud type, and, if 167 // exactly one is detected, returns it. 168 // 169 // If no credentials are detected, an error satisfying errors.IsNotFound will 170 // be returned. If more than one credential is detected, ErrMultipleCredentials 171 // will be returned. 172 func DetectCredential(cloudName string, provider environs.EnvironProvider) (*cloud.CloudCredential, error) { 173 detected, err := provider.DetectCredentials() 174 if err != nil { 175 return nil, errors.Annotatef( 176 err, "detecting credentials for %q cloud provider", cloudName, 177 ) 178 } 179 logger.Tracef("provider detected credentials: %v", detected) 180 if len(detected.AuthCredentials) == 0 { 181 return nil, errors.NotFoundf("credentials for cloud %q", cloudName) 182 } 183 if len(detected.AuthCredentials) > 1 { 184 return nil, ErrMultipleCredentials 185 } 186 return detected, nil 187 } 188 189 // RegisterCredentialsParams contains parameters for the RegisterCredentials function. 190 type RegisterCredentialsParams struct { 191 // Cloud is the cloud definition. 192 Cloud cloud.Cloud 193 } 194 195 // RegisterCredentials returns any credentials that need to be registered for 196 // a provider. 197 func RegisterCredentials(provider environs.EnvironProvider, args RegisterCredentialsParams) (map[string]*cloud.CloudCredential, error) { 198 if register, ok := provider.(environs.ProviderCredentialsRegister); ok { 199 found, err := register.RegisterCredentials(args.Cloud) 200 if err != nil { 201 return nil, errors.Annotatef( 202 err, "registering credentials for provider", 203 ) 204 } 205 logger.Tracef("provider registered credentials: %v", found) 206 if len(found) == 0 { 207 return nil, errors.NotFoundf("credentials for provider") 208 } 209 return found, errors.Trace(err) 210 } 211 return nil, nil 212 } 213 214 //go:generate mockgen -package modelcmd -destination cloudprovider_mock_test.go github.com/juju/juju/cmd/modelcmd TestCloudProvider 215 216 // TestCloudProvider is used for testing. 217 type TestCloudProvider interface { 218 environs.EnvironProvider 219 environs.ProviderCredentialsRegister 220 }