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  }