github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"gopkg.in/amz.v3/aws"
    13  	"gopkg.in/amz.v3/ec2"
    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  	// The endpoints in public-clouds.yaml from 2.0-rc2
    38  	// and before were wrong, so we use whatever is defined
    39  	// in goamz/aws if available.
    40  	if isBrokenCloud(e.cloud) {
    41  		if region, ok := aws.Regions[e.cloud.Region]; ok {
    42  			e.cloud.Endpoint = region.EC2Endpoint
    43  		}
    44  	}
    45  
    46  	var err error
    47  	e.ec2, err = awsClient(e.cloud)
    48  	if err != nil {
    49  		return nil, errors.Trace(err)
    50  	}
    51  
    52  	if err := e.SetConfig(args.Config); err != nil {
    53  		return nil, errors.Trace(err)
    54  	}
    55  	return e, nil
    56  }
    57  
    58  // isBrokenCloud reports whether the given CloudSpec is from an old,
    59  // broken version of public-clouds.yaml.
    60  func isBrokenCloud(cloud environs.CloudSpec) bool {
    61  	// The public-clouds.yaml from 2.0-rc2 and before was
    62  	// complete nonsense for general regions and for
    63  	// govcloud. The cn-north-1 region has a trailing slash,
    64  	// which we don't want as it means we won't match the
    65  	// simplestreams data.
    66  	switch cloud.Region {
    67  	case "us-east-1", "us-west-1", "us-west-2", "eu-west-1",
    68  		"eu-central-1", "ap-southeast-1", "ap-southeast-2",
    69  		"ap-northeast-1", "ap-northeast-2", "sa-east-1":
    70  		return cloud.Endpoint == fmt.Sprintf("https://%s.aws.amazon.com/v1.2/", cloud.Region)
    71  	case "cn-north-1":
    72  		return strings.HasSuffix(cloud.Endpoint, "/")
    73  	case "us-gov-west-1":
    74  		return cloud.Endpoint == "https://ec2.us-gov-west-1.amazonaws-govcloud.com"
    75  	}
    76  	return false
    77  }
    78  
    79  func awsClient(cloud environs.CloudSpec) (*ec2.EC2, error) {
    80  	if err := validateCloudSpec(cloud); err != nil {
    81  		return nil, errors.Annotate(err, "validating cloud spec")
    82  	}
    83  
    84  	credentialAttrs := cloud.Credential.Attributes()
    85  	accessKey := credentialAttrs["access-key"]
    86  	secretKey := credentialAttrs["secret-key"]
    87  	auth := aws.Auth{
    88  		AccessKey: accessKey,
    89  		SecretKey: secretKey,
    90  	}
    91  
    92  	region := aws.Region{
    93  		Name:        cloud.Region,
    94  		EC2Endpoint: cloud.Endpoint,
    95  	}
    96  	signer := aws.SignV4Factory(cloud.Region, "ec2")
    97  	return ec2.New(auth, region, signer), nil
    98  }
    99  
   100  // PrepareConfig is specified in the EnvironProvider interface.
   101  func (p environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
   102  	if err := validateCloudSpec(args.Cloud); err != nil {
   103  		return nil, errors.Annotate(err, "validating cloud spec")
   104  	}
   105  	// Set the default block-storage source.
   106  	attrs := make(map[string]interface{})
   107  	if _, ok := args.Config.StorageDefaultBlockSource(); !ok {
   108  		attrs[config.StorageDefaultBlockSourceKey] = EBS_ProviderType
   109  	}
   110  	if len(attrs) == 0 {
   111  		return args.Config, nil
   112  	}
   113  	return args.Config.Apply(attrs)
   114  }
   115  
   116  func validateCloudSpec(c environs.CloudSpec) error {
   117  	if err := c.Validate(); err != nil {
   118  		return errors.Trace(err)
   119  	}
   120  	if c.Credential == nil {
   121  		return errors.NotValidf("missing credential")
   122  	}
   123  	if authType := c.Credential.AuthType(); authType != cloud.AccessKeyAuthType {
   124  		return errors.NotSupportedf("%q auth-type", authType)
   125  	}
   126  	return nil
   127  }
   128  
   129  // Validate is specified in the EnvironProvider interface.
   130  func (environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
   131  	newEcfg, err := validateConfig(cfg, old)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("invalid EC2 provider config: %v", err)
   134  	}
   135  	return newEcfg.Apply(newEcfg.attrs)
   136  }
   137  
   138  // MetadataLookupParams returns parameters which are used to query image metadata to
   139  // find matching image information.
   140  func (p environProvider) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) {
   141  	if region == "" {
   142  		return nil, fmt.Errorf("region must be specified")
   143  	}
   144  	ec2Region, ok := aws.Regions[region]
   145  	if !ok {
   146  		return nil, fmt.Errorf("unknown region %q", region)
   147  	}
   148  	return &simplestreams.MetadataLookupParams{
   149  		Region:   region,
   150  		Endpoint: ec2Region.EC2Endpoint,
   151  	}, nil
   152  }
   153  
   154  const badAccessKey = `
   155  Please ensure the Access Key ID you have specified is correct.
   156  You can obtain the Access Key ID via the "Security Credentials"
   157  page in the AWS console.`
   158  
   159  const badSecretKey = `
   160  Please ensure the Secret Access Key you have specified is correct.
   161  You can obtain the Secret Access Key via the "Security Credentials"
   162  page in the AWS console.`
   163  
   164  // verifyCredentials issues a cheap, non-modifying/idempotent request to EC2 to
   165  // verify the configured credentials. If verification fails, a user-friendly
   166  // error will be returned, and the original error will be logged at debug
   167  // level.
   168  var verifyCredentials = func(e *environ) error {
   169  	_, err := e.ec2.AccountAttributes()
   170  	if err != nil {
   171  		logger.Debugf("ec2 request failed: %v", err)
   172  		if err, ok := err.(*ec2.Error); ok {
   173  			switch err.Code {
   174  			case "AuthFailure":
   175  				return errors.New("authentication failed.\n" + badAccessKey)
   176  			case "SignatureDoesNotMatch":
   177  				return errors.New("authentication failed.\n" + badSecretKey)
   178  			default:
   179  				return err
   180  			}
   181  		}
   182  		return err
   183  	}
   184  	return nil
   185  }