Skip to content

Commit

Permalink
Merge pull request #10817 from rpomykala/dedicated-host-branch
Browse files Browse the repository at this point in the history
Add aws_dedicated_host for terraform-provider-aws
  • Loading branch information
ewbankkit authored Sep 27, 2021
2 parents 52ea31f + 2e7a7a4 commit 47bf15a
Show file tree
Hide file tree
Showing 14 changed files with 1,641 additions and 574 deletions.
7 changes: 7 additions & 0 deletions .changelog/10817.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:new-resource
aws_ec2_host
```

```release-note:new-data-source
aws_ec2_host
```
107 changes: 107 additions & 0 deletions aws/data_source_aws_ec2_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package aws

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func dataSourceAwsEc2Host() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsAwsEc2HostRead,

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"auto_placement": {
Type: schema.TypeString,
Computed: true,
},
"availability_zone": {
Type: schema.TypeString,
Computed: true,
},
"cores": {
Type: schema.TypeInt,
Computed: true,
},
"filter": dataSourceFiltersSchema(),
"host_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"host_recovery": {
Type: schema.TypeString,
Computed: true,
},
"instance_family": {
Type: schema.TypeString,
Computed: true,
},
"instance_type": {
Type: schema.TypeString,
Computed: true,
},
"owner_id": {
Type: schema.TypeString,
Computed: true,
},
"sockets": {
Type: schema.TypeInt,
Computed: true,
},
"tags": tagsSchemaComputed(),
"total_vcpus": {
Type: schema.TypeInt,
Computed: true,
},
},
}
}

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

host, err := finder.HostByIDAndFilters(conn, d.Get("host_id").(string), buildAwsDataSourceFilters(d.Get("filter").(*schema.Set)))

if err != nil {
return tfresource.SingularDataSourceFindError("EC2 Host", err)
}

d.SetId(aws.StringValue(host.HostId))

arn := arn.ARN{
Partition: meta.(*AWSClient).partition,
Service: ec2.ServiceName,
Region: meta.(*AWSClient).region,
AccountID: aws.StringValue(host.OwnerId),
Resource: fmt.Sprintf("dedicated-host/%s", d.Id()),
}.String()
d.Set("arn", arn)
d.Set("auto_placement", host.AutoPlacement)
d.Set("availability_zone", host.AvailabilityZone)
d.Set("cores", host.HostProperties.Cores)
d.Set("host_id", host.HostId)
d.Set("host_recovery", host.HostRecovery)
d.Set("instance_family", host.HostProperties.InstanceFamily)
d.Set("instance_type", host.HostProperties.InstanceType)
d.Set("owner_id", host.OwnerId)
d.Set("sockets", host.HostProperties.Sockets)
d.Set("total_vcpus", host.HostProperties.TotalVCpus)

if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(host.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

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

import (
"fmt"
"testing"

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

func TestAccAWSEc2HostDataSource_basic(t *testing.T) {
dataSourceName := "data.aws_ec2_host.test"
resourceName := "aws_ec2_host.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID),
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAWSEc2HostConfig(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"),
resource.TestCheckResourceAttrPair(dataSourceName, "auto_placement", resourceName, "auto_placement"),
resource.TestCheckResourceAttrPair(dataSourceName, "availability_zone", resourceName, "availability_zone"),
resource.TestCheckResourceAttrSet(dataSourceName, "cores"),
resource.TestCheckResourceAttrPair(dataSourceName, "host_id", resourceName, "id"),
resource.TestCheckResourceAttrPair(dataSourceName, "host_recovery", resourceName, "host_recovery"),
resource.TestCheckResourceAttrPair(dataSourceName, "instance_family", resourceName, "instance_family"),
resource.TestCheckResourceAttrPair(dataSourceName, "instance_type", resourceName, "instance_type"),
resource.TestCheckResourceAttrPair(dataSourceName, "owner_id", resourceName, "owner_id"),
resource.TestCheckResourceAttrSet(dataSourceName, "sockets"),
resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"),
resource.TestCheckResourceAttrSet(dataSourceName, "total_vcpus"),
),
},
},
})
}

func TestAccAWSEc2HostDataSource_Filter(t *testing.T) {
dataSourceName := "data.aws_ec2_host.test"
resourceName := "aws_ec2_host.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID),
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAWSEc2HostConfigFilter(rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"),
resource.TestCheckResourceAttrPair(dataSourceName, "auto_placement", resourceName, "auto_placement"),
resource.TestCheckResourceAttrPair(dataSourceName, "availability_zone", resourceName, "availability_zone"),
resource.TestCheckResourceAttrSet(dataSourceName, "cores"),
resource.TestCheckResourceAttrPair(dataSourceName, "host_id", resourceName, "id"),
resource.TestCheckResourceAttrPair(dataSourceName, "host_recovery", resourceName, "host_recovery"),
resource.TestCheckResourceAttrPair(dataSourceName, "instance_family", resourceName, "instance_family"),
resource.TestCheckResourceAttrPair(dataSourceName, "instance_type", resourceName, "instance_type"),
resource.TestCheckResourceAttrPair(dataSourceName, "owner_id", resourceName, "owner_id"),
resource.TestCheckResourceAttrSet(dataSourceName, "sockets"),
resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"),
resource.TestCheckResourceAttrSet(dataSourceName, "total_vcpus"),
),
},
},
})
}

func testAccDataSourceAWSEc2HostConfig(rName string) string {
return composeConfig(testAccAvailableAZsNoOptInConfig(), fmt.Sprintf(`
resource "aws_ec2_host" "test" {
availability_zone = data.aws_availability_zones.available.names[0]
instance_type = "a1.large"
tags = {
Name = %[1]q
}
}
data "aws_ec2_host" "test" {
host_id = aws_ec2_host.test.id
}
`, rName))
}

func testAccDataSourceAWSEc2HostConfigFilter(rName string) string {
return composeConfig(testAccAvailableAZsNoOptInConfig(), fmt.Sprintf(`
resource "aws_ec2_host" "test" {
availability_zone = data.aws_availability_zones.available.names[0]
instance_type = "a1.large"
tags = {
%[1]q = "True"
}
}
data "aws_ec2_host" "test" {
filter {
name = "availability-zone"
values = [aws_ec2_host.test.availability_zone]
}
filter {
name = "instance-type"
values = [aws_ec2_host.test.instance_type]
}
filter {
name = "tag-key"
values = [%[1]q]
}
}
`, rName))
}
5 changes: 5 additions & 0 deletions aws/internal/service/ec2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ const (
ErrCodeInvalidCarrierGatewayIDNotFound = "InvalidCarrierGatewayID.NotFound"
)

const (
ErrCodeClientInvalidHostIDNotFound = "Client.InvalidHostID.NotFound"
ErrCodeInvalidHostIDNotFound = "InvalidHostID.NotFound"
)

const (
ErrCodeInvalidNetworkInterfaceIDNotFound = "InvalidNetworkInterfaceID.NotFound"
)
Expand Down
56 changes: 56 additions & 0 deletions aws/internal/service/ec2/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,62 @@ func ClientVpnRouteByID(conn *ec2.EC2, routeID string) (*ec2.DescribeClientVpnRo
return ClientVpnRoute(conn, endpointID, targetSubnetID, destinationCidr)
}

func HostByID(conn *ec2.EC2, id string) (*ec2.Host, error) {
input := &ec2.DescribeHostsInput{
HostIds: aws.StringSlice([]string{id}),
}

return Host(conn, input)
}

func HostByIDAndFilters(conn *ec2.EC2, id string, filters []*ec2.Filter) (*ec2.Host, error) {
input := &ec2.DescribeHostsInput{}

if id != "" {
input.HostIds = aws.StringSlice([]string{id})
}

if len(filters) > 0 {
input.Filter = filters
}

return Host(conn, input)
}

func Host(conn *ec2.EC2, input *ec2.DescribeHostsInput) (*ec2.Host, error) {
output, err := conn.DescribeHosts(input)

if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidHostIDNotFound) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

if output == nil || len(output.Hosts) == 0 || output.Hosts[0] == nil || output.Hosts[0].HostProperties == nil {
return nil, tfresource.NewEmptyResultError(input)
}

if count := len(output.Hosts); count > 1 {
return nil, tfresource.NewTooManyResultsError(count, input)
}

host := output.Hosts[0]

if state := aws.StringValue(host.State); state == ec2.AllocationStateReleased || state == ec2.AllocationStateReleasedPermanentFailure {
return nil, &resource.NotFoundError{
Message: state,
LastRequest: input,
}
}

return host, nil
}

// InstanceByID looks up a Instance by ID. When not found, returns nil and potentially an API error.
func InstanceByID(conn *ec2.EC2, id string) (*ec2.Instance, error) {
input := &ec2.DescribeInstancesInput{
Expand Down
20 changes: 18 additions & 2 deletions aws/internal/service/ec2/waiter/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,22 @@ func VpnGatewayVpcAttachmentState(conn *ec2.EC2, vpnGatewayID, vpcID string) res
}
}

func HostState(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := finder.HostByID(conn, id)

if tfresource.NotFound(err) {
return nil, "", nil
}

if err != nil {
return nil, "", err
}

return output, aws.StringValue(output.State), nil
}
}

func ManagedPrefixListState(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := finder.ManagedPrefixListByID(conn, id)
Expand All @@ -499,7 +515,7 @@ func ManagedPrefixListState(conn *ec2.EC2, id string) resource.StateRefreshFunc

func VpcEndpointState(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
vpcEndpoint, err := finder.VpcEndpointByID(conn, id)
output, err := finder.VpcEndpointByID(conn, id)

if tfresource.NotFound(err) {
return nil, "", nil
Expand All @@ -509,7 +525,7 @@ func VpcEndpointState(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return nil, "", err
}

return vpcEndpoint, aws.StringValue(vpcEndpoint.State), nil
return output, aws.StringValue(output.State), nil
}
}

Expand Down
Loading

0 comments on commit 47bf15a

Please sign in to comment.