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

d/aws_ec2_instance_spot_price - add new data source #12504

Merged
merged 17 commits into from
Jul 31, 2020
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
88 changes: 88 additions & 0 deletions aws/data_source_aws_ec2_spot_price.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package aws

import (
"fmt"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func dataSourceAwsEc2SpotPrice() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsEc2SpotPriceRead,

Schema: map[string]*schema.Schema{
"filter": dataSourceFiltersSchema(),
"instance_type": {
Type: schema.TypeString,
Optional: true,
},
"availability_zone": {
Type: schema.TypeString,
Optional: true,
},
"spot_price": {
Type: schema.TypeString,
Computed: true,
},
"spot_price_timestamp": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

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

now := time.Now()
input := &ec2.DescribeSpotPriceHistoryInput{
StartTime: &now,
}

if v, ok := d.GetOk("instance_type"); ok {
instanceType := v.(string)
input.InstanceTypes = []*string{
aws.String(instanceType),
}
}

if v, ok := d.GetOk("availability_zone"); ok {
availabilityZone := v.(string)
input.AvailabilityZone = aws.String(availabilityZone)
}

if v, ok := d.GetOk("filter"); ok {
input.Filters = buildAwsDataSourceFilters(v.(*schema.Set))
}

var foundSpotPrice []*ec2.SpotPrice

err := conn.DescribeSpotPriceHistoryPages(input, func(output *ec2.DescribeSpotPriceHistoryOutput, lastPage bool) bool {
foundSpotPrice = append(foundSpotPrice, output.SpotPriceHistory...)
return true
})
if err != nil {
return fmt.Errorf("error reading EC2 Spot Price History: %w", err)
}

if len(foundSpotPrice) == 0 {
return fmt.Errorf("no EC2 Spot Price History found matching criteria; try different search")
}

if len(foundSpotPrice) > 1 {
return fmt.Errorf("multiple EC2 Spot Price History results found matching criteria; try different search")
}

resultSpotPrice := foundSpotPrice[0]

d.Set("spot_price", resultSpotPrice.SpotPrice)
d.Set("spot_price_timestamp", (*resultSpotPrice.Timestamp).Format(time.RFC3339))
d.SetId(resource.UniqueId())

return nil
}
139 changes: 139 additions & 0 deletions aws/data_source_aws_ec2_spot_price_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package aws

import (
"fmt"
"regexp"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccAwsEc2SpotPriceDataSource(t *testing.T) {
dataSourceName := "data.aws_ec2_spot_price.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckAwsEc2SpotPrice(t) },
Providers: testAccProviders,
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: testAccAwsEc2SpotPriceDataSourceConfig(),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(dataSourceName, "spot_price", regexp.MustCompile(`^\d+\.\d+$`)),
resource.TestMatchResourceAttr(dataSourceName, "spot_price_timestamp", regexp.MustCompile(rfc3339RegexPattern)),
),
},
},
})
}

func TestAccAwsEc2SpotPriceDataSourceFilter(t *testing.T) {
dataSourceName := "data.aws_ec2_spot_price.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckAwsEc2SpotPrice(t) },
Providers: testAccProviders,
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: testAccAwsEc2SpotPriceDataSourceFilterConfig(),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr(dataSourceName, "spot_price", regexp.MustCompile(`^\d+\.\d+$`)),
resource.TestMatchResourceAttr(dataSourceName, "spot_price_timestamp", regexp.MustCompile(rfc3339RegexPattern)),
),
},
},
})
}

func testAccPreCheckAwsEc2SpotPrice(t *testing.T) {
conn := testAccProvider.Meta().(*AWSClient).ec2conn

input := &ec2.DescribeSpotPriceHistoryInput{
MaxResults: aws.Int64(5),
}

_, err := conn.DescribeSpotPriceHistory(input)

if testAccPreCheckSkipError(err) {
t.Skipf("skipping acceptance testing: %s", err)
}

if err != nil {
t.Fatalf("unexpected PreCheck error: %s", err)
}
}

func testAccAwsEc2SpotPriceDataSourceConfig() string {
return fmt.Sprintf(`
data "aws_region" "current" {}

data "aws_ec2_instance_type_offering" "test" {
filter {
name = "instance-type"
values = ["m5.xlarge"]
}
}

data "aws_availability_zones" "available" {
state = "available"

filter {
name = "region-name"
values = [data.aws_region.current.name]
}
}

data "aws_ec2_spot_price" "test" {
instance_type = data.aws_ec2_instance_type_offering.test.instance_type

availability_zone = data.aws_availability_zones.available.names[0]

filter {
name = "product-description"
values = ["Linux/UNIX"]
}
}
`)
}

func testAccAwsEc2SpotPriceDataSourceFilterConfig() string {
return fmt.Sprintf(`
data "aws_region" "current" {}

data "aws_ec2_instance_type_offering" "test" {
filter {
name = "instance-type"
values = ["m5.xlarge"]
}
}

data "aws_availability_zones" "available" {
state = "available"

filter {
name = "region-name"
values = [data.aws_region.current.name]
}
}

data "aws_ec2_spot_price" "test" {
filter {
name = "product-description"
values = ["Linux/UNIX"]
}

filter {
name = "instance-type"
values = [data.aws_ec2_instance_type_offering.test.instance_type]
}

filter {
name = "availability-zone"
values = [data.aws_availability_zones.available.names[0]]
}
}
`)
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ func Provider() terraform.ResourceProvider {
"aws_ec2_local_gateway_virtual_interface": dataSourceAwsEc2LocalGatewayVirtualInterface(),
"aws_ec2_local_gateway_virtual_interface_group": dataSourceAwsEc2LocalGatewayVirtualInterfaceGroup(),
"aws_ec2_local_gateway_virtual_interface_groups": dataSourceAwsEc2LocalGatewayVirtualInterfaceGroups(),
"aws_ec2_spot_price": dataSourceAwsEc2SpotPrice(),
Copy link
Contributor

Choose a reason for hiding this comment

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

@p0pr0ck5 @bflad This data source is documented as aws_ec2_instance_spot_price (note instance) but as this line demonstrates, the name is actually aws_ec2_spot_price. So either the docs or the data source name here has to be changed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please see: #14680

"aws_ec2_transit_gateway": dataSourceAwsEc2TransitGateway(),
"aws_ec2_transit_gateway_dx_gateway_attachment": dataSourceAwsEc2TransitGatewayDxGatewayAttachment(),
"aws_ec2_transit_gateway_peering_attachment": dataSourceAwsEc2TransitGatewayPeeringAttachment(),
Expand Down
3 changes: 3 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,9 @@
<li>
<a href="/docs/providers/aws/d/ec2_local_gateway_virtual_interface_groups.html">aws_ec2_local_gateway_virtual_interface_groups</a>
</li>
<li>
<a href="/docs/providers/aws/d/ec2_spot_price.html">aws_ec2_spot_price</a>
</li>
<li>
<a href="/docs/providers/aws/d/ec2_transit_gateway.html">aws_ec2_transit_gateway</a>
</li>
Expand Down
45 changes: 45 additions & 0 deletions website/docs/d/ec2_spot_price.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
subcategory: "EC2"
layout: "aws"
page_title: "AWS: aws_ec2_instance_spot_price"
description: |-
Information about most recent Spot Price for a given EC2 instance.
---

# Data Source: aws_ec2_instance_spot_price

Information about most recent Spot Price for a given EC2 instance.

## Example Usage

```hcl
data "aws_ec2_instance_spot_price" "example" {
instance_type = "t3.medium"
availability_zone = "us-west-2a"

filter {
name = "product-description"
values = ["Linux/UNIX"]
}
}
```

## Argument Reference

The following arguments are supported:

* `instance_type` - (Optional) The type of instance for which to query Spot Price information.
* `availability_zone` - (Optional) The availability zone in which to query Spot price information.
* `filter` - (Optional) One or more configuration blocks containing name-values filters. See the [EC2 API Reference](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSpotPriceHistory.html) for supported filters. Detailed below.

### filter Argument Reference

* `name` - (Required) Name of the filter.
* `values` - (Required) List of one or more values for the filter.

## Attribute Reference

In addition to all arguments above, the following attributes are exported:

* `spot_price` - The most recent Spot Price value for the given instance type and AZ.
* `spot_price_timestamp` - The timestamp at which the Spot Price value was published.