Merge pull request #2828 from jeanlaurent/aws-vpc-id

ec2 Don't force user to use VPC_ID but use the default one when available
This commit is contained in:
Jean-Laurent de Morlhon 2016-01-14 12:11:50 +01:00
commit fa4f5edf9b
4 changed files with 202 additions and 35 deletions

View File

@ -10,26 +10,16 @@ parent="smn_machine_drivers"
# Amazon Web Services
Create machines on [Amazon Web Services](http://aws.amazon.com). To create machines on [Amazon Web Services](http://aws.amazon.com), you must supply three required parameters:
Create machines on [Amazon Web Services](http://aws.amazon.com). To create machines on [Amazon Web Services](http://aws.amazon.com), you must supply two required parameters:
- Access Key ID
- Secret Access Key
- VPC ID
Obtain your IDs and Keys from AWS. To find the VPC ID:
1. Login to the AWS console
2. Go to **Services -> VPC -> Your VPCs**.
3. Locate the VPC ID you want from the _VPC_ column.
4. Go to **Services -> VPC -> Subnets**. Examine the _Availability Zone_ column to verify that zone `a` exists and matches your VPC ID.
For example, `us-east1-a` is in the `a` availability zone. If the `a` zone is not present, you can create a new subnet in that zone or specify a different zone when you create the machine.
Obtain your IDs and Keys from AWS.
To create the machine instance, specify `--driver amazonec2` and the three required parameters.
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C********* --amazonec2-vpc-id vpc-****** aws01
This example assumes the VPC ID was found in the `a` availability zone. Use the`--amazonec2-zone` flag to specify a zone other than the `a` zone. For example, `--amazonec2-zone c` signifies `us-east1-c`.
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C******* aws01
## Options
@ -38,7 +28,7 @@ This example assumes the VPC ID was found in the `a` availability zone. Use the`
- `--amazonec2-session-token`: Your session token for the Amazon Web Services API.
- `--amazonec2-ami`: The AMI ID of the instance to use.
- `--amazonec2-region`: The region to use when launching the instance.
- `--amazonec2-vpc-id`: **required** Your VPC ID to launch the instance in.
- `--amazonec2-vpc-id`: Your VPC ID to launch the instance in.
- `--amazonec2-zone`: The AWS zone to launch the instance in (i.e. one of a,b,c,d,e).
- `--amazonec2-subnet-id`: AWS VPC subnet id.
- `--amazonec2-security-group`: AWS VPC security group name.
@ -81,7 +71,7 @@ Environment variables and default values:
| `--amazonec2-session-token` | `AWS_SESSION_TOKEN` | - |
| `--amazonec2-ami` | `AWS_AMI` | `ami-5f709f34` |
| `--amazonec2-region` | `AWS_DEFAULT_REGION` | `us-east-1` |
| **`--amazonec2-vpc-id`** | `AWS_VPC_ID` | - |
| `--amazonec2-vpc-id` | `AWS_VPC_ID` | - |
| `--amazonec2-zone` | `AWS_ZONE` | `a` |
| `--amazonec2-subnet-id` | `AWS_SUBNET_ID` | - |
| `--amazonec2-security-group` | `AWS_SECURITY_GROUP` | `docker-machine` |
@ -109,3 +99,23 @@ Note that a security group will be created and associated to the host. This secu
If you specify a security group yourself using the `--amazonec2-security-group` flag, the above ports will be checked and opened and the security group modified.
If you want more ports to be opened, like application specific ports, use the aws console and modify the configuration manually.
## VPC ID
We determine your default vpc id at the start of a command.
In some cases, either because your account does not have a default vpc, or you don't want to use the default one, you can specify a vpc with the `--amazonec2-vpc-id` flag.
To find the VPC ID:
1. Login to the AWS console
2. Go to **Services -> VPC -> Your VPCs**.
3. Locate the VPC ID you want from the _VPC_ column.
4. Go to **Services -> VPC -> Subnets**. Examine the _Availability Zone_ column to verify that zone `a` exists and matches your VPC ID.
For example, `us-east1-a` is in the `a` availability zone. If the `a` zone is not present, you can create a new subnet in that zone or specify a different zone when you create the machine.
To create a machine with a non-default vpc-id:
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C********* --amazonec2-vpc-id vpc-****** aws02
This example assumes the VPC ID was found in the `a` availability zone. Use the`--amazonec2-zone` flag to specify a zone other than the `a` zone. For example, `--amazonec2-zone c` signifies `us-east1-c`.

View File

@ -3,6 +3,7 @@ package amazonec2
import (
"crypto/md5"
"crypto/rand"
"errors"
"fmt"
"io"
"io/ioutil"
@ -46,12 +47,16 @@ const (
)
var (
dockerPort = 2376
swarmPort = 3376
dockerPort = 2376
swarmPort = 3376
errorMissingAccessKeyOption = errors.New("amazonec2 driver requires the --amazonec2-access-key option")
errorMissingSecretKeyOption = errors.New("amazonec2 driver requires the --amazonec2-secret-key option")
errorNoVPCIdFound = errors.New("amazonec2 driver requires either the --amazonec2-subnet-id or --amazonec2-vpc-id option or an AWS Account with a default vpc-id")
)
type Driver struct {
*drivers.BaseDriver
clientFactory func() Ec2Client
Id string
AccessKey string
SecretKey string
@ -83,6 +88,10 @@ type Driver struct {
Monitoring bool
}
type clientFactory interface {
build(d *Driver) Ec2Client
}
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
return []mcnflag.Flag{
mcnflag.StringFlag{
@ -201,9 +210,9 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
}
}
func NewDriver(hostName, storePath string) drivers.Driver {
func NewDriver(hostName, storePath string) *Driver {
id := generateId()
return &Driver{
driver := &Driver{
Id: id,
AMI: defaultAmiId,
Region: defaultRegion,
@ -218,6 +227,24 @@ func NewDriver(hostName, storePath string) drivers.Driver {
StorePath: storePath,
},
}
driver.clientFactory = driver.buildClient
return driver
}
func (d *Driver) buildClient() Ec2Client {
config := aws.NewConfig()
alogger := AwsLogger()
config = config.WithRegion(d.Region)
config = config.WithCredentials(credentials.NewStaticCredentials(d.AccessKey, d.SecretKey, d.SessionToken))
config = config.WithLogger(alogger)
config = config.WithLogLevel(aws.LogDebugWithHTTPBody)
return ec2.New(session.New(config))
}
func (d *Driver) getClient() Ec2Client {
return d.clientFactory()
}
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
@ -258,15 +285,22 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
d.SetSwarmConfigFromFlags(flags)
if d.AccessKey == "" {
return fmt.Errorf("amazonec2 driver requires the --amazonec2-access-key option")
return errorMissingAccessKeyOption
}
if d.SecretKey == "" {
return fmt.Errorf("amazonec2 driver requires the --amazonec2-secret-key option")
return errorMissingSecretKeyOption
}
if d.VpcId == "" {
d.VpcId, err = d.getDefaultVPCId()
if err != nil {
log.Warnf("Couldn't determine your account Default VPC ID : %q", err)
}
}
if d.SubnetId == "" && d.VpcId == "" {
return fmt.Errorf("amazonec2 driver requires either the --amazonec2-subnet-id or --amazonec2-vpc-id option")
return errorNoVPCIdFound
}
if d.SubnetId != "" && d.VpcId != "" {
@ -608,7 +642,7 @@ func (d *Driver) GetSSHHostname() (string, error) {
func (d *Driver) GetSSHUsername() string {
if d.SSHUser == "" {
d.SSHUser = "ubuntu"
d.SSHUser = defaultSSHUser
}
return d.SSHUser
@ -661,16 +695,6 @@ func (d *Driver) Remove() error {
return nil
}
func (d *Driver) getClient() *ec2.EC2 {
config := aws.NewConfig()
alogger := AwsLogger()
config = config.WithRegion(d.Region)
config = config.WithCredentials(credentials.NewStaticCredentials(d.AccessKey, d.SecretKey, d.SessionToken))
config = config.WithLogger(alogger)
config = config.WithLogLevel(aws.LogDebugWithHTTPBody)
return ec2.New(session.New(config))
}
func (d *Driver) getInstance() (*ec2.Instance, error) {
instances, err := d.getClient().DescribeInstances(&ec2.DescribeInstancesInput{
InstanceIds: []*string{&d.InstanceId},
@ -938,6 +962,21 @@ func (d *Driver) deleteKeyPair() error {
return nil
}
func (d *Driver) getDefaultVPCId() (string, error) {
output, err := d.getClient().DescribeAccountAttributes(&ec2.DescribeAccountAttributesInput{})
if err != nil {
return "", err
}
for _, attribute := range output.AccountAttributes {
if *attribute.AttributeName == "default-vpc" {
return *attribute.AttributeValues[0].AttributeValue, nil
}
}
return "", errors.New("No default-vpc attribute")
}
func generateId() string {
rb := make([]byte, 10)
_, err := rand.Read(rb)

View File

@ -5,6 +5,8 @@ import (
"os"
"testing"
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/docker/machine/commands/commandstest"
@ -85,8 +87,7 @@ func getTestDriver() (*Driver, error) {
d := NewDriver(machineTestName, storePath)
d.SetConfigFromFlags(getDefaultTestDriverFlags())
drv := d.(*Driver)
return drv, nil
return d, nil
}
func TestConfigureSecurityGroupPermissionsEmpty(t *testing.T) {
@ -238,3 +239,69 @@ func TestSetConfigFromFlags(t *testing.T) {
assert.NoError(t, err)
assert.Empty(t, checkFlags.InvalidFlags)
}
type fakeEC2WithDescribe struct {
*ec2.EC2
output *ec2.DescribeAccountAttributesOutput
err error
}
func (f *fakeEC2WithDescribe) DescribeAccountAttributes(input *ec2.DescribeAccountAttributesInput) (*ec2.DescribeAccountAttributesOutput, error) {
return f.output, f.err
}
func TestFindDefaultVPC(t *testing.T) {
defaultVpc := "default-vpc"
vpcName := "vpc-9999"
driver := NewDriver("machineFoo", "path")
driver.clientFactory = func() Ec2Client {
return &fakeEC2WithDescribe{
output: &ec2.DescribeAccountAttributesOutput{
AccountAttributes: []*ec2.AccountAttribute{
{
AttributeName: &defaultVpc,
AttributeValues: []*ec2.AccountAttributeValue{
{AttributeValue: &vpcName},
},
},
},
},
}
}
vpc, err := driver.getDefaultVPCId()
assert.Equal(t, "vpc-9999", vpc)
assert.NoError(t, err)
}
func TestDefaultVPCIsMissing(t *testing.T) {
driver := NewDriver("machineFoo", "path")
driver.clientFactory = func() Ec2Client {
return &fakeEC2WithDescribe{
output: &ec2.DescribeAccountAttributesOutput{
AccountAttributes: []*ec2.AccountAttribute{},
},
}
}
vpc, err := driver.getDefaultVPCId()
assert.EqualError(t, err, "No default-vpc attribute")
assert.Empty(t, vpc)
}
func TestDescribeAccountAttributeFails(t *testing.T) {
driver := NewDriver("machineFoo", "path")
driver.clientFactory = func() Ec2Client {
return &fakeEC2WithDescribe{
err: errors.New("Not Found"),
}
}
vpc, err := driver.getDefaultVPCId()
assert.EqualError(t, err, "Not Found")
assert.Empty(t, vpc)
}

View File

@ -0,0 +1,51 @@
package amazonec2
import "github.com/aws/aws-sdk-go/service/ec2"
type Ec2Client interface {
DescribeAccountAttributes(input *ec2.DescribeAccountAttributesInput) (*ec2.DescribeAccountAttributesOutput, error)
DescribeSubnets(input *ec2.DescribeSubnetsInput) (*ec2.DescribeSubnetsOutput, error)
CreateTags(input *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
//SecurityGroup
CreateSecurityGroup(input *ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error)
AuthorizeSecurityGroupIngress(input *ec2.AuthorizeSecurityGroupIngressInput) (*ec2.AuthorizeSecurityGroupIngressOutput, error)
DescribeSecurityGroups(input *ec2.DescribeSecurityGroupsInput) (*ec2.DescribeSecurityGroupsOutput, error)
DeleteSecurityGroup(input *ec2.DeleteSecurityGroupInput) (*ec2.DeleteSecurityGroupOutput, error)
//KeyPair
DeleteKeyPair(input *ec2.DeleteKeyPairInput) (*ec2.DeleteKeyPairOutput, error)
ImportKeyPair(input *ec2.ImportKeyPairInput) (*ec2.ImportKeyPairOutput, error)
DescribeKeyPairs(input *ec2.DescribeKeyPairsInput) (*ec2.DescribeKeyPairsOutput, error)
//Instances
DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
StartInstances(input *ec2.StartInstancesInput) (*ec2.StartInstancesOutput, error)
RebootInstances(input *ec2.RebootInstancesInput) (*ec2.RebootInstancesOutput, error)
StopInstances(input *ec2.StopInstancesInput) (*ec2.StopInstancesOutput, error)
RunInstances(input *ec2.RunInstancesInput) (*ec2.Reservation, error)
TerminateInstances(input *ec2.TerminateInstancesInput) (*ec2.TerminateInstancesOutput, error)
//SpotInstances
RequestSpotInstances(input *ec2.RequestSpotInstancesInput) (*ec2.RequestSpotInstancesOutput, error)
DescribeSpotInstanceRequests(input *ec2.DescribeSpotInstanceRequestsInput) (*ec2.DescribeSpotInstanceRequestsOutput, error)
WaitUntilSpotInstanceRequestFulfilled(input *ec2.DescribeSpotInstanceRequestsInput) error
}