github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/ec2/provider.go (about)

     1  // Copyright 2011-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ec2
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"gopkg.in/amz.v3/aws"
    12  	"gopkg.in/amz.v3/ec2"
    13  	"gopkg.in/amz.v3/s3"
    14  
    15  	"github.com/juju/juju/cloud"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/environs/simplestreams"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.provider.ec2")
    22  
    23  type environProvider struct {
    24  	environProviderCredentials
    25  }
    26  
    27  var providerInstance environProvider
    28  
    29  // Open is specified in the EnvironProvider interface.
    30  func (p environProvider) Open(args environs.OpenParams) (environs.Environ, error) {
    31  	logger.Infof("opening model %q", args.Config.Name())
    32  
    33  	e := new(environ)
    34  	e.cloud = args.Cloud
    35  	e.name = args.Config.Name()
    36  
    37  	var err error
    38  	e.ec2, e.s3, err = awsClients(args.Cloud)
    39  	if err != nil {
    40  		return nil, errors.Trace(err)
    41  	}
    42  
    43  	if err := e.SetConfig(args.Config); err != nil {
    44  		return nil, errors.Trace(err)
    45  	}
    46  	return e, nil
    47  }
    48  
    49  func awsClients(cloud environs.CloudSpec) (*ec2.EC2, *s3.S3, error) {
    50  	if err := validateCloudSpec(cloud); err != nil {
    51  		return nil, nil, errors.Annotate(err, "validating cloud spec")
    52  	}
    53  
    54  	credentialAttrs := cloud.Credential.Attributes()
    55  	accessKey := credentialAttrs["access-key"]
    56  	secretKey := credentialAttrs["secret-key"]
    57  	auth := aws.Auth{
    58  		AccessKey: accessKey,
    59  		SecretKey: secretKey,
    60  	}
    61  
    62  	// TODO(axw) define region in terms of EC2 and S3 endpoints.
    63  	region := aws.Regions[cloud.Region]
    64  	signer := aws.SignV4Factory(region.Name, "ec2")
    65  	return ec2.New(auth, region, signer), s3.New(auth, region), nil
    66  }
    67  
    68  // PrepareConfig is specified in the EnvironProvider interface.
    69  func (p environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
    70  	if err := validateCloudSpec(args.Cloud); err != nil {
    71  		return nil, errors.Annotate(err, "validating cloud spec")
    72  	}
    73  	// Set the default block-storage source.
    74  	attrs := make(map[string]interface{})
    75  	if _, ok := args.Config.StorageDefaultBlockSource(); !ok {
    76  		attrs[config.StorageDefaultBlockSourceKey] = EBS_ProviderType
    77  	}
    78  	if len(attrs) == 0 {
    79  		return args.Config, nil
    80  	}
    81  	return args.Config.Apply(attrs)
    82  }
    83  
    84  func validateCloudSpec(c environs.CloudSpec) error {
    85  	if err := c.Validate(); err != nil {
    86  		return errors.Trace(err)
    87  	}
    88  	if _, ok := aws.Regions[c.Region]; !ok {
    89  		return errors.NotValidf("region name %q", c.Region)
    90  	}
    91  	if c.Credential == nil {
    92  		return errors.NotValidf("missing credential")
    93  	}
    94  	if authType := c.Credential.AuthType(); authType != cloud.AccessKeyAuthType {
    95  		return errors.NotSupportedf("%q auth-type", authType)
    96  	}
    97  	return nil
    98  }
    99  
   100  // Validate is specified in the EnvironProvider interface.
   101  func (environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
   102  	newEcfg, err := validateConfig(cfg, old)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("invalid EC2 provider config: %v", err)
   105  	}
   106  	return newEcfg.Apply(newEcfg.attrs)
   107  }
   108  
   109  // MetadataLookupParams returns parameters which are used to query image metadata to
   110  // find matching image information.
   111  func (p environProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   112  	if region == "" {
   113  		return nil, fmt.Errorf("region must be specified")
   114  	}
   115  	ec2Region, ok := allRegions[region]
   116  	if !ok {
   117  		return nil, fmt.Errorf("unknown region %q", region)
   118  	}
   119  	return &simplestreams.MetadataLookupParams{
   120  		Region:   region,
   121  		Endpoint: ec2Region.EC2Endpoint,
   122  	}, nil
   123  }
   124  
   125  const badAccessKey = `
   126  Please ensure the Access Key ID you have specified is correct.
   127  You can obtain the Access Key ID via the "Security Credentials"
   128  page in the AWS console.`
   129  
   130  const badSecretKey = `
   131  Please ensure the Secret Access Key you have specified is correct.
   132  You can obtain the Secret Access Key via the "Security Credentials"
   133  page in the AWS console.`
   134  
   135  // verifyCredentials issues a cheap, non-modifying/idempotent request to EC2 to
   136  // verify the configured credentials. If verification fails, a user-friendly
   137  // error will be returned, and the original error will be logged at debug
   138  // level.
   139  var verifyCredentials = func(e *environ) error {
   140  	_, err := e.ec2.AccountAttributes()
   141  	if err != nil {
   142  		logger.Debugf("ec2 request failed: %v", err)
   143  		if err, ok := err.(*ec2.Error); ok {
   144  			switch err.Code {
   145  			case "AuthFailure":
   146  				return errors.New("authentication failed.\n" + badAccessKey)
   147  			case "SignatureDoesNotMatch":
   148  				return errors.New("authentication failed.\n" + badSecretKey)
   149  			default:
   150  				return err
   151  			}
   152  		}
   153  		return err
   154  	}
   155  	return nil
   156  }