From 21919ad7f8a2357338645f6cfbc4e2677ae79ce4 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 23 Mar 2020 11:31:32 -0700 Subject: [PATCH 01/17] d/aws_ec2_instance_spot_price - add new data source Adds a data source to fetch the most recently published Spot Price value for a given EC2 instance type and availability zone. The value can be manipulated and fed into spot price parameters in other resources, allowing for dynamic adjusted of spot requests. --- ...data_source_aws_ec2_instance_spot_price.go | 106 ++++++++++++++++++ ...source_aws_ec2_instance_spot_price_test.go | 61 ++++++++++ main.tf | 21 ++++ .../d/ec2_instance_spot_price.html.markdown | 45 ++++++++ 4 files changed, 233 insertions(+) create mode 100644 aws/data_source_aws_ec2_instance_spot_price.go create mode 100644 aws/data_source_aws_ec2_instance_spot_price_test.go create mode 100644 main.tf create mode 100644 website/docs/d/ec2_instance_spot_price.html.markdown diff --git a/aws/data_source_aws_ec2_instance_spot_price.go b/aws/data_source_aws_ec2_instance_spot_price.go new file mode 100644 index 000000000000..c83036ab7811 --- /dev/null +++ b/aws/data_source_aws_ec2_instance_spot_price.go @@ -0,0 +1,106 @@ +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 dataSourceAwsEc2InstanceSpotPrice() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsEc2InstanceSpotPriceRead, + + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + "instance_type": { + Type: schema.TypeString, + Required: true, + }, + "availability_zone": { + Type: schema.TypeString, + Required: true, + }, + "spot_price": { + Type: schema.TypeString, + Computed: true, + }, + "spot_price_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAwsEc2InstanceSpotPriceRead(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 + + for { + output, err := conn.DescribeSpotPriceHistory(input) + + if err != nil { + return fmt.Errorf("error reading EC2 Spot Price History: %w", err) + } + + if output == nil { + break + } + + for _, instanceSpotPrice := range output.SpotPriceHistory { + if instanceSpotPrice == nil { + continue + } + + foundSpotPrice = append(foundSpotPrice, instanceSpotPrice) + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + if len(foundSpotPrice) == 0 { + return fmt.Errorf("no EC2 Instance Type Offerings found matching criteria; try different search") + } + + if len(foundSpotPrice) > 1 { + return fmt.Errorf("multiple EC2 Instance Offerings found matching criteria; try different search") + } + + resultSpotPrice := foundSpotPrice[0] + + d.Set("spot_price", resultSpotPrice.SpotPrice) + d.Set("spot_price_timestamp", resultSpotPrice.Timestamp.String()) + d.SetId(resource.UniqueId()) + + return nil +} diff --git a/aws/data_source_aws_ec2_instance_spot_price_test.go b/aws/data_source_aws_ec2_instance_spot_price_test.go new file mode 100644 index 000000000000..5b55353bc28d --- /dev/null +++ b/aws/data_source_aws_ec2_instance_spot_price_test.go @@ -0,0 +1,61 @@ +package aws + +import ( + "fmt" + "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 TestAccAWSEc2InstanceSpotPriceDataSource_Filter(t *testing.T) { + dataSourceName := "data.aws_ec2_instance_spot_price.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEc2InstanceSpotPrice(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2InstanceSpotPriceDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "instance_type"), + ), + }, + }, + }) +} + +func testAccPreCheckAWSEc2InstanceSpotPrice(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 testAccAWSEc2InstanceSpotPriceDataSourceConfig() string { + return fmt.Sprintf(` +data "aws_ec2_instance_spot_price" "test" { + instance_type = "t3.medium" + + availability_zone = "us-west-2a" + + filter { + name = "product-description" + values = ["Linux/UNIX"] + } +} +`) +} diff --git a/main.tf b/main.tf new file mode 100644 index 000000000000..ee9b5718334a --- /dev/null +++ b/main.tf @@ -0,0 +1,21 @@ +provider "aws" { + version = "~> 2.0" +} + +data "aws_ec2_instance_spot_price" "foo" { + instance_type = "t3.medium" + availability_zone = "us-west-2a" + + filter { + name = "product-description" + values = ["Linux/UNIX"] + } +} + +output "foo" { + value = data.aws_ec2_instance_spot_price.foo.spot_price +} + +output "when" { + value = data.aws_ec2_instance_spot_price.foo.spot_price_timestamp +} diff --git a/website/docs/d/ec2_instance_spot_price.html.markdown b/website/docs/d/ec2_instance_spot_price.html.markdown new file mode 100644 index 000000000000..96e921f338e3 --- /dev/null +++ b/website/docs/d/ec2_instance_spot_price.html.markdown @@ -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` - The type of instance for which to query Spot Price information. +* `availability_zone` - The available 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_DescribeInstanceTypeOfferings.html) for supported filters. Detailed below. + +### filter Argument Reference + +* `name` - (Required) Name of the filter. The `location` filter depends on the top-level `location_type` argument and if not specified, defaults to the current region. +* `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. From 6a7a60b85f05bd4f8e63dc23dd80998c309c31a2 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 13:47:09 -0700 Subject: [PATCH 02/17] remove accidental inclusion of local test example --- main.tf | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 main.tf diff --git a/main.tf b/main.tf deleted file mode 100644 index ee9b5718334a..000000000000 --- a/main.tf +++ /dev/null @@ -1,21 +0,0 @@ -provider "aws" { - version = "~> 2.0" -} - -data "aws_ec2_instance_spot_price" "foo" { - instance_type = "t3.medium" - availability_zone = "us-west-2a" - - filter { - name = "product-description" - values = ["Linux/UNIX"] - } -} - -output "foo" { - value = data.aws_ec2_instance_spot_price.foo.spot_price -} - -output "when" { - value = data.aws_ec2_instance_spot_price.foo.spot_price_timestamp -} From 37ad45ddaf80c9cf8c37ee60bc818906017d0c11 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 13:48:34 -0700 Subject: [PATCH 03/17] make instance_type and availability_zone optional --- aws/data_source_aws_ec2_instance_spot_price.go | 4 ++-- website/docs/d/ec2_instance_spot_price.html.markdown | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aws/data_source_aws_ec2_instance_spot_price.go b/aws/data_source_aws_ec2_instance_spot_price.go index c83036ab7811..7d9fbcbe98ec 100644 --- a/aws/data_source_aws_ec2_instance_spot_price.go +++ b/aws/data_source_aws_ec2_instance_spot_price.go @@ -18,11 +18,11 @@ func dataSourceAwsEc2InstanceSpotPrice() *schema.Resource { "filter": dataSourceFiltersSchema(), "instance_type": { Type: schema.TypeString, - Required: true, + Optional: true, }, "availability_zone": { Type: schema.TypeString, - Required: true, + Optional: true, }, "spot_price": { Type: schema.TypeString, diff --git a/website/docs/d/ec2_instance_spot_price.html.markdown b/website/docs/d/ec2_instance_spot_price.html.markdown index 96e921f338e3..66a66cf29945 100644 --- a/website/docs/d/ec2_instance_spot_price.html.markdown +++ b/website/docs/d/ec2_instance_spot_price.html.markdown @@ -28,9 +28,9 @@ data "aws_ec2_instance_spot_price" "example" { The following arguments are supported: -* `instance_type` - The type of instance for which to query Spot Price information. -* `availability_zone` - The available 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_DescribeInstanceTypeOfferings.html) for supported filters. Detailed below. +* `instance_type` - (Optional) The type of instance for which to query Spot Price information. +* `availability_zone` - (Optional) The availablity 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 From 613c1302b339340451ac397ee028a3670d210ffa Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 13:49:15 -0700 Subject: [PATCH 04/17] simplify DescribeSpotPriceHistory usage since we are only interested in fetching the latest SpotPrice, we don't need to iterate over a series of requests, as we only ever need to fetch call's worth of data --- ...data_source_aws_ec2_instance_spot_price.go | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/aws/data_source_aws_ec2_instance_spot_price.go b/aws/data_source_aws_ec2_instance_spot_price.go index 7d9fbcbe98ec..520bb3ed4ea5 100644 --- a/aws/data_source_aws_ec2_instance_spot_price.go +++ b/aws/data_source_aws_ec2_instance_spot_price.go @@ -60,34 +60,13 @@ func dataSourceAwsEc2InstanceSpotPriceRead(d *schema.ResourceData, meta interfac input.Filters = buildAwsDataSourceFilters(v.(*schema.Set)) } - var foundSpotPrice []*ec2.SpotPrice - - for { - output, err := conn.DescribeSpotPriceHistory(input) - - if err != nil { - return fmt.Errorf("error reading EC2 Spot Price History: %w", err) - } - - if output == nil { - break - } - - for _, instanceSpotPrice := range output.SpotPriceHistory { - if instanceSpotPrice == nil { - continue - } - - foundSpotPrice = append(foundSpotPrice, instanceSpotPrice) - } - - if aws.StringValue(output.NextToken) == "" { - break - } - - input.NextToken = output.NextToken + output, err := conn.DescribeSpotPriceHistory(input) + if err != nil { + return fmt.Errorf("error reading EC2 Spot Price History: %w", err) } + foundSpotPrice := output.SpotPriceHistory + if len(foundSpotPrice) == 0 { return fmt.Errorf("no EC2 Instance Type Offerings found matching criteria; try different search") } From e354ef26bed52963e1bf32052d4619f9ae99d176 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 13:50:55 -0700 Subject: [PATCH 05/17] copypasta --- aws/data_source_aws_ec2_instance_spot_price.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/data_source_aws_ec2_instance_spot_price.go b/aws/data_source_aws_ec2_instance_spot_price.go index 520bb3ed4ea5..072db641e5e7 100644 --- a/aws/data_source_aws_ec2_instance_spot_price.go +++ b/aws/data_source_aws_ec2_instance_spot_price.go @@ -68,11 +68,11 @@ func dataSourceAwsEc2InstanceSpotPriceRead(d *schema.ResourceData, meta interfac foundSpotPrice := output.SpotPriceHistory if len(foundSpotPrice) == 0 { - return fmt.Errorf("no EC2 Instance Type Offerings found matching criteria; try different search") + return fmt.Errorf("no EC2 Spot Price History found matching criteria; try different search") } if len(foundSpotPrice) > 1 { - return fmt.Errorf("multiple EC2 Instance Offerings found matching criteria; try different search") + return fmt.Errorf("multiple EC2 Spot Price History results found matching criteria; try different search") } resultSpotPrice := foundSpotPrice[0] From 4d1e0bde5a2520edae6fad1082db0a9b73037ac6 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 13:51:10 -0700 Subject: [PATCH 06/17] add more thorough tests for aws_ec2_instance_spot_price resource --- aws/data_source_aws_ec2_instance_spot_price_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws/data_source_aws_ec2_instance_spot_price_test.go b/aws/data_source_aws_ec2_instance_spot_price_test.go index 5b55353bc28d..47cceef1c2ce 100644 --- a/aws/data_source_aws_ec2_instance_spot_price_test.go +++ b/aws/data_source_aws_ec2_instance_spot_price_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "regexp" "testing" "github.com/aws/aws-sdk-go/aws" @@ -20,7 +21,8 @@ func TestAccAWSEc2InstanceSpotPriceDataSource_Filter(t *testing.T) { { Config: testAccAWSEc2InstanceSpotPriceDataSourceConfig(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet(dataSourceName, "instance_type"), + resource.TestMatchResourceAttr(dataSourceName, "spot_price", regexp.MustCompile(`^\d+\.\d+$`)), + resource.TestMatchResourceAttr(dataSourceName, "spot_price_timestamp", regexp.MustCompile(rfc3339RegexPattern)), ), }, }, From 14ab44c8abf51407427386a0c8374fd0d23a56b7 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 13:51:29 -0700 Subject: [PATCH 07/17] use dynamic data sources for filtering in tests --- ...source_aws_ec2_instance_spot_price_test.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/aws/data_source_aws_ec2_instance_spot_price_test.go b/aws/data_source_aws_ec2_instance_spot_price_test.go index 47cceef1c2ce..4b00cf568a38 100644 --- a/aws/data_source_aws_ec2_instance_spot_price_test.go +++ b/aws/data_source_aws_ec2_instance_spot_price_test.go @@ -49,10 +49,25 @@ func testAccPreCheckAWSEc2InstanceSpotPrice(t *testing.T) { func testAccAWSEc2InstanceSpotPriceDataSourceConfig() string { return fmt.Sprintf(` +# Rather than hardcode an instance type in the testing, +# use the first result from all available offerings. +data "aws_ec2_instance_type_offerings" "test" {} + +data "aws_ec2_instance_type_offering" "test" { + filter { + name = "instance-type" + values = [tolist(data.aws_ec2_instance_type_offerings.test.instance_types)[0]] + } +} + +data "aws_availability_zones" "available" { + state = "available" +} + data "aws_ec2_instance_spot_price" "test" { - instance_type = "t3.medium" + instance_type = data.aws_ec2_instance_type_offering.test.instance_type - availability_zone = "us-west-2a" + availability_zone = data.aws_availability_zones.available.names[0] filter { name = "product-description" From be0353b8f0034d814fb116dc4c72b0d3eb9cd2ed Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 14:15:49 -0700 Subject: [PATCH 08/17] format spot_price_timestamp as RFC3339 --- aws/data_source_aws_ec2_instance_spot_price.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/data_source_aws_ec2_instance_spot_price.go b/aws/data_source_aws_ec2_instance_spot_price.go index 072db641e5e7..0f6678fe4a83 100644 --- a/aws/data_source_aws_ec2_instance_spot_price.go +++ b/aws/data_source_aws_ec2_instance_spot_price.go @@ -78,7 +78,7 @@ func dataSourceAwsEc2InstanceSpotPriceRead(d *schema.ResourceData, meta interfac resultSpotPrice := foundSpotPrice[0] d.Set("spot_price", resultSpotPrice.SpotPrice) - d.Set("spot_price_timestamp", resultSpotPrice.Timestamp.String()) + d.Set("spot_price_timestamp", (*resultSpotPrice.Timestamp).Format(time.RFC3339)) d.SetId(resource.UniqueId()) return nil From df35b6fa4313ad869609dbbc41a5898b2e97b2bf Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 14:31:09 -0700 Subject: [PATCH 09/17] rebase aws/provider.go --- aws/provider.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/provider.go b/aws/provider.go index 7f8b23a3c821..28167c2d8095 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -214,6 +214,7 @@ func Provider() terraform.ResourceProvider { "aws_ec2_coip_pools": dataSourceAwsEc2CoipPools(), "aws_ec2_instance_type_offering": dataSourceAwsEc2InstanceTypeOffering(), "aws_ec2_instance_type_offerings": dataSourceAwsEc2InstanceTypeOfferings(), + "aws_ec2_instance_spot_price": dataSourceAwsEc2InstanceSpotPrice(), "aws_ec2_local_gateway": dataSourceAwsEc2LocalGateway(), "aws_ec2_local_gateways": dataSourceAwsEc2LocalGateways(), "aws_ec2_local_gateway_route_table": dataSourceAwsEc2LocalGatewayRouteTable(), From ce454d9b4527d4dae05ccf2f96e57e27a6108d11 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 14:57:37 -0700 Subject: [PATCH 10/17] fix up tests --- aws/data_source_aws_ec2_instance_spot_price_test.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/aws/data_source_aws_ec2_instance_spot_price_test.go b/aws/data_source_aws_ec2_instance_spot_price_test.go index 4b00cf568a38..7a989c17a177 100644 --- a/aws/data_source_aws_ec2_instance_spot_price_test.go +++ b/aws/data_source_aws_ec2_instance_spot_price_test.go @@ -49,19 +49,22 @@ func testAccPreCheckAWSEc2InstanceSpotPrice(t *testing.T) { func testAccAWSEc2InstanceSpotPriceDataSourceConfig() string { return fmt.Sprintf(` -# Rather than hardcode an instance type in the testing, -# use the first result from all available offerings. -data "aws_ec2_instance_type_offerings" "test" {} +data "aws_region" "current" {} data "aws_ec2_instance_type_offering" "test" { filter { name = "instance-type" - values = [tolist(data.aws_ec2_instance_type_offerings.test.instance_types)[0]] + values = ["m5.xlarge"] } } data "aws_availability_zones" "available" { state = "available" + + filter { + name = "region-name" + values = [data.aws_region.current.name] + } } data "aws_ec2_instance_spot_price" "test" { From 00be40d206987bcfd56bb2a01da2723774e19c63 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 15:04:50 -0700 Subject: [PATCH 11/17] lint and typo fixes --- website/docs/d/ec2_instance_spot_price.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/d/ec2_instance_spot_price.html.markdown b/website/docs/d/ec2_instance_spot_price.html.markdown index 66a66cf29945..9955b3e1737e 100644 --- a/website/docs/d/ec2_instance_spot_price.html.markdown +++ b/website/docs/d/ec2_instance_spot_price.html.markdown @@ -14,11 +14,11 @@ Information about most recent Spot Price for a given EC2 instance. ```hcl data "aws_ec2_instance_spot_price" "example" { - instance_type = "t3.medium" + instance_type = "t3.medium" availability_zone = "us-west-2a" filter { - name = "product-description" + name = "product-description" values = ["Linux/UNIX"] } } @@ -29,7 +29,7 @@ data "aws_ec2_instance_spot_price" "example" { The following arguments are supported: * `instance_type` - (Optional) The type of instance for which to query Spot Price information. -* `availability_zone` - (Optional) The availablity zone in 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 From 495c8e7b40396452375b51899cd15cc3304a6211 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 15:14:41 -0700 Subject: [PATCH 12/17] update docs --- website/aws.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/aws.erb b/website/aws.erb index 9bfefea77e6e..b8785bf9082f 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1121,6 +1121,9 @@
  • aws_ec2_coip_pools
  • +
  • + aws_ec2_instance_spot_price +
  • aws_ec2_instance_type_offering
  • From 3d92b6f1412f8ea22a791c23dd7f69464390cb06 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Mon, 13 Jul 2020 15:20:39 -0700 Subject: [PATCH 13/17] whitespace --- aws/data_source_aws_ec2_instance_spot_price_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aws/data_source_aws_ec2_instance_spot_price_test.go b/aws/data_source_aws_ec2_instance_spot_price_test.go index 7a989c17a177..c85ac58ebc79 100644 --- a/aws/data_source_aws_ec2_instance_spot_price_test.go +++ b/aws/data_source_aws_ec2_instance_spot_price_test.go @@ -54,7 +54,7 @@ data "aws_region" "current" {} data "aws_ec2_instance_type_offering" "test" { filter { name = "instance-type" - values = ["m5.xlarge"] + values = ["m5.xlarge"] } } @@ -62,8 +62,8 @@ data "aws_availability_zones" "available" { state = "available" filter { - name = "region-name" - values = [data.aws_region.current.name] + name = "region-name" + values = [data.aws_region.current.name] } } From 2e9154949e03443398f840076c4a40aba2bcb66a Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Tue, 14 Jul 2020 15:34:14 -0700 Subject: [PATCH 14/17] aws_ec2_instance_spot_price -> aws_ec2_spot_price --- ...spot_price.go => data_source_aws_ec2_spot_price.go} | 6 +++--- ..._test.go => data_source_aws_ec2_spot_price_test.go} | 10 +++++----- aws/provider.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename aws/{data_source_aws_ec2_instance_spot_price.go => data_source_aws_ec2_spot_price.go} (90%) rename aws/{data_source_aws_ec2_instance_spot_price_test.go => data_source_aws_ec2_spot_price_test.go} (82%) diff --git a/aws/data_source_aws_ec2_instance_spot_price.go b/aws/data_source_aws_ec2_spot_price.go similarity index 90% rename from aws/data_source_aws_ec2_instance_spot_price.go rename to aws/data_source_aws_ec2_spot_price.go index 0f6678fe4a83..fe69d3c2acbc 100644 --- a/aws/data_source_aws_ec2_instance_spot_price.go +++ b/aws/data_source_aws_ec2_spot_price.go @@ -10,9 +10,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) -func dataSourceAwsEc2InstanceSpotPrice() *schema.Resource { +func dataSourceAwsEc2SpotPrice() *schema.Resource { return &schema.Resource{ - Read: dataSourceAwsEc2InstanceSpotPriceRead, + Read: dataSourceAwsEc2SpotPriceRead, Schema: map[string]*schema.Schema{ "filter": dataSourceFiltersSchema(), @@ -36,7 +36,7 @@ func dataSourceAwsEc2InstanceSpotPrice() *schema.Resource { } } -func dataSourceAwsEc2InstanceSpotPriceRead(d *schema.ResourceData, meta interface{}) error { +func dataSourceAwsEc2SpotPriceRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn now := time.Now() diff --git a/aws/data_source_aws_ec2_instance_spot_price_test.go b/aws/data_source_aws_ec2_spot_price_test.go similarity index 82% rename from aws/data_source_aws_ec2_instance_spot_price_test.go rename to aws/data_source_aws_ec2_spot_price_test.go index c85ac58ebc79..9b8b4cc88701 100644 --- a/aws/data_source_aws_ec2_instance_spot_price_test.go +++ b/aws/data_source_aws_ec2_spot_price_test.go @@ -10,16 +10,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/resource" ) -func TestAccAWSEc2InstanceSpotPriceDataSource_Filter(t *testing.T) { +func TestAccAwsEc2SpotPriceDataSource_Filter(t *testing.T) { dataSourceName := "data.aws_ec2_instance_spot_price.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEc2InstanceSpotPrice(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAwsEc2SpotPrice(t) }, Providers: testAccProviders, CheckDestroy: nil, Steps: []resource.TestStep{ { - Config: testAccAWSEc2InstanceSpotPriceDataSourceConfig(), + Config: testAccAwsEc2SpotPriceDataSourceConfig(), Check: resource.ComposeTestCheckFunc( resource.TestMatchResourceAttr(dataSourceName, "spot_price", regexp.MustCompile(`^\d+\.\d+$`)), resource.TestMatchResourceAttr(dataSourceName, "spot_price_timestamp", regexp.MustCompile(rfc3339RegexPattern)), @@ -29,7 +29,7 @@ func TestAccAWSEc2InstanceSpotPriceDataSource_Filter(t *testing.T) { }) } -func testAccPreCheckAWSEc2InstanceSpotPrice(t *testing.T) { +func testAccPreCheckAwsEc2SpotPrice(t *testing.T) { conn := testAccProvider.Meta().(*AWSClient).ec2conn input := &ec2.DescribeSpotPriceHistoryInput{ @@ -47,7 +47,7 @@ func testAccPreCheckAWSEc2InstanceSpotPrice(t *testing.T) { } } -func testAccAWSEc2InstanceSpotPriceDataSourceConfig() string { +func testAccAwsEc2SpotPriceDataSourceConfig() string { return fmt.Sprintf(` data "aws_region" "current" {} diff --git a/aws/provider.go b/aws/provider.go index 28167c2d8095..e6f699cfdc1e 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -214,7 +214,6 @@ func Provider() terraform.ResourceProvider { "aws_ec2_coip_pools": dataSourceAwsEc2CoipPools(), "aws_ec2_instance_type_offering": dataSourceAwsEc2InstanceTypeOffering(), "aws_ec2_instance_type_offerings": dataSourceAwsEc2InstanceTypeOfferings(), - "aws_ec2_instance_spot_price": dataSourceAwsEc2InstanceSpotPrice(), "aws_ec2_local_gateway": dataSourceAwsEc2LocalGateway(), "aws_ec2_local_gateways": dataSourceAwsEc2LocalGateways(), "aws_ec2_local_gateway_route_table": dataSourceAwsEc2LocalGatewayRouteTable(), @@ -222,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(), "aws_ec2_transit_gateway": dataSourceAwsEc2TransitGateway(), "aws_ec2_transit_gateway_dx_gateway_attachment": dataSourceAwsEc2TransitGatewayDxGatewayAttachment(), "aws_ec2_transit_gateway_peering_attachment": dataSourceAwsEc2TransitGatewayPeeringAttachment(), From f1ff164f56f4cf0f4ec8e7bece45afcb81728b62 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Tue, 14 Jul 2020 16:07:40 -0700 Subject: [PATCH 15/17] support spot price data by solely filtering add a test for sourcing an aws_ec2_spot_price data source using filter{} blocks exclusively. this commit also involves a rework of the DescribeSpotPriceHistory AWS API call, namely using the native SDK paging function; without this function, making a singular direct call to the DescribeSpotPriceHistory SDK function returned a response with an empty slice, but the underlying API call returned a response with a non-nil NextToken value. to support this, we simply use the DescribeSpotPriceHistoryPages unconditionally during reads, and add all found SpotPriceHistory objects to a slice. --- aws/data_source_aws_ec2_spot_price.go | 9 ++- aws/data_source_aws_ec2_spot_price_test.go | 64 +++++++++++++++++++++- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/aws/data_source_aws_ec2_spot_price.go b/aws/data_source_aws_ec2_spot_price.go index fe69d3c2acbc..7b16eb12f319 100644 --- a/aws/data_source_aws_ec2_spot_price.go +++ b/aws/data_source_aws_ec2_spot_price.go @@ -60,13 +60,16 @@ func dataSourceAwsEc2SpotPriceRead(d *schema.ResourceData, meta interface{}) err input.Filters = buildAwsDataSourceFilters(v.(*schema.Set)) } - output, err := conn.DescribeSpotPriceHistory(input) + 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) } - foundSpotPrice := output.SpotPriceHistory - if len(foundSpotPrice) == 0 { return fmt.Errorf("no EC2 Spot Price History found matching criteria; try different search") } diff --git a/aws/data_source_aws_ec2_spot_price_test.go b/aws/data_source_aws_ec2_spot_price_test.go index 9b8b4cc88701..a08764b13fc6 100644 --- a/aws/data_source_aws_ec2_spot_price_test.go +++ b/aws/data_source_aws_ec2_spot_price_test.go @@ -10,8 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/resource" ) -func TestAccAwsEc2SpotPriceDataSource_Filter(t *testing.T) { - dataSourceName := "data.aws_ec2_instance_spot_price.test" +func TestAccAwsEc2SpotPriceDataSource(t *testing.T) { + dataSourceName := "data.aws_ec2_spot_price.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t); testAccPreCheckAwsEc2SpotPrice(t) }, @@ -29,6 +29,25 @@ func TestAccAwsEc2SpotPriceDataSource_Filter(t *testing.T) { }) } +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 @@ -67,7 +86,7 @@ data "aws_availability_zones" "available" { } } -data "aws_ec2_instance_spot_price" "test" { +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] @@ -79,3 +98,42 @@ data "aws_ec2_instance_spot_price" "test" { } `) } + +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]] + } +} +`) +} From 4a5163aefbfe201100e12ded2f8156ae5c8649b5 Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Tue, 14 Jul 2020 16:27:05 -0700 Subject: [PATCH 16/17] update documentation following resource rename --- website/aws.erb | 6 +++--- ...pot_price.html.markdown => ec2_spot_price.html.markdown} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename website/docs/d/{ec2_instance_spot_price.html.markdown => ec2_spot_price.html.markdown} (100%) diff --git a/website/aws.erb b/website/aws.erb index b8785bf9082f..006ba66aef08 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1121,9 +1121,6 @@
  • aws_ec2_coip_pools
  • -
  • - aws_ec2_instance_spot_price -
  • aws_ec2_instance_type_offering
  • @@ -1151,6 +1148,9 @@
  • aws_ec2_local_gateway_virtual_interface_groups
  • +
  • + aws_ec2_spot_price +
  • aws_ec2_transit_gateway
  • diff --git a/website/docs/d/ec2_instance_spot_price.html.markdown b/website/docs/d/ec2_spot_price.html.markdown similarity index 100% rename from website/docs/d/ec2_instance_spot_price.html.markdown rename to website/docs/d/ec2_spot_price.html.markdown From 5a167da6f799e64b0b2ac72df20bae9eb97198af Mon Sep 17 00:00:00 2001 From: Robert Paprocki Date: Thu, 16 Jul 2020 12:26:26 -0700 Subject: [PATCH 17/17] copypasta documention updates --- website/docs/d/ec2_spot_price.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/d/ec2_spot_price.html.markdown b/website/docs/d/ec2_spot_price.html.markdown index 9955b3e1737e..6958ea0afde1 100644 --- a/website/docs/d/ec2_spot_price.html.markdown +++ b/website/docs/d/ec2_spot_price.html.markdown @@ -34,7 +34,7 @@ The following arguments are supported: ### filter Argument Reference -* `name` - (Required) Name of the filter. The `location` filter depends on the top-level `location_type` argument and if not specified, defaults to the current region. +* `name` - (Required) Name of the filter. * `values` - (Required) List of one or more values for the filter. ## Attribute Reference