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 }