github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	// CloudName is the name of the cloud for which credentials are being
    30  	// obtained.
    31  	CloudName string
    32  
    33  	// CloudRegion is the name of the region that the user has specified.
    34  	// If this is empty, then GetCredentials will determine the default
    35  	// region, and return that. The default region is the one set by the
    36  	// user in credentials.yaml, or if there is none set, the first region
    37  	// in the cloud's list.
    38  	CloudRegion string
    39  
    40  	// CredentialName is the name of the credential to get.
    41  	CredentialName string
    42  }
    43  
    44  // GetCredentials returns a curated set of credential values for a given cloud.
    45  // The credential key values are read from the credentials store and the provider
    46  // finalises the values to resolve things like json files.
    47  // If region is not specified, the default credential region is used.
    48  func GetCredentials(
    49  	ctx *cmd.Context,
    50  	store jujuclient.CredentialGetter,
    51  	args GetCredentialsParams,
    52  ) (_ *cloud.Credential, chosenCredentialName, regionName string, _ error) {
    53  
    54  	credential, credentialName, defaultRegion, err := credentialByName(
    55  		store, args.CloudName, args.CredentialName,
    56  	)
    57  	if err != nil {
    58  		return nil, "", "", errors.Trace(err)
    59  	}
    60  
    61  	regionName = args.CloudRegion
    62  	if regionName == "" {
    63  		regionName = defaultRegion
    64  		if regionName == "" && len(args.Cloud.Regions) > 0 {
    65  			// No region was specified, use the first region
    66  			// in the list.
    67  			regionName = args.Cloud.Regions[0].Name
    68  		}
    69  	}
    70  
    71  	cloudEndpoint := args.Cloud.Endpoint
    72  	cloudIdentityEndpoint := args.Cloud.IdentityEndpoint
    73  	if regionName != "" {
    74  		region, err := cloud.RegionByName(args.Cloud.Regions, regionName)
    75  		if err != nil {
    76  			return nil, "", "", errors.Trace(err)
    77  		}
    78  		cloudEndpoint = region.Endpoint
    79  		cloudIdentityEndpoint = region.IdentityEndpoint
    80  	}
    81  
    82  	readFile := func(f string) ([]byte, error) {
    83  		f, err := utils.NormalizePath(f)
    84  		if err != nil {
    85  			return nil, errors.Trace(err)
    86  		}
    87  		return ioutil.ReadFile(f)
    88  	}
    89  
    90  	// Finalize credential against schemas supported by the provider.
    91  	provider, err := environs.Provider(args.Cloud.Type)
    92  	if err != nil {
    93  		return nil, "", "", errors.Trace(err)
    94  	}
    95  
    96  	credential, err = cloud.FinalizeCredential(
    97  		*credential, provider.CredentialSchemas(), readFile,
    98  	)
    99  	if err != nil {
   100  		return nil, "", "", errors.Annotatef(
   101  			err, "finalizing %q credential for cloud %q",
   102  			credentialName, args.CloudName,
   103  		)
   104  	}
   105  
   106  	credential, err = provider.FinalizeCredential(
   107  		ctx, environs.FinalizeCredentialParams{
   108  			Credential:            *credential,
   109  			CloudEndpoint:         cloudEndpoint,
   110  			CloudIdentityEndpoint: cloudIdentityEndpoint,
   111  		},
   112  	)
   113  	if err != nil {
   114  		return nil, "", "", errors.Annotatef(
   115  			err, "finalizing %q credential for cloud %q",
   116  			credentialName, args.CloudName,
   117  		)
   118  	}
   119  
   120  	return credential, credentialName, regionName, nil
   121  }
   122  
   123  // credentialByName returns the credential and default region to use for the
   124  // specified cloud, optionally specifying a credential name. If no credential
   125  // name is specified, then use the default credential for the cloud if one has
   126  // been specified. The credential name is returned also, in case the default
   127  // credential is used. If there is only one credential, it is implicitly the
   128  // default.
   129  //
   130  // If there exists no matching credentials, an error satisfying
   131  // errors.IsNotFound will be returned.
   132  func credentialByName(
   133  	store jujuclient.CredentialGetter, cloudName, credentialName string,
   134  ) (_ *cloud.Credential, credentialNameUsed string, defaultRegion string, _ error) {
   135  
   136  	cloudCredentials, err := store.CredentialForCloud(cloudName)
   137  	if err != nil {
   138  		return nil, "", "", errors.Annotate(err, "loading credentials")
   139  	}
   140  	if credentialName == "" {
   141  		credentialName = cloudCredentials.DefaultCredential
   142  		if credentialName == "" {
   143  			// No credential specified, but there's more than one.
   144  			if len(cloudCredentials.AuthCredentials) > 1 {
   145  				return nil, "", "", ErrMultipleCredentials
   146  			}
   147  			// No credential specified, so use the default for the cloud.
   148  			for credentialName = range cloudCredentials.AuthCredentials {
   149  			}
   150  		}
   151  	}
   152  	credential, ok := cloudCredentials.AuthCredentials[credentialName]
   153  	if !ok {
   154  		return nil, "", "", errors.NotFoundf(
   155  			"%q credential for cloud %q", credentialName, cloudName,
   156  		)
   157  	}
   158  	return &credential, credentialName, cloudCredentials.DefaultRegion, nil
   159  }
   160  
   161  // DetectCredential detects credentials for the specified cloud type, and, if
   162  // exactly one is detected, returns it.
   163  //
   164  // If no credentials are detected, an error satisfying errors.IsNotFound will
   165  // be returned. If more than one credential is detected, ErrMultipleCredentials
   166  // will be returned.
   167  func DetectCredential(cloudName, cloudType string) (*cloud.CloudCredential, error) {
   168  	provider, err := environs.Provider(cloudType)
   169  	if err != nil {
   170  		return nil, errors.Trace(err)
   171  	}
   172  	detected, err := provider.DetectCredentials()
   173  	if err != nil {
   174  		return nil, errors.Annotatef(
   175  			err, "detecting credentials for %q cloud provider", cloudName,
   176  		)
   177  	}
   178  	logger.Tracef("provider detected credentials: %v", detected)
   179  	if len(detected.AuthCredentials) == 0 {
   180  		return nil, errors.NotFoundf("credentials for cloud %q", cloudName)
   181  	}
   182  	if len(detected.AuthCredentials) > 1 {
   183  		return nil, ErrMultipleCredentials
   184  	}
   185  	return detected, nil
   186  }