Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

providers/aws: Update Spot Instance request to provide connection information #3940

Merged
merged 4 commits into from
Nov 23, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions builtin/providers/aws/resource_aws_spot_instance_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ func resourceAwsSpotInstanceRequestCreate(d *schema.ResourceData, meta interface
},
}

// If the instance is configured with a Network Interface (a subnet, has
// public IP, etc), then the instanceOpts.SecurityGroupIds and SubnetId will
// be nil
if len(instanceOpts.NetworkInterfaces) > 0 {
spotOpts.LaunchSpecification.SecurityGroupIds = instanceOpts.NetworkInterfaces[0].Groups
spotOpts.LaunchSpecification.SubnetId = instanceOpts.NetworkInterfaces[0].SubnetId
}

// Make the spot instance request
log.Printf("[DEBUG] Requesting spot bid opts: %s", spotOpts)
resp, err := conn.RequestSpotInstances(spotOpts)
Expand Down Expand Up @@ -172,13 +180,65 @@ func resourceAwsSpotInstanceRequestRead(d *schema.ResourceData, meta interface{}
// Instance ID is not set if the request is still pending
if request.InstanceId != nil {
d.Set("spot_instance_id", *request.InstanceId)
// Read the instance data, setting up connection information
if err := readInstance(d, meta); err != nil {
return fmt.Errorf("[ERR] Error reading Spot Instance Data: %s", err)
}
}
d.Set("spot_request_state", *request.State)
d.Set("tags", tagsToMap(request.Tags))

return nil
}

func readInstance(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
InstanceIds: []*string{aws.String(d.Get("spot_instance_id").(string))},
})
if err != nil {
// If the instance was not found, return nil so that we can show
// that the instance is gone.
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
return fmt.Errorf("no instance found")
}

// Some other error, report it
return err
}

// If nothing was found, then return no state
if len(resp.Reservations) == 0 {
return fmt.Errorf("no instances found")
}

instance := resp.Reservations[0].Instances[0]

// Set these fields for connection information
if instance != nil {
d.Set("public_dns", instance.PublicDnsName)
d.Set("public_ip", instance.PublicIpAddress)
d.Set("private_dns", instance.PrivateDnsName)
d.Set("private_ip", instance.PrivateIpAddress)

// set connection information
if instance.PublicIpAddress != nil {
d.SetConnInfo(map[string]string{
"type": "ssh",
"host": *instance.PublicIpAddress,
})
} else if instance.PrivateIpAddress != nil {
d.SetConnInfo(map[string]string{
"type": "ssh",
"host": *instance.PrivateIpAddress,
})
}
}

return nil
}

func resourceAwsSpotInstanceRequestUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

Expand Down
99 changes: 99 additions & 0 deletions builtin/providers/aws/resource_aws_spot_instance_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,26 @@ func TestAccAWSSpotInstanceRequest_vpc(t *testing.T) {
})
}

func TestAccAWSSpotInstanceRequest_SubnetAndSG(t *testing.T) {
var sir ec2.SpotInstanceRequest

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSpotInstanceRequestDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSpotInstanceRequestConfig_SubnetAndSG,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSpotInstanceRequestExists(
"aws_spot_instance_request.foo", &sir),
testAccCheckAWSSpotInstanceRequest_InstanceAttributes(&sir),
),
},
},
})
}

func testCheckKeyPair(keyName string, sir *ec2.SpotInstanceRequest) resource.TestCheckFunc {
return func(*terraform.State) error {
if sir.LaunchSpecification.KeyName == nil {
Expand Down Expand Up @@ -178,6 +198,44 @@ func testAccCheckAWSSpotInstanceRequestAttributes(
}
}

func testAccCheckAWSSpotInstanceRequest_InstanceAttributes(
sir *ec2.SpotInstanceRequest) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn
resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
InstanceIds: []*string{sir.InstanceId},
})
if err != nil {
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
return fmt.Errorf("Spot Instance not found")
}
return err
}

// If nothing was found, then return no state
if len(resp.Reservations) == 0 {
return fmt.Errorf("Spot Instance not found")
}

instance := resp.Reservations[0].Instances[0]

var sgMatch bool
for _, s := range instance.SecurityGroups {
// Hardcoded name for the security group that should be added inside the
// VPC
if *s.GroupName == "tf_test_sg_ssh" {
sgMatch = true
}
}

if !sgMatch {
return fmt.Errorf("Error in matching Spot Instance Security Group, expected 'tf_test_sg_ssh', got %s", instance.SecurityGroups)
}

return nil
}
}

func testAccCheckAWSSpotInstanceRequestAttributesVPC(
sir *ec2.SpotInstanceRequest) resource.TestCheckFunc {
return func(s *terraform.State) error {
Expand Down Expand Up @@ -249,3 +307,44 @@ resource "aws_spot_instance_request" "foo_VPC" {
}
}
`

const testAccAWSSpotInstanceRequestConfig_SubnetAndSG = `
resource "aws_spot_instance_request" "foo" {
ami = "ami-6f6d635f"
spot_price = "0.05"
instance_type = "t1.micro"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: this will probably fulfill faster using a current generation instance like t2.micro.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the AMI I am using supports t2.. I'll find one that does though, thanks 😄

wait_for_fulfillment = true
subnet_id = "${aws_subnet.tf_test_subnet.id}"
vpc_security_group_ids = ["${aws_security_group.tf_test_sg_ssh.id}"]
associate_public_ip_address = true
}

resource "aws_vpc" "default" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true

tags {
Name = "tf_test_vpc"
}
}

resource "aws_subnet" "tf_test_subnet" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "10.0.0.0/24"
map_public_ip_on_launch = true

tags {
Name = "tf_test_subnet"
}
}

resource "aws_security_group" "tf_test_sg_ssh" {
name = "tf_test_sg_ssh"
description = "tf_test_sg_ssh"
vpc_id = "${aws_vpc.default.id}"

tags {
Name = "tf_test_sg_ssh"
}
}
`