Skip to content

Commit

Permalink
Merge pull request #21993 from csabakollar/f/shield_protection_health…
Browse files Browse the repository at this point in the history
…_check_association

New Resource: aws_shield_protection_health_check_association
  • Loading branch information
ewbankkit authored Jan 12, 2022
2 parents f4a39bc + 5e381d5 commit d5449ef
Show file tree
Hide file tree
Showing 6 changed files with 423 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/21993.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
resource/aws_shield_protection_health_check_association
```
5 changes: 3 additions & 2 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1664,8 +1664,9 @@ func Provider() *schema.Provider {
"aws_sfn_activity": sfn.ResourceActivity(),
"aws_sfn_state_machine": sfn.ResourceStateMachine(),

"aws_shield_protection": shield.ResourceProtection(),
"aws_shield_protection_group": shield.ResourceProtectionGroup(),
"aws_shield_protection": shield.ResourceProtection(),
"aws_shield_protection_group": shield.ResourceProtectionGroup(),
"aws_shield_protection_health_check_association": shield.ResourceProtectionHealthCheckAssociation(),

"aws_signer_signing_job": signer.ResourceSigningJob(),
"aws_signer_signing_profile": signer.ResourceSigningProfile(),
Expand Down
25 changes: 25 additions & 0 deletions internal/service/shield/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package shield

import (
"fmt"
"strings"
)

const protectionHealthCheckAssociationResourceIDSeparator = "+"

func ProtectionHealthCheckAssociationCreateResourceID(protectionId, healthCheckArn string) string {
parts := []string{protectionId, healthCheckArn}
id := strings.Join(parts, protectionHealthCheckAssociationResourceIDSeparator)

return id
}

func ProtectionHealthCheckAssociationParseResourceID(id string) (string, string, error) {
parts := strings.Split(id, protectionHealthCheckAssociationResourceIDSeparator)

if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
return parts[0], parts[1], nil
}

return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected PROTECTIONID%[2]sHEALTHCHECKARN", id, protectionHealthCheckAssociationResourceIDSeparator)
}
125 changes: 125 additions & 0 deletions internal/service/shield/protection_health_check_association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package shield

import (
"fmt"
"log"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/shield"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
)

func ResourceProtectionHealthCheckAssociation() *schema.Resource {
return &schema.Resource{
Create: ResourceProtectionHealthCheckAssociationCreate,
Read: ResourceProtectionHealthCheckAssociationRead,
Delete: ResourceProtectionHealthCheckAssociationDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"shield_protection_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"health_check_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func ResourceProtectionHealthCheckAssociationCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).ShieldConn

protectionId := d.Get("shield_protection_id").(string)
healthCheckArn := d.Get("health_check_arn").(string)
id := ProtectionHealthCheckAssociationCreateResourceID(protectionId, healthCheckArn)

input := &shield.AssociateHealthCheckInput{
ProtectionId: aws.String(protectionId),
HealthCheckArn: aws.String(healthCheckArn),
}

_, err := conn.AssociateHealthCheck(input)
if err != nil {
return fmt.Errorf("error associating Route53 Health Check (%s) with Shield Protected resource (%s): %s", d.Get("health_check_arn"), d.Get("shield_protection_id"), err)
}
d.SetId(id)
return ResourceProtectionHealthCheckAssociationRead(d, meta)
}

func ResourceProtectionHealthCheckAssociationRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).ShieldConn

protectionId, healthCheckArn, err := ProtectionHealthCheckAssociationParseResourceID(d.Id())

if err != nil {
return fmt.Errorf("error parsing Shield Protection and Route53 Health Check Association ID: %w", err)
}

input := &shield.DescribeProtectionInput{
ProtectionId: aws.String(protectionId),
}

resp, err := conn.DescribeProtection(input)

if tfawserr.ErrMessageContains(err, shield.ErrCodeResourceNotFoundException, "") {
log.Printf("[WARN] Shield Protection itself (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error reading Shield Protection Health Check Association (%s): %s", d.Id(), err)
}

isHealthCheck := stringInSlice(strings.Split(healthCheckArn, "/")[1], aws.StringValueSlice(resp.Protection.HealthCheckIds))
if !isHealthCheck {
log.Printf("[WARN] Shield Protection Health Check Association (%s) not found, removing from state", d.Id())
d.SetId("")
}

d.Set("health_check_arn", healthCheckArn)
d.Set("shield_protection_id", resp.Protection.Id)

return nil
}

func ResourceProtectionHealthCheckAssociationDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).ShieldConn

protectionId, healthCheckId, err := ProtectionHealthCheckAssociationParseResourceID(d.Id())

if err != nil {
return fmt.Errorf("error parsing Shield Protection and Route53 Health Check Association ID: %w", err)
}

input := &shield.DisassociateHealthCheckInput{
ProtectionId: aws.String(protectionId),
HealthCheckArn: aws.String(healthCheckId),
}

_, err = conn.DisassociateHealthCheck(input)

if err != nil {
return fmt.Errorf("error disassociating Route53 Health Check (%s) from Shield Protected resource (%s): %s", d.Get("health_check_arn"), d.Get("shield_protection_id"), err)
}
return nil
}

func stringInSlice(expected string, list []string) bool {
for _, item := range list {
if item == expected {
return true
}
}
return false
}
192 changes: 192 additions & 0 deletions internal/service/shield/protection_health_check_association_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package shield_test

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/shield"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield"
)

func TestAccShieldProtectionHealthCheckAssociation_basic(t *testing.T) {
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_shield_protection_health_check_association.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(t)
acctest.PreCheckPartitionHasService(shield.EndpointsID, t)
testAccPreCheck(t)
},
ErrorCheck: acctest.ErrorCheck(t, shield.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckAWSShieldProtectionHealthCheckAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccShieldProtectionaHealthCheckAssociationConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSShieldProtectionHealthCheckAssociationExists(resourceName),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccShieldProtectionHealthCheckAssociation_disappears(t *testing.T) {
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_shield_protection_health_check_association.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(t)
acctest.PreCheckPartitionHasService(shield.EndpointsID, t)
testAccPreCheck(t)
},
ErrorCheck: acctest.ErrorCheck(t, shield.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckAWSShieldProtectionHealthCheckAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccShieldProtectionaHealthCheckAssociationConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSShieldProtectionHealthCheckAssociationExists(resourceName),
acctest.CheckResourceDisappears(acctest.Provider, tfshield.ResourceProtectionHealthCheckAssociation(), resourceName),
),
ExpectNonEmptyPlan: true,
},
},
})
}

func testAccCheckAWSShieldProtectionHealthCheckAssociationDestroy(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_shield_protection_health_check_association" {
continue
}

protectionId, _, err := tfshield.ProtectionHealthCheckAssociationParseResourceID(rs.Primary.ID)

if err != nil {
return err
}

input := &shield.DescribeProtectionInput{
ProtectionId: aws.String(protectionId),
}

resp, err := conn.DescribeProtection(input)

if tfawserr.ErrMessageContains(err, shield.ErrCodeResourceNotFoundException, "") {
continue
}

if err != nil {
return err
}

if resp != nil && resp.Protection != nil && len(aws.StringValueSlice(resp.Protection.HealthCheckIds)) == 0 {
return fmt.Errorf("The Shield protection HealthCheck with IDs %v still exists", aws.StringValueSlice(resp.Protection.HealthCheckIds))
}
}

return nil
}

func testAccCheckAWSShieldProtectionHealthCheckAssociationExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Not found: %s", resourceName)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No Shield Protection and Route53 Health Check Association ID is set")
}

protectionId, _, err := tfshield.ProtectionHealthCheckAssociationParseResourceID(rs.Primary.ID)

if err != nil {
return err
}

conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn

input := &shield.DescribeProtectionInput{
ProtectionId: aws.String(protectionId),
}

resp, err := conn.DescribeProtection(input)

if err != nil {
return err
}

if resp == nil || resp.Protection == nil {
return fmt.Errorf("The Shield protection does not exist")
}

if resp.Protection.HealthCheckIds == nil || len(aws.StringValueSlice(resp.Protection.HealthCheckIds)) != 1 {
return fmt.Errorf("The Shield protection HealthCheck does not exist")
}

return nil
}
}

func testAccShieldProtectionaHealthCheckAssociationConfig(rName string) string {
return fmt.Sprintf(`
data "aws_availability_zones" "available" {
state = "available"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
data "aws_partition" "current" {}
resource "aws_eip" "test" {
vpc = true
tags = {
foo = "bar"
Name = %[1]q
}
}
resource "aws_shield_protection" "test" {
name = %[1]q
resource_arn = "arn:${data.aws_partition.current.partition}:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:eip-allocation/${aws_eip.test.id}"
}
resource "aws_route53_health_check" "test" {
fqdn = "example.com"
port = 80
type = "HTTP"
resource_path = "/"
failure_threshold = "5"
request_interval = "30"
tags = {
Name = "tf-test-health-check"
}
}
resource "aws_shield_protection_health_check_association" "test" {
shield_protection_id = aws_shield_protection.test.id
health_check_arn = aws_route53_health_check.test.arn
}
`, rName)
}
Loading

0 comments on commit d5449ef

Please sign in to comment.