github.com/cbroglie/terraform@v0.7.0-rc3.0.20170410193827-735dfc416d46/builtin/providers/aws/resource_aws_network_acl_rule.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "strconv" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/service/ec2" 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsNetworkAclRule() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsNetworkAclRuleCreate, 20 Read: resourceAwsNetworkAclRuleRead, 21 Delete: resourceAwsNetworkAclRuleDelete, 22 23 Schema: map[string]*schema.Schema{ 24 "network_acl_id": { 25 Type: schema.TypeString, 26 Required: true, 27 ForceNew: true, 28 }, 29 "rule_number": { 30 Type: schema.TypeInt, 31 Required: true, 32 ForceNew: true, 33 }, 34 "egress": { 35 Type: schema.TypeBool, 36 Optional: true, 37 ForceNew: true, 38 Default: false, 39 }, 40 "protocol": { 41 Type: schema.TypeString, 42 Required: true, 43 ForceNew: true, 44 DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { 45 if old == "all" && new == "-1" || old == "-1" && new == "all" { 46 return true 47 } 48 return false 49 }, 50 }, 51 "rule_action": { 52 Type: schema.TypeString, 53 Required: true, 54 ForceNew: true, 55 }, 56 "cidr_block": { 57 Type: schema.TypeString, 58 Optional: true, 59 ForceNew: true, 60 }, 61 "ipv6_cidr_block": { 62 Type: schema.TypeString, 63 Optional: true, 64 ForceNew: true, 65 }, 66 "from_port": { 67 Type: schema.TypeInt, 68 Optional: true, 69 ForceNew: true, 70 }, 71 "to_port": { 72 Type: schema.TypeInt, 73 Optional: true, 74 ForceNew: true, 75 }, 76 "icmp_type": { 77 Type: schema.TypeString, 78 Optional: true, 79 ForceNew: true, 80 ValidateFunc: validateICMPArgumentValue, 81 }, 82 "icmp_code": { 83 Type: schema.TypeString, 84 Optional: true, 85 ForceNew: true, 86 ValidateFunc: validateICMPArgumentValue, 87 }, 88 }, 89 } 90 } 91 92 func resourceAwsNetworkAclRuleCreate(d *schema.ResourceData, meta interface{}) error { 93 conn := meta.(*AWSClient).ec2conn 94 95 protocol := d.Get("protocol").(string) 96 p, protocolErr := strconv.Atoi(protocol) 97 if protocolErr != nil { 98 var ok bool 99 p, ok = protocolIntegers()[protocol] 100 if !ok { 101 return fmt.Errorf("Invalid Protocol %s for rule %d", protocol, d.Get("rule_number").(int)) 102 } 103 } 104 log.Printf("[INFO] Transformed Protocol %s into %d", protocol, p) 105 106 params := &ec2.CreateNetworkAclEntryInput{ 107 NetworkAclId: aws.String(d.Get("network_acl_id").(string)), 108 Egress: aws.Bool(d.Get("egress").(bool)), 109 RuleNumber: aws.Int64(int64(d.Get("rule_number").(int))), 110 Protocol: aws.String(strconv.Itoa(p)), 111 RuleAction: aws.String(d.Get("rule_action").(string)), 112 PortRange: &ec2.PortRange{ 113 From: aws.Int64(int64(d.Get("from_port").(int))), 114 To: aws.Int64(int64(d.Get("to_port").(int))), 115 }, 116 } 117 118 cidr, hasCidr := d.GetOk("cidr_block") 119 ipv6Cidr, hasIpv6Cidr := d.GetOk("ipv6_cidr_block") 120 121 if hasCidr == false && hasIpv6Cidr == false { 122 return fmt.Errorf("Either `cidr_block` or `ipv6_cidr_block` must be defined") 123 } 124 125 if hasCidr { 126 params.CidrBlock = aws.String(cidr.(string)) 127 } 128 129 if hasIpv6Cidr { 130 params.Ipv6CidrBlock = aws.String(ipv6Cidr.(string)) 131 } 132 133 // Specify additional required fields for ICMP. For the list 134 // of ICMP codes and types, see: http://www.nthelp.com/icmp.html 135 if p == 1 { 136 params.IcmpTypeCode = &ec2.IcmpTypeCode{} 137 if v, ok := d.GetOk("icmp_type"); ok { 138 icmpType, err := strconv.Atoi(v.(string)) 139 if err != nil { 140 return fmt.Errorf("Unable to parse ICMP type %s for rule %d", v, d.Get("rule_number").(int)) 141 } 142 params.IcmpTypeCode.Type = aws.Int64(int64(icmpType)) 143 log.Printf("[DEBUG] Got ICMP type %d for rule %d", icmpType, d.Get("rule_number").(int)) 144 } 145 if v, ok := d.GetOk("icmp_code"); ok { 146 icmpCode, err := strconv.Atoi(v.(string)) 147 if err != nil { 148 return fmt.Errorf("Unable to parse ICMP code %s for rule %d", v, d.Get("rule_number").(int)) 149 } 150 params.IcmpTypeCode.Code = aws.Int64(int64(icmpCode)) 151 log.Printf("[DEBUG] Got ICMP code %d for rule %d", icmpCode, d.Get("rule_number").(int)) 152 } 153 } 154 155 log.Printf("[INFO] Creating Network Acl Rule: %d (%t)", d.Get("rule_number").(int), d.Get("egress").(bool)) 156 _, err := conn.CreateNetworkAclEntry(params) 157 if err != nil { 158 return fmt.Errorf("Error Creating Network Acl Rule: %s", err.Error()) 159 } 160 d.SetId(networkAclIdRuleNumberEgressHash(d.Get("network_acl_id").(string), d.Get("rule_number").(int), d.Get("egress").(bool), d.Get("protocol").(string))) 161 162 // It appears it might be a while until the newly created rule is visible via the 163 // API (see issue GH-4721). Retry the `findNetworkAclRule` function until it is 164 // visible (which in most cases is likely immediately). 165 err = resource.Retry(3*time.Minute, func() *resource.RetryError { 166 _, findErr := findNetworkAclRule(d, meta) 167 if findErr != nil { 168 return resource.RetryableError(findErr) 169 } 170 171 return nil 172 }) 173 if err != nil { 174 return fmt.Errorf("Created Network ACL Rule was not visible in API within 3 minute period. Running 'terraform apply' again will resume infrastructure creation.") 175 } 176 177 return resourceAwsNetworkAclRuleRead(d, meta) 178 } 179 180 func resourceAwsNetworkAclRuleRead(d *schema.ResourceData, meta interface{}) error { 181 resp, err := findNetworkAclRule(d, meta) 182 if err != nil { 183 return err 184 } 185 186 d.Set("rule_number", resp.RuleNumber) 187 d.Set("cidr_block", resp.CidrBlock) 188 d.Set("ipv6_cidr_block", resp.Ipv6CidrBlock) 189 d.Set("egress", resp.Egress) 190 if resp.IcmpTypeCode != nil { 191 d.Set("icmp_code", resp.IcmpTypeCode.Code) 192 d.Set("icmp_type", resp.IcmpTypeCode.Type) 193 } 194 if resp.PortRange != nil { 195 d.Set("from_port", resp.PortRange.From) 196 d.Set("to_port", resp.PortRange.To) 197 } 198 199 d.Set("rule_action", resp.RuleAction) 200 201 p, protocolErr := strconv.Atoi(*resp.Protocol) 202 log.Printf("[INFO] Converting the protocol %v", p) 203 if protocolErr == nil { 204 var ok bool 205 protocol, ok := protocolStrings(protocolIntegers())[p] 206 if !ok { 207 return fmt.Errorf("Invalid Protocol %s for rule %d", *resp.Protocol, d.Get("rule_number").(int)) 208 } 209 log.Printf("[INFO] Transformed Protocol %s back into %s", *resp.Protocol, protocol) 210 d.Set("protocol", protocol) 211 } 212 213 return nil 214 } 215 216 func resourceAwsNetworkAclRuleDelete(d *schema.ResourceData, meta interface{}) error { 217 conn := meta.(*AWSClient).ec2conn 218 219 params := &ec2.DeleteNetworkAclEntryInput{ 220 NetworkAclId: aws.String(d.Get("network_acl_id").(string)), 221 RuleNumber: aws.Int64(int64(d.Get("rule_number").(int))), 222 Egress: aws.Bool(d.Get("egress").(bool)), 223 } 224 225 log.Printf("[INFO] Deleting Network Acl Rule: %s", d.Id()) 226 _, err := conn.DeleteNetworkAclEntry(params) 227 if err != nil { 228 return fmt.Errorf("Error Deleting Network Acl Rule: %s", err.Error()) 229 } 230 231 return nil 232 } 233 234 func findNetworkAclRule(d *schema.ResourceData, meta interface{}) (*ec2.NetworkAclEntry, error) { 235 conn := meta.(*AWSClient).ec2conn 236 237 filters := make([]*ec2.Filter, 0, 2) 238 ruleNumberFilter := &ec2.Filter{ 239 Name: aws.String("entry.rule-number"), 240 Values: []*string{aws.String(fmt.Sprintf("%d", d.Get("rule_number").(int)))}, 241 } 242 filters = append(filters, ruleNumberFilter) 243 egressFilter := &ec2.Filter{ 244 Name: aws.String("entry.egress"), 245 Values: []*string{aws.String(fmt.Sprintf("%v", d.Get("egress").(bool)))}, 246 } 247 filters = append(filters, egressFilter) 248 params := &ec2.DescribeNetworkAclsInput{ 249 NetworkAclIds: []*string{aws.String(d.Get("network_acl_id").(string))}, 250 Filters: filters, 251 } 252 253 log.Printf("[INFO] Describing Network Acl: %s", d.Get("network_acl_id").(string)) 254 log.Printf("[INFO] Describing Network Acl with the Filters %#v", params) 255 resp, err := conn.DescribeNetworkAcls(params) 256 if err != nil { 257 return nil, fmt.Errorf("Error Finding Network Acl Rule %d: %s", d.Get("rule_number").(int), err.Error()) 258 } 259 260 if resp == nil || len(resp.NetworkAcls) != 1 || resp.NetworkAcls[0] == nil { 261 return nil, fmt.Errorf( 262 "Expected to find one Network ACL, got: %#v", 263 resp.NetworkAcls) 264 } 265 networkAcl := resp.NetworkAcls[0] 266 if networkAcl.Entries != nil { 267 for _, i := range networkAcl.Entries { 268 if *i.RuleNumber == int64(d.Get("rule_number").(int)) && *i.Egress == d.Get("egress").(bool) { 269 return i, nil 270 } 271 } 272 } 273 return nil, fmt.Errorf( 274 "Expected the Network ACL to have Entries, got: %#v", 275 networkAcl) 276 277 } 278 279 func networkAclIdRuleNumberEgressHash(networkAclId string, ruleNumber int, egress bool, protocol string) string { 280 var buf bytes.Buffer 281 buf.WriteString(fmt.Sprintf("%s-", networkAclId)) 282 buf.WriteString(fmt.Sprintf("%d-", ruleNumber)) 283 buf.WriteString(fmt.Sprintf("%t-", egress)) 284 buf.WriteString(fmt.Sprintf("%s-", protocol)) 285 return fmt.Sprintf("nacl-%d", hashcode.String(buf.String())) 286 } 287 288 func validateICMPArgumentValue(v interface{}, k string) (ws []string, errors []error) { 289 value := v.(string) 290 _, err := strconv.Atoi(value) 291 if len(value) == 0 || err != nil { 292 errors = append(errors, fmt.Errorf("%q must be an integer value: %q", k, value)) 293 } 294 return 295 }