github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/common/cloudcredential.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "fmt" 8 "sort" 9 "strings" 10 11 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/apiserver/params" 16 jujucloud "github.com/juju/juju/cloud" 17 "github.com/juju/juju/cmd/modelcmd" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/jujuclient" 20 ) 21 22 // ErrMultipleDetectedCredentials is the error returned by 23 // GetOrDetectCredential when multiple credentials are 24 // detected, meaning Juju cannot choose one automatically. 25 var ErrMultipleDetectedCredentials = errors.New("multiple detected credentials") 26 27 //go:generate mockgen -package common -destination credentialstore_mock_test.go github.com/juju/juju/jujuclient CredentialStore 28 29 // RegisterCredentials will attempt to register any credentials that a provider 30 // has to offer. 31 func RegisterCredentials( 32 ctx *cmd.Context, 33 store jujuclient.CredentialStore, 34 provider environs.EnvironProvider, 35 args modelcmd.RegisterCredentialsParams, 36 ) error { 37 credentials, err := modelcmd.RegisterCredentials(provider, args) 38 switch { 39 case errors.IsNotFound(err): 40 return nil 41 case err != nil: 42 return errors.Trace(err) 43 case credentials == nil: 44 return nil 45 } 46 47 ctx.Verbosef("updating credential store") 48 49 for name, credential := range credentials { 50 if err := store.UpdateCredential(name, *credential); err != nil { 51 return errors.Trace(err) 52 } 53 } 54 return nil 55 } 56 57 // GetOrDetectCredential returns a credential to use for given cloud. This 58 // function first calls modelcmd.GetCredentials, and returns its results if it 59 // finds credentials. If modelcmd.GetCredentials cannot find a credential, and a 60 // credential has not been specified by name, then this function will attempt to 61 // detect credentials from the environment. 62 // 63 // If multiple credentials are found in the client store, then 64 // modelcmd.ErrMultipleCredentials is returned. If multiple credentials are 65 // detected by the provider, then ErrMultipleDetectedCredentials is returned. 66 func GetOrDetectCredential( 67 ctx *cmd.Context, 68 store jujuclient.CredentialGetter, 69 provider environs.EnvironProvider, 70 args modelcmd.GetCredentialsParams, 71 ) (_ *jujucloud.Credential, chosenCredentialName, regionName string, isDetected bool, _ error) { 72 fail := func(err error) (*jujucloud.Credential, string, string, bool, error) { 73 return nil, "", "", false, err 74 } 75 credential, chosenCredentialName, regionName, err := modelcmd.GetCredentials(ctx, store, args) 76 if !errors.IsNotFound(err) || args.CredentialName != "" { 77 return credential, chosenCredentialName, regionName, false, err 78 } 79 80 // No credential was explicitly specified, and no credential was found 81 // in the credential store; have the provider detect credentials from 82 // the environment. 83 ctx.Verbosef("no credentials found, checking environment") 84 85 detected, err := modelcmd.DetectCredential(args.Cloud.Name, provider) 86 if errors.Cause(err) == modelcmd.ErrMultipleCredentials { 87 return fail(ErrMultipleDetectedCredentials) 88 } else if err != nil { 89 return fail(errors.Trace(err)) 90 } 91 92 // We have one credential so extract it from the map. 93 var oneCredential jujucloud.Credential 94 for chosenCredentialName, oneCredential = range detected.AuthCredentials { 95 } 96 regionName = args.CloudRegion 97 if regionName == "" { 98 regionName = detected.DefaultRegion 99 } 100 101 // Finalize the credential against the cloud/region. 102 region, err := ChooseCloudRegion(args.Cloud, regionName) 103 if err != nil { 104 return fail(err) 105 } 106 107 credential, err = modelcmd.FinalizeFileContent(&oneCredential, provider) 108 if err != nil { 109 return nil, "", "", false, modelcmd.AnnotateWithFinalizationError(err, chosenCredentialName, args.Cloud.Name) 110 } 111 112 credential, err = provider.FinalizeCredential( 113 ctx, environs.FinalizeCredentialParams{ 114 Credential: *credential, 115 CloudEndpoint: region.Endpoint, 116 CloudStorageEndpoint: region.StorageEndpoint, 117 CloudIdentityEndpoint: region.IdentityEndpoint, 118 }, 119 ) 120 if err != nil { 121 return fail(errors.Trace(err)) 122 } 123 return credential, chosenCredentialName, regionName, true, nil 124 } 125 126 // ResolveCloudCredentialTag takes a string which is of either the format 127 // "<credential>" or "<user>/<credential>". If the string does not include 128 // a user, then the supplied user tag is implied. 129 func ResolveCloudCredentialTag(user names.UserTag, cloud names.CloudTag, credentialName string) (names.CloudCredentialTag, error) { 130 if i := strings.IndexRune(credentialName, '/'); i == -1 { 131 credentialName = fmt.Sprintf("%s/%s", user.Id(), credentialName) 132 } 133 s := fmt.Sprintf("%s/%s", cloud.Id(), credentialName) 134 if !names.IsValidCloudCredential(s) { 135 return names.CloudCredentialTag{}, errors.NotValidf("cloud credential name %q", s) 136 } 137 return names.NewCloudCredentialTag(s), nil 138 } 139 140 // OutputUpdateCredentialModelResult prints detailed results of UpdateCredentialsCheckModels. 141 func OutputUpdateCredentialModelResult(ctx *cmd.Context, models []params.UpdateCredentialModelResult, showValid bool) { 142 var valid []string 143 invalid := map[string][]error{} 144 for _, m := range models { 145 if len(m.Errors) == 0 { 146 valid = append(valid, m.ModelName) 147 continue 148 } else { 149 var mError []error 150 for _, anErr := range m.Errors { 151 mError = append(mError, errors.Trace(anErr.Error)) 152 } 153 invalid[m.ModelName] = mError 154 } 155 } 156 157 if showValid && len(valid) > 0 { 158 ctx.Infof("Credential valid for:") 159 for _, v := range valid { 160 ctx.Infof(" %v", v) 161 } 162 } 163 if len(invalid) > 0 { 164 // ensure we sort the valid, invalid slices so that the output is consistent 165 i := 0 166 names := make([]string, len(invalid)) 167 for k := range invalid { 168 names[i] = k 169 i++ 170 } 171 sort.Strings(names) 172 173 ctx.Infof("Credential invalid for:") 174 for _, v := range names { 175 ctx.Infof(" %v:", v) 176 for _, e := range invalid[v] { 177 ctx.Infof(" %v", e) 178 } 179 } 180 } 181 } 182 183 //go:generate mockgen -package common -destination cloudprovider_mock_test.go github.com/juju/juju/cmd/juju/common TestCloudProvider 184 185 // TestCloudProvider is used for testing. 186 type TestCloudProvider interface { 187 environs.EnvironProvider 188 environs.ProviderCredentialsRegister 189 }