Skip to content

Commit

Permalink
Merge pull request #8 from MarcoPolo/marco/placement-and-terminate-on…
Browse files Browse the repository at this point in the history
…-error

Terminate when attach volume fails. Add option to control AZ placement
  • Loading branch information
stephank committed Mar 11, 2021
2 parents 47c64a3 + fa361cd commit 1f5c913
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 12 deletions.
8 changes: 8 additions & 0 deletions doc/providers/aws_ec2.md
Expand Up @@ -88,8 +88,16 @@ target "<address>" "aws_ec2" {
}
# Control where the instance launches. Optional, but needed if you attach a
# volume.
placement {
availability_zone = "us-west-2d"
}
# Optional existing EBS volumes to attach, once the machine is running. This
# block can be repeated multiple times to attach multiple volumes.
# Note that you can only attach a volume to an instance in the same AZ,
# so you likely want to set the placement attribute as well.
attach_volume {
# Name of the device. (Required)
Expand Down
50 changes: 38 additions & 12 deletions providers/aws_ec2/impl.go
Expand Up @@ -4,6 +4,7 @@ package aws_ec2

import (
"encoding/base64"
"errors"
"fmt"
"log"
"net"
Expand Down Expand Up @@ -33,6 +34,7 @@ type Provider struct {
ImageId string
InstanceType types.InstanceType
KeyName string
Placement *types.Placement
SubnetId *string
UserData64 *string
CheckPort uint16
Expand All @@ -49,6 +51,7 @@ type state struct {
type hclTarget struct {
EbsBlockDevice []*hclEbsBlockDevice `hcl:"ebs_block_device,block"`
AttachVolumes []*hclVolume `hcl:"attach_volume,block"`
Placement *hclPlacement `hcl:"placement,block"`
ImageId string `hcl:"image_id,attr"`
InstanceType string `hcl:"instance_type,attr"`
KeyName string `hcl:"key_name,attr"`
Expand Down Expand Up @@ -78,6 +81,13 @@ type hclVolume struct {
VolumeId string `hcl:"volume_id,optional"`
}

// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Placement.html
type hclPlacement struct {
AvailabilityZone string `hcl:"availability_zone,optional"`
}

var errAttachVolume = errors.New("failed to attach volume")

const requestTimeout = 30 * time.Second

func (factory *Factory) NewProvider(target string, hclBlock hcl.Body) (providers.Provider, error) {
Expand Down Expand Up @@ -164,6 +174,11 @@ func (factory *Factory) NewProvider(target string, hclBlock hcl.Body) (providers
})
}

prov.Placement = &types.Placement{}
if parsed.Placement != nil {
prov.Placement.AvailabilityZone = aws.String(parsed.Placement.AvailabilityZone)
}

if parsed.UserData != nil {
prov.UserData64 = aws.String(base64.StdEncoding.EncodeToString([]byte(*parsed.UserData)))
}
Expand All @@ -186,15 +201,24 @@ func (prov *Provider) IsShared() bool {
}

func (prov *Provider) RunMachine(mach *providers.Machine) {
if prov.start(mach) {
ok, err := prov.start(mach)
if errors.Is(err, errAttachVolume) {
fmt.Printf("Error in Attaching Volumes. Stopping instance\n")
prov.stop(mach)
} else if err != nil {
fmt.Printf("Error in starting machine: %v\n", err)
return
}

if ok {
if prov.connectivityTest(mach) {
prov.msgLoop(mach)
}
prov.stop(mach)
}
}

func (prov *Provider) start(mach *providers.Machine) bool {
func (prov *Provider) start(mach *providers.Machine) (bool, error) {
bgCtx := context.Background()

ctx, _ := context.WithTimeout(bgCtx, requestTimeout)
Expand All @@ -208,10 +232,11 @@ func (prov *Provider) start(mach *providers.Machine) bool {
SubnetId: prov.SubnetId,
UserData: prov.UserData64,
IamInstanceProfile: prov.IamInstanceProfile,
Placement: prov.Placement,
})
if err != nil {
log.Printf("EC2 instance failed to start: %s\n", err.Error())
return false
return false, nil
}

inst := res.Instances[0]
Expand All @@ -226,38 +251,39 @@ func (prov *Provider) start(mach *providers.Machine) bool {
})
if err != nil {
log.Printf("Could not check EC2 instance '%s' state: %s\n", *inst.InstanceId, err.Error())
return false
return false, nil
}
if res.Reservations == nil || res.Reservations[0].Instances == nil {
log.Printf("EC2 instance '%s' disappeared while waiting for it to start\n", *inst.InstanceId)
return false
return false, nil
}

inst = res.Reservations[0].Instances[0]
}

if inst.State.Name != "running" {
log.Printf("EC2 instance '%s' in unexpected state '%s'\n", *inst.InstanceId, inst.State.Name)
return false
return false, nil
}

log.Printf("EC2 instance '%s' is running\n", *inst.InstanceId)

mach.State = &state{
id: *inst.InstanceId,
addr: inst.PublicIpAddress,
}

// We're running, we can attach the volumes
for _, v := range prov.AttachVolumes {
v.InstanceId = inst.InstanceId
_, err := prov.Ec2.AttachVolume(ctx, v)
if err != nil {
fmt.Printf("Error in attaching volume: %v\n", err)
return false
return false, fmt.Errorf("%w: %v", errAttachVolume, err)
}
}

mach.State = &state{
id: *inst.InstanceId,
addr: inst.PublicIpAddress,
}
return true
return true, nil
}

func (prov *Provider) stop(mach *providers.Machine) {
Expand Down

0 comments on commit 1f5c913

Please sign in to comment.