mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-20 11:48:24 -06:00
Fixes #4721. It seems there may be some eventual consistency in the API for network ACLs. This fix doesn't use resource.WaitForState() as there the NACL is not something that can be looked up by ID and has a property which determines if it is present. Instead we reuse the findNetworkAclRule function which the Read function exhibiting the problem uses, and retry over a 3 minute period, returning an error message informing the user that running `terraform apply` again will likely allow them to continue.
248 lines
7.3 KiB
Go
248 lines
7.3 KiB
Go
package aws
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
)
|
|
|
|
func resourceAwsNetworkAclRule() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceAwsNetworkAclRuleCreate,
|
|
Read: resourceAwsNetworkAclRuleRead,
|
|
Delete: resourceAwsNetworkAclRuleDelete,
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"network_acl_id": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"rule_number": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"egress": &schema.Schema{
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Default: false,
|
|
},
|
|
"protocol": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"rule_action": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"cidr_block": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"from_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"to_port": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"icmp_type": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"icmp_code": &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceAwsNetworkAclRuleCreate(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
protocol := d.Get("protocol").(string)
|
|
p, protocolErr := strconv.Atoi(protocol)
|
|
if protocolErr != nil {
|
|
var ok bool
|
|
p, ok = protocolIntegers()[protocol]
|
|
if !ok {
|
|
return fmt.Errorf("Invalid Protocol %s for rule %#v", protocol, d.Get("rule_number").(int))
|
|
}
|
|
}
|
|
log.Printf("[INFO] Transformed Protocol %s into %d", protocol, p)
|
|
|
|
params := &ec2.CreateNetworkAclEntryInput{
|
|
NetworkAclId: aws.String(d.Get("network_acl_id").(string)),
|
|
Egress: aws.Bool(d.Get("egress").(bool)),
|
|
RuleNumber: aws.Int64(int64(d.Get("rule_number").(int))),
|
|
Protocol: aws.String(strconv.Itoa(p)),
|
|
CidrBlock: aws.String(d.Get("cidr_block").(string)),
|
|
RuleAction: aws.String(d.Get("rule_action").(string)),
|
|
PortRange: &ec2.PortRange{
|
|
From: aws.Int64(int64(d.Get("from_port").(int))),
|
|
To: aws.Int64(int64(d.Get("to_port").(int))),
|
|
},
|
|
}
|
|
|
|
// Specify additional required fields for ICMP
|
|
if p == 1 {
|
|
params.IcmpTypeCode = &ec2.IcmpTypeCode{}
|
|
if v, ok := d.GetOk("icmp_code"); ok {
|
|
params.IcmpTypeCode.Code = aws.Int64(int64(v.(int)))
|
|
}
|
|
if v, ok := d.GetOk("icmp_type"); ok {
|
|
params.IcmpTypeCode.Type = aws.Int64(int64(v.(int)))
|
|
}
|
|
}
|
|
|
|
log.Printf("[INFO] Creating Network Acl Rule: %d (%t)", d.Get("rule_number").(int), d.Get("egress").(bool))
|
|
_, err := conn.CreateNetworkAclEntry(params)
|
|
if err != nil {
|
|
return fmt.Errorf("Error Creating Network Acl Rule: %s", err.Error())
|
|
}
|
|
d.SetId(networkAclIdRuleNumberEgressHash(d.Get("network_acl_id").(string), d.Get("rule_number").(int), d.Get("egress").(bool), d.Get("protocol").(string)))
|
|
|
|
// It appears it might be a while until the newly created rule is visible via the
|
|
// API (see issue GH-4721). Retry the `findNetworkAclRule` function until it is
|
|
// visible (which in most cases is likely immediately).
|
|
err = resource.Retry(3*time.Minute, func() error {
|
|
_, findErr := findNetworkAclRule(d, meta)
|
|
if findErr != nil {
|
|
return findErr
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("Created Network ACL Rule was not visible in API within 3 minute period. Running 'terraform apply' again will resume infrastructure creation.")
|
|
}
|
|
|
|
return resourceAwsNetworkAclRuleRead(d, meta)
|
|
}
|
|
|
|
func resourceAwsNetworkAclRuleRead(d *schema.ResourceData, meta interface{}) error {
|
|
resp, err := findNetworkAclRule(d, meta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.Set("rule_number", resp.RuleNumber)
|
|
d.Set("cidr_block", resp.CidrBlock)
|
|
d.Set("egress", resp.Egress)
|
|
if resp.IcmpTypeCode != nil {
|
|
d.Set("icmp_code", resp.IcmpTypeCode.Code)
|
|
d.Set("icmp_type", resp.IcmpTypeCode.Type)
|
|
}
|
|
if resp.PortRange != nil {
|
|
d.Set("from_port", resp.PortRange.From)
|
|
d.Set("to_port", resp.PortRange.To)
|
|
}
|
|
|
|
d.Set("rule_action", resp.RuleAction)
|
|
|
|
p, protocolErr := strconv.Atoi(*resp.Protocol)
|
|
log.Printf("[INFO] Converting the protocol %v", p)
|
|
if protocolErr == nil {
|
|
var ok bool
|
|
protocol, ok := protocolStrings(protocolIntegers())[p]
|
|
if !ok {
|
|
return fmt.Errorf("Invalid Protocol %s for rule %#v", *resp.Protocol, d.Get("rule_number").(int))
|
|
}
|
|
log.Printf("[INFO] Transformed Protocol %s back into %s", *resp.Protocol, protocol)
|
|
d.Set("protocol", protocol)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceAwsNetworkAclRuleDelete(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
params := &ec2.DeleteNetworkAclEntryInput{
|
|
NetworkAclId: aws.String(d.Get("network_acl_id").(string)),
|
|
RuleNumber: aws.Int64(int64(d.Get("rule_number").(int))),
|
|
Egress: aws.Bool(d.Get("egress").(bool)),
|
|
}
|
|
|
|
log.Printf("[INFO] Deleting Network Acl Rule: %s", d.Id())
|
|
_, err := conn.DeleteNetworkAclEntry(params)
|
|
if err != nil {
|
|
return fmt.Errorf("Error Deleting Network Acl Rule: %s", err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func findNetworkAclRule(d *schema.ResourceData, meta interface{}) (*ec2.NetworkAclEntry, error) {
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
filters := make([]*ec2.Filter, 0, 2)
|
|
ruleNumberFilter := &ec2.Filter{
|
|
Name: aws.String("entry.rule-number"),
|
|
Values: []*string{aws.String(fmt.Sprintf("%v", d.Get("rule_number").(int)))},
|
|
}
|
|
filters = append(filters, ruleNumberFilter)
|
|
egressFilter := &ec2.Filter{
|
|
Name: aws.String("entry.egress"),
|
|
Values: []*string{aws.String(fmt.Sprintf("%v", d.Get("egress").(bool)))},
|
|
}
|
|
filters = append(filters, egressFilter)
|
|
params := &ec2.DescribeNetworkAclsInput{
|
|
NetworkAclIds: []*string{aws.String(d.Get("network_acl_id").(string))},
|
|
Filters: filters,
|
|
}
|
|
|
|
log.Printf("[INFO] Describing Network Acl: %s", d.Get("network_acl_id").(string))
|
|
log.Printf("[INFO] Describing Network Acl with the Filters %#v", params)
|
|
resp, err := conn.DescribeNetworkAcls(params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error Finding Network Acl Rule %d: %s", d.Get("rule_number").(int), err.Error())
|
|
}
|
|
|
|
if resp == nil || len(resp.NetworkAcls) != 1 || resp.NetworkAcls[0] == nil {
|
|
return nil, fmt.Errorf(
|
|
"Expected to find one Network ACL, got: %#v",
|
|
resp.NetworkAcls)
|
|
}
|
|
networkAcl := resp.NetworkAcls[0]
|
|
if networkAcl.Entries != nil {
|
|
for _, i := range networkAcl.Entries {
|
|
if *i.RuleNumber == int64(d.Get("rule_number").(int)) && *i.Egress == d.Get("egress").(bool) {
|
|
return i, nil
|
|
}
|
|
}
|
|
}
|
|
return nil, fmt.Errorf(
|
|
"Expected the Network ACL to have Entries, got: %#v",
|
|
networkAcl)
|
|
|
|
}
|
|
|
|
func networkAclIdRuleNumberEgressHash(networkAclId string, ruleNumber int, egress bool, protocol string) string {
|
|
var buf bytes.Buffer
|
|
buf.WriteString(fmt.Sprintf("%s-", networkAclId))
|
|
buf.WriteString(fmt.Sprintf("%d-", ruleNumber))
|
|
buf.WriteString(fmt.Sprintf("%t-", egress))
|
|
buf.WriteString(fmt.Sprintf("%s-", protocol))
|
|
return fmt.Sprintf("nacl-%d", hashcode.String(buf.String()))
|
|
}
|