diff --git a/aws/internal/service/networkfirewall/waiter/waiter.go b/aws/internal/service/networkfirewall/waiter/waiter.go index 903fb38bcba..9cc70453e70 100644 --- a/aws/internal/service/networkfirewall/waiter/waiter.go +++ b/aws/internal/service/networkfirewall/waiter/waiter.go @@ -40,6 +40,10 @@ func FirewallUpdated(ctx context.Context, conn *networkfirewall.NetworkFirewall, Target: []string{networkfirewall.FirewallStatusValueReady}, Refresh: FirewallUpdatedStatus(ctx, conn, arn), Timeout: FirewallTimeout, + // Delay added to account for Associate/DisassociateSubnet calls that return + // a READY status immediately after the method is called instead of immediately + // returning PROVISIONING + Delay: 30 * time.Second, } outputRaw, err := stateConf.WaitForState() diff --git a/aws/resource_aws_networkfirewall_firewall.go b/aws/resource_aws_networkfirewall_firewall.go index 2f8c1805623..b5820d4ab1d 100644 --- a/aws/resource_aws_networkfirewall_firewall.go +++ b/aws/resource_aws_networkfirewall_firewall.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/service/networkfirewall" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" @@ -27,6 +28,12 @@ func resourceAwsNetworkFirewallFirewall() *schema.Resource { StateContext: schema.ImportStatePassthroughContext, }, + CustomizeDiff: customdiff.Sequence( + customdiff.ComputedIf("firewall_status", func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) bool { + return diff.HasChange("subnet_mapping") + }), + ), + Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -50,6 +57,42 @@ func resourceAwsNetworkFirewallFirewall() *schema.Resource { Type: schema.TypeBool, Optional: true, }, + "firewall_status": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sync_states": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "availability_zone": { + Type: schema.TypeString, + Computed: true, + }, + "attachment": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "endpoint_id": { + Type: schema.TypeString, + Computed: true, + }, + "subnet_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, "name": { Type: schema.TypeString, Required: true, @@ -162,6 +205,7 @@ func resourceAwsNetworkFirewallFirewallRead(ctx context.Context, d *schema.Resou d.Set("name", firewall.FirewallName) d.Set("firewall_policy_arn", firewall.FirewallPolicyArn) d.Set("firewall_policy_change_protection", firewall.FirewallPolicyChangeProtection) + d.Set("firewall_status", flattenNetworkFirewallFirewallStatus(output.FirewallStatus)) d.Set("subnet_change_protection", firewall.SubnetChangeProtection) d.Set("update_token", output.UpdateToken) d.Set("vpc_id", firewall.VpcId) @@ -380,6 +424,48 @@ func expandNetworkFirewallSubnetMappingIds(l []interface{}) []string { return ids } +func flattenNetworkFirewallFirewallStatus(status *networkfirewall.FirewallStatus) []interface{} { + if status == nil { + return nil + } + + m := map[string]interface{}{ + "sync_states": flattenNetworkFirewallSyncStates(status.SyncStates), + } + + return []interface{}{m} +} + +func flattenNetworkFirewallSyncStates(s map[string]*networkfirewall.SyncState) []interface{} { + if s == nil { + return nil + } + + syncStates := make([]interface{}, 0, len(s)) + for k, v := range s { + m := map[string]interface{}{ + "availability_zone": k, + "attachment": flattenNetworkFirewallSyncStateAttachment(v.Attachment), + } + syncStates = append(syncStates, m) + } + + return syncStates +} + +func flattenNetworkFirewallSyncStateAttachment(a *networkfirewall.Attachment) []interface{} { + if a == nil { + return nil + } + + m := map[string]interface{}{ + "endpoint_id": aws.StringValue(a.EndpointId), + "subnet_id": aws.StringValue(a.SubnetId), + } + + return []interface{}{m} +} + func flattenNetworkFirewallSubnetMappings(sm []*networkfirewall.SubnetMapping) []interface{} { mappings := make([]interface{}, 0, len(sm)) for _, s := range sm { diff --git a/aws/resource_aws_networkfirewall_firewall_test.go b/aws/resource_aws_networkfirewall_firewall_test.go index 9f5366702f8..97db1dd6dc9 100644 --- a/aws/resource_aws_networkfirewall_firewall_test.go +++ b/aws/resource_aws_networkfirewall_firewall_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "regexp" "testing" "github.com/aws/aws-sdk-go/aws" @@ -97,11 +98,19 @@ func TestAccAwsNetworkFirewallFirewall_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "delete_protection", "false"), resource.TestCheckResourceAttr(resourceName, "description", ""), resource.TestCheckResourceAttrPair(resourceName, "firewall_policy_arn", policyResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "firewall_status.#", "1"), + resource.TestCheckResourceAttr(resourceName, "firewall_status.0.sync_states.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.availability_zone", subnetResourceName, "availability_zone"), + resource.TestMatchTypeSetElemNestedAttrs(resourceName, "firewall_status.0.sync_states.*", map[string]*regexp.Regexp{ + "attachment.0.endpoint_id": regexp.MustCompile(`vpce-`), + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.attachment.0.subnet_id", subnetResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttrPair(resourceName, "vpc_id", vpcResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "1"), tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", subnetResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrSet(resourceName, "update_token"), ), }, { @@ -216,6 +225,13 @@ func TestAccAwsNetworkFirewallFirewall_subnetMappings_updateSubnet(t *testing.T) Config: testAccNetworkFirewallFirewall_updateSubnet(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAwsNetworkFirewallFirewallExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "firewall_status.#", "1"), + resource.TestCheckResourceAttr(resourceName, "firewall_status.0.sync_states.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.availability_zone", updateSubnetResourceName, "availability_zone"), + resource.TestMatchTypeSetElemNestedAttrs(resourceName, "firewall_status.0.sync_states.*", map[string]*regexp.Regexp{ + "attachment.0.endpoint_id": regexp.MustCompile(`vpce-`), + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.attachment.0.subnet_id", updateSubnetResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "1"), tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", updateSubnetResourceName, "id"), ), @@ -252,6 +268,12 @@ func TestAccAwsNetworkFirewallFirewall_subnetMappings_updateMultipleSubnets(t *t Config: testAccNetworkFirewallFirewall_updateMultipleSubnets(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAwsNetworkFirewallFirewallExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "firewall_status.#", "1"), + resource.TestCheckResourceAttr(resourceName, "firewall_status.0.sync_states.#", "2"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.availability_zone", subnetResourceName, "availability_zone"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.attachment.0.subnet_id", subnetResourceName, "id"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.availability_zone", updateSubnetResourceName, "availability_zone"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.attachment.0.subnet_id", updateSubnetResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "2"), tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", subnetResourceName, "id"), tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", updateSubnetResourceName, "id"), @@ -261,6 +283,13 @@ func TestAccAwsNetworkFirewallFirewall_subnetMappings_updateMultipleSubnets(t *t Config: testAccNetworkFirewallFirewall_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAwsNetworkFirewallFirewallExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "firewall_status.#", "1"), + resource.TestCheckResourceAttr(resourceName, "firewall_status.0.sync_states.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.availability_zone", subnetResourceName, "availability_zone"), + resource.TestMatchTypeSetElemNestedAttrs(resourceName, "firewall_status.0.sync_states.*", map[string]*regexp.Regexp{ + "attachment.0.endpoint_id": regexp.MustCompile(`vpce-`), + }), + resource.TestCheckTypeSetElemAttrPair(resourceName, "firewall_status.0.sync_states.*.attachment.0.subnet_id", subnetResourceName, "id"), resource.TestCheckResourceAttr(resourceName, "subnet_mapping.#", "1"), tfawsresource.TestCheckTypeSetElemAttrPair(resourceName, "subnet_mapping.*.subnet_id", subnetResourceName, "id"), ), diff --git a/website/docs/r/networkfirewall_firewall.html.markdown b/website/docs/r/networkfirewall_firewall.html.markdown index caa5196277a..ca93fdabb73 100644 --- a/website/docs/r/networkfirewall_firewall.html.markdown +++ b/website/docs/r/networkfirewall_firewall.html.markdown @@ -64,6 +64,13 @@ In addition to all arguments above, the following attributes are exported: * `arn` - The Amazon Resource Name (ARN) that identifies the firewall. +* `firewall_status` - Nested list of information about the current status of the firewall. + * `sync_states` - Set of subnets configured for use by the firewall. + * `attachment` - Nested list describing the attachment status of the firewall's association with a single VPC subnet. + * `endpoint_id` - The identifier of the firewall endpoint that AWS Network Firewall has instantiated in the subnet. You use this to identify the firewall endpoint in the VPC route tables, when you redirect the VPC traffic through the endpoint. + * `subnet_id` - The unique identifier of the subnet that you've specified to be used for a firewall endpoint. + * `availability_zone` - The Availability Zone where the subnet is configured. + * `update_token` - A string token used when updating a firewall. ## Import