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  }