diff --git a/aws/data_source_aws_ec2_instance_type_offering.go b/aws/data_source_aws_ec2_instance_type_offering.go new file mode 100644 index 00000000000..fd53d836530 --- /dev/null +++ b/aws/data_source_aws_ec2_instance_type_offering.go @@ -0,0 +1,128 @@ +package aws + +import ( + "fmt" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func dataSourceAwsEc2InstanceTypeOffering() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsEc2InstanceTypeOfferingRead, + + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + "instance_type": { + Type: schema.TypeString, + Computed: true, + }, + "location_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + ec2.LocationTypeAvailabilityZone, + ec2.LocationTypeAvailabilityZoneId, + ec2.LocationTypeRegion, + }, false), + }, + "preferred_instance_types": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceAwsEc2InstanceTypeOfferingRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DescribeInstanceTypeOfferingsInput{} + + if v, ok := d.GetOk("filter"); ok { + input.Filters = buildAwsDataSourceFilters(v.(*schema.Set)) + } + + if v, ok := d.GetOk("location_type"); ok { + input.LocationType = aws.String(v.(string)) + } + + var foundInstanceTypes []string + + for { + output, err := conn.DescribeInstanceTypeOfferings(input) + + if err != nil { + return fmt.Errorf("error reading EC2 Instance Type Offerings: %w", err) + } + + if output == nil { + break + } + + for _, instanceTypeOffering := range output.InstanceTypeOfferings { + if instanceTypeOffering == nil { + continue + } + + foundInstanceTypes = append(foundInstanceTypes, aws.StringValue(instanceTypeOffering.InstanceType)) + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + if len(foundInstanceTypes) == 0 { + return fmt.Errorf("no EC2 Instance Type Offerings found matching criteria; try different search") + } + + var resultInstanceType string + + // Search preferred instance types in their given order and set result + // instance type for first match found + if l := d.Get("preferred_instance_types").([]interface{}); len(l) > 0 { + for _, elem := range l { + preferredInstanceType, ok := elem.(string) + + if !ok { + continue + } + + for _, foundInstanceType := range foundInstanceTypes { + if foundInstanceType == preferredInstanceType { + resultInstanceType = preferredInstanceType + break + } + } + + if resultInstanceType != "" { + break + } + } + } + + if resultInstanceType == "" && len(foundInstanceTypes) > 1 { + return fmt.Errorf("multiple EC2 Instance Offerings found matching criteria; try different search") + } + + if resultInstanceType == "" && len(foundInstanceTypes) == 1 { + resultInstanceType = foundInstanceTypes[0] + } + + if resultInstanceType == "" { + return fmt.Errorf("no EC2 Instance Type Offerings found matching criteria; try different search") + } + + d.Set("instance_type", resultInstanceType) + + d.SetId(resource.UniqueId()) + + return nil +} diff --git a/aws/data_source_aws_ec2_instance_type_offering_test.go b/aws/data_source_aws_ec2_instance_type_offering_test.go new file mode 100644 index 00000000000..5da0f721ed4 --- /dev/null +++ b/aws/data_source_aws_ec2_instance_type_offering_test.go @@ -0,0 +1,143 @@ +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 TestAccAWSEc2InstanceTypeOfferingDataSource_Filter(t *testing.T) { + dataSourceName := "data.aws_ec2_instance_type_offering.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEc2InstanceTypeOffering(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2InstanceTypeOfferingDataSourceConfigFilter(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "instance_type"), + ), + }, + }, + }) +} + +func TestAccAWSEc2InstanceTypeOfferingDataSource_LocationType(t *testing.T) { + dataSourceName := "data.aws_ec2_instance_type_offering.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEc2InstanceTypeOffering(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2InstanceTypeOfferingDataSourceConfigLocationType(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "instance_type"), + ), + }, + }, + }) +} + +func TestAccAWSEc2InstanceTypeOfferingDataSource_PreferredInstanceTypes(t *testing.T) { + dataSourceName := "data.aws_ec2_instance_type_offering.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEc2InstanceTypeOffering(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2InstanceTypeOfferingDataSourceConfigPreferredInstanceTypes(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "instance_type", "t3.micro"), + ), + }, + }, + }) +} + +func testAccPreCheckAWSEc2InstanceTypeOffering(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + input := &ec2.DescribeInstanceTypeOfferingsInput{ + MaxResults: aws.Int64(5), + } + + _, err := conn.DescribeInstanceTypeOfferings(input) + + if testAccPreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccAWSEc2InstanceTypeOfferingDataSourceConfigFilter() 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]] + } +} +`) +} + +func testAccAWSEc2InstanceTypeOfferingDataSourceConfigLocationType() string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + state = "available" +} + +# Rather than hardcode an instance type in the testing, +# use the first result from all available offerings. +data "aws_ec2_instance_type_offerings" "test" { + filter { + name = "location" + values = [data.aws_availability_zones.available.names[0]] + } + + location_type = "availability-zone" +} + +data "aws_ec2_instance_type_offering" "test" { + filter { + name = "instance-type" + values = [tolist(data.aws_ec2_instance_type_offerings.test.instance_types)[0]] + } + + filter { + name = "location" + values = [data.aws_availability_zones.available.names[0]] + } + + location_type = "availability-zone" +} +`) +} + +func testAccAWSEc2InstanceTypeOfferingDataSourceConfigPreferredInstanceTypes() string { + return fmt.Sprintf(` +data "aws_ec2_instance_type_offering" "test" { + filter { + name = "instance-type" + values = ["t1.micro", "t2.micro", "t3.micro"] + } + + preferred_instance_types = ["t3.micro", "t2.micro", "t1.micro"] +} +`) +} diff --git a/aws/data_source_aws_ec2_instance_type_offerings.go b/aws/data_source_aws_ec2_instance_type_offerings.go new file mode 100644 index 00000000000..84fac611f41 --- /dev/null +++ b/aws/data_source_aws_ec2_instance_type_offerings.go @@ -0,0 +1,85 @@ +package aws + +import ( + "fmt" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func dataSourceAwsEc2InstanceTypeOfferings() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsEc2InstanceTypeOfferingsRead, + + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + "instance_types": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "location_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + ec2.LocationTypeAvailabilityZone, + ec2.LocationTypeAvailabilityZoneId, + ec2.LocationTypeRegion, + }, false), + }, + }, + } +} + +func dataSourceAwsEc2InstanceTypeOfferingsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DescribeInstanceTypeOfferingsInput{} + + if v, ok := d.GetOk("filter"); ok { + input.Filters = buildAwsDataSourceFilters(v.(*schema.Set)) + } + + if v, ok := d.GetOk("location_type"); ok { + input.LocationType = aws.String(v.(string)) + } + + var instanceTypes []string + + for { + output, err := conn.DescribeInstanceTypeOfferings(input) + + if err != nil { + return fmt.Errorf("error reading EC2 Instance Type Offerings: %w", err) + } + + if output == nil { + break + } + + for _, instanceTypeOffering := range output.InstanceTypeOfferings { + if instanceTypeOffering == nil { + continue + } + + instanceTypes = append(instanceTypes, aws.StringValue(instanceTypeOffering.InstanceType)) + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + if err := d.Set("instance_types", instanceTypes); err != nil { + return fmt.Errorf("error setting instance_types: %s", err) + } + + d.SetId(resource.UniqueId()) + + return nil +} diff --git a/aws/data_source_aws_ec2_instance_type_offerings_test.go b/aws/data_source_aws_ec2_instance_type_offerings_test.go new file mode 100644 index 00000000000..f81bcd5872f --- /dev/null +++ b/aws/data_source_aws_ec2_instance_type_offerings_test.go @@ -0,0 +1,108 @@ +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" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSEc2InstanceTypeOfferingsDataSource_Filter(t *testing.T) { + dataSourceName := "data.aws_ec2_instance_type_offerings.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEc2InstanceTypeOfferings(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2InstanceTypeOfferingsDataSourceConfigFilter(), + Check: resource.ComposeTestCheckFunc( + testAccCheckEc2InstanceTypeOfferingsInstanceTypes(dataSourceName), + ), + }, + }, + }) +} + +func TestAccAWSEc2InstanceTypeOfferingsDataSource_LocationType(t *testing.T) { + dataSourceName := "data.aws_ec2_instance_type_offerings.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEc2InstanceTypeOfferings(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAWSEc2InstanceTypeOfferingsDataSourceConfigLocationType(), + Check: resource.ComposeTestCheckFunc( + testAccCheckEc2InstanceTypeOfferingsInstanceTypes(dataSourceName), + ), + }, + }, + }) +} + +func testAccCheckEc2InstanceTypeOfferingsInstanceTypes(dataSourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[dataSourceName] + if !ok { + return fmt.Errorf("Not found: %s", dataSourceName) + } + + if v := rs.Primary.Attributes["instance_types.#"]; v == "0" { + return fmt.Errorf("expected at least one instance_types result, got none") + } + + return nil + } +} + +func testAccPreCheckAWSEc2InstanceTypeOfferings(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + input := &ec2.DescribeInstanceTypeOfferingsInput{ + MaxResults: aws.Int64(5), + } + + _, err := conn.DescribeInstanceTypeOfferings(input) + + if testAccPreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccAWSEc2InstanceTypeOfferingsDataSourceConfigFilter() string { + return fmt.Sprintf(` +data "aws_ec2_instance_type_offerings" "test" { + filter { + name = "instance-type" + values = ["t2.micro", "t3.micro"] + } +} +`) +} + +func testAccAWSEc2InstanceTypeOfferingsDataSourceConfigLocationType() string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + state = "available" +} + +data "aws_ec2_instance_type_offerings" "test" { + filter { + name = "location" + values = ["${data.aws_availability_zones.available.names[0]}"] + } + + location_type = "availability-zone" +} +`) +} diff --git a/aws/provider.go b/aws/provider.go index abe8391c72f..f627e338ecc 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -195,6 +195,8 @@ func Provider() terraform.ResourceProvider { "aws_ebs_snapshot": dataSourceAwsEbsSnapshot(), "aws_ebs_snapshot_ids": dataSourceAwsEbsSnapshotIds(), "aws_ebs_volume": dataSourceAwsEbsVolume(), + "aws_ec2_instance_type_offering": dataSourceAwsEc2InstanceTypeOffering(), + "aws_ec2_instance_type_offerings": dataSourceAwsEc2InstanceTypeOfferings(), "aws_ec2_transit_gateway": dataSourceAwsEc2TransitGateway(), "aws_ec2_transit_gateway_dx_gateway_attachment": dataSourceAwsEc2TransitGatewayDxGatewayAttachment(), "aws_ec2_transit_gateway_route_table": dataSourceAwsEc2TransitGatewayRouteTable(), diff --git a/website/aws.erb b/website/aws.erb index ac47078a07d..a8ff1aa4d49 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1035,6 +1035,12 @@
  • aws_ebs_volume
  • +
  • + aws_ec2_instance_type_offering +
  • +
  • + aws_ec2_instance_type_offerings +
  • aws_ec2_transit_gateway
  • diff --git a/website/docs/d/ec2_instance_type_offering.html.markdown b/website/docs/d/ec2_instance_type_offering.html.markdown new file mode 100644 index 00000000000..46cf5ba9d83 --- /dev/null +++ b/website/docs/d/ec2_instance_type_offering.html.markdown @@ -0,0 +1,43 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_instance_type_offering" +description: |- + Information about single EC2 Instance Type Offering. +--- + +# Data Source: aws_ec2_instance_type_offering + +Information about single EC2 Instance Type Offering. + +## Example Usage + +```hcl +data "aws_ec2_instance_type_offering" "example" { + filter { + name = "instance-type" + values = ["t1.micro", "t2.micro", "t3.micro"] + } + + preferred_instance_types = ["t3.micro", "t2.micro", "t1.micro"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `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. +* `location_type` - (Optional) Location type. Defaults to `region`. Valid values: `availability-zone`, `availability-zone-id`, and `region`. +* `preferred_instance_types` - (Optional) Ordered list of preferred EC2 Instance Types. The first match in this list will be returned. If no preferred matches are found and the original search returned more than one result, an error is returned. + +### 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: + +* `instance_type` - EC2 Instance Type. diff --git a/website/docs/d/ec2_instance_type_offerings.html.markdown b/website/docs/d/ec2_instance_type_offerings.html.markdown new file mode 100644 index 00000000000..c65c2c4e020 --- /dev/null +++ b/website/docs/d/ec2_instance_type_offerings.html.markdown @@ -0,0 +1,47 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_instance_type_offerings" +description: |- + Information about EC2 Instance Type Offerings. +--- + +# Data Source: aws_ec2_instance_type_offerings + +Information about EC2 Instance Type Offerings. + +## Example Usage + +```hcl +data "aws_ec2_instance_type_offerings" "example" { + filter { + name = "instance-type" + values = ["t2.micro", "t3.micro"] + } + + filter { + name = "location" + values = ["usw2-az4"] + } + + location_type = "availability-zone-id" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `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. +* `location_type` - (Optional) Location type. Defaults to `region`. Valid values: `availability-zone`, `availability-zone-id`, and `region`. + +### 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: + +* `instance_types` - Set of EC2 Instance Types.