github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/builtin/providers/aws/resource_aws_network_acl.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "time" 8 9 "github.com/hashicorp/aws-sdk-go/aws" 10 "github.com/hashicorp/aws-sdk-go/gen/ec2" 11 "github.com/hashicorp/terraform/helper/hashcode" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 func resourceAwsNetworkAcl() *schema.Resource { 17 18 return &schema.Resource{ 19 Create: resourceAwsNetworkAclCreate, 20 Read: resourceAwsNetworkAclRead, 21 Delete: resourceAwsNetworkAclDelete, 22 Update: resourceAwsNetworkAclUpdate, 23 24 Schema: map[string]*schema.Schema{ 25 "vpc_id": &schema.Schema{ 26 Type: schema.TypeString, 27 Required: true, 28 ForceNew: true, 29 Computed: false, 30 }, 31 "subnet_id": &schema.Schema{ 32 Type: schema.TypeString, 33 Optional: true, 34 ForceNew: true, 35 Computed: false, 36 }, 37 "ingress": &schema.Schema{ 38 Type: schema.TypeSet, 39 Required: false, 40 Optional: true, 41 Elem: &schema.Resource{ 42 Schema: map[string]*schema.Schema{ 43 "from_port": &schema.Schema{ 44 Type: schema.TypeInt, 45 Required: true, 46 }, 47 "to_port": &schema.Schema{ 48 Type: schema.TypeInt, 49 Required: true, 50 }, 51 "rule_no": &schema.Schema{ 52 Type: schema.TypeInt, 53 Required: true, 54 }, 55 "action": &schema.Schema{ 56 Type: schema.TypeString, 57 Required: true, 58 }, 59 "protocol": &schema.Schema{ 60 Type: schema.TypeString, 61 Required: true, 62 }, 63 "cidr_block": &schema.Schema{ 64 Type: schema.TypeString, 65 Optional: true, 66 }, 67 }, 68 }, 69 Set: resourceAwsNetworkAclEntryHash, 70 }, 71 "egress": &schema.Schema{ 72 Type: schema.TypeSet, 73 Required: false, 74 Optional: true, 75 Elem: &schema.Resource{ 76 Schema: map[string]*schema.Schema{ 77 "from_port": &schema.Schema{ 78 Type: schema.TypeInt, 79 Required: true, 80 }, 81 "to_port": &schema.Schema{ 82 Type: schema.TypeInt, 83 Required: true, 84 }, 85 "rule_no": &schema.Schema{ 86 Type: schema.TypeInt, 87 Required: true, 88 }, 89 "action": &schema.Schema{ 90 Type: schema.TypeString, 91 Required: true, 92 }, 93 "protocol": &schema.Schema{ 94 Type: schema.TypeString, 95 Required: true, 96 }, 97 "cidr_block": &schema.Schema{ 98 Type: schema.TypeString, 99 Optional: true, 100 }, 101 }, 102 }, 103 Set: resourceAwsNetworkAclEntryHash, 104 }, 105 "tags": tagsSchema(), 106 }, 107 } 108 } 109 110 func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error { 111 112 ec2conn := meta.(*AWSClient).ec2conn 113 114 // Create the Network Acl 115 createOpts := &ec2.CreateNetworkACLRequest{ 116 VPCID: aws.String(d.Get("vpc_id").(string)), 117 } 118 119 log.Printf("[DEBUG] Network Acl create config: %#v", createOpts) 120 resp, err := ec2conn.CreateNetworkACL(createOpts) 121 if err != nil { 122 return fmt.Errorf("Error creating network acl: %s", err) 123 } 124 125 // Get the ID and store it 126 networkAcl := resp.NetworkACL 127 d.SetId(*networkAcl.NetworkACLID) 128 log.Printf("[INFO] Network Acl ID: %s", *networkAcl.NetworkACLID) 129 130 // Update rules and subnet association once acl is created 131 return resourceAwsNetworkAclUpdate(d, meta) 132 } 133 134 func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error { 135 ec2conn := meta.(*AWSClient).ec2conn 136 137 resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{ 138 NetworkACLIDs: []string{d.Id()}, 139 }) 140 141 if err != nil { 142 return err 143 } 144 if resp == nil { 145 return nil 146 } 147 148 networkAcl := &resp.NetworkACLs[0] 149 var ingressEntries []ec2.NetworkACLEntry 150 var egressEntries []ec2.NetworkACLEntry 151 152 // separate the ingress and egress rules 153 for _, e := range networkAcl.Entries { 154 if *e.Egress == true { 155 egressEntries = append(egressEntries, e) 156 } else { 157 ingressEntries = append(ingressEntries, e) 158 } 159 } 160 161 d.Set("vpc_id", networkAcl.VPCID) 162 d.Set("ingress", ingressEntries) 163 d.Set("egress", egressEntries) 164 d.Set("tags", tagsToMap(networkAcl.Tags)) 165 166 return nil 167 } 168 169 func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error { 170 ec2conn := meta.(*AWSClient).ec2conn 171 d.Partial(true) 172 173 if d.HasChange("ingress") { 174 err := updateNetworkAclEntries(d, "ingress", ec2conn) 175 if err != nil { 176 return err 177 } 178 } 179 180 if d.HasChange("egress") { 181 err := updateNetworkAclEntries(d, "egress", ec2conn) 182 if err != nil { 183 return err 184 } 185 } 186 187 if d.HasChange("subnet_id") { 188 189 //associate new subnet with the acl. 190 _, n := d.GetChange("subnet_id") 191 newSubnet := n.(string) 192 association, err := findNetworkAclAssociation(newSubnet, ec2conn) 193 if err != nil { 194 return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err) 195 } 196 _, err = ec2conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationRequest{ 197 AssociationID: association.NetworkACLAssociationID, 198 NetworkACLID: aws.String(d.Id()), 199 }) 200 if err != nil { 201 return err 202 } 203 } 204 205 if err := setTags(ec2conn, d); err != nil { 206 return err 207 } else { 208 d.SetPartial("tags") 209 } 210 211 d.Partial(false) 212 return resourceAwsNetworkAclRead(d, meta) 213 } 214 215 func updateNetworkAclEntries(d *schema.ResourceData, entryType string, ec2conn *ec2.EC2) error { 216 217 o, n := d.GetChange(entryType) 218 219 if o == nil { 220 o = new(schema.Set) 221 } 222 if n == nil { 223 n = new(schema.Set) 224 } 225 226 os := o.(*schema.Set) 227 ns := n.(*schema.Set) 228 229 toBeDeleted, err := expandNetworkAclEntries(os.Difference(ns).List(), entryType) 230 if err != nil { 231 return err 232 } 233 for _, remove := range toBeDeleted { 234 // Delete old Acl 235 err := ec2conn.DeleteNetworkACLEntry(&ec2.DeleteNetworkACLEntryRequest{ 236 NetworkACLID: aws.String(d.Id()), 237 RuleNumber: remove.RuleNumber, 238 Egress: remove.Egress, 239 }) 240 if err != nil { 241 return fmt.Errorf("Error deleting %s entry: %s", entryType, err) 242 } 243 } 244 245 toBeCreated, err := expandNetworkAclEntries(ns.Difference(os).List(), entryType) 246 if err != nil { 247 return err 248 } 249 for _, add := range toBeCreated { 250 // Add new Acl entry 251 err := ec2conn.CreateNetworkACLEntry(&ec2.CreateNetworkACLEntryRequest{ 252 NetworkACLID: aws.String(d.Id()), 253 CIDRBlock: add.CIDRBlock, 254 Egress: add.Egress, 255 PortRange: add.PortRange, 256 Protocol: add.Protocol, 257 RuleAction: add.RuleAction, 258 RuleNumber: add.RuleNumber, 259 }) 260 if err != nil { 261 return fmt.Errorf("Error creating %s entry: %s", entryType, err) 262 } 263 } 264 return nil 265 } 266 267 func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error { 268 ec2conn := meta.(*AWSClient).ec2conn 269 270 log.Printf("[INFO] Deleting Network Acl: %s", d.Id()) 271 return resource.Retry(5*time.Minute, func() error { 272 err := ec2conn.DeleteNetworkACL(&ec2.DeleteNetworkACLRequest{ 273 NetworkACLID: aws.String(d.Id()), 274 }) 275 if err != nil { 276 ec2err := err.(aws.APIError) 277 switch ec2err.Code { 278 case "InvalidNetworkAclID.NotFound": 279 return nil 280 case "DependencyViolation": 281 // In case of dependency violation, we remove the association between subnet and network acl. 282 // This means the subnet is attached to default acl of vpc. 283 association, err := findNetworkAclAssociation(d.Get("subnet_id").(string), ec2conn) 284 if err != nil { 285 return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err) 286 } 287 defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), ec2conn) 288 if err != nil { 289 return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err) 290 } 291 _, err = ec2conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationRequest{ 292 AssociationID: association.NetworkACLAssociationID, 293 NetworkACLID: defaultAcl.NetworkACLID, 294 }) 295 return resource.RetryError{Err: err} 296 default: 297 // Any other error, we want to quit the retry loop immediately 298 return resource.RetryError{Err: err} 299 } 300 } 301 log.Printf("[Info] Deleted network ACL %s successfully", d.Id()) 302 return nil 303 }) 304 } 305 306 func resourceAwsNetworkAclEntryHash(v interface{}) int { 307 var buf bytes.Buffer 308 m := v.(map[string]interface{}) 309 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 310 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 311 buf.WriteString(fmt.Sprintf("%d-", m["rule_no"].(int))) 312 buf.WriteString(fmt.Sprintf("%s-", m["action"].(string))) 313 buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) 314 buf.WriteString(fmt.Sprintf("%s-", m["cidr_block"].(string))) 315 316 if v, ok := m["ssl_certificate_id"]; ok { 317 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 318 } 319 320 return hashcode.String(buf.String()) 321 } 322 323 func getDefaultNetworkAcl(vpc_id string, ec2conn *ec2.EC2) (defaultAcl *ec2.NetworkACL, err error) { 324 resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{ 325 NetworkACLIDs: []string{}, 326 Filters: []ec2.Filter{ 327 ec2.Filter{ 328 Name: aws.String("default"), 329 Values: []string{"true"}, 330 }, 331 ec2.Filter{ 332 Name: aws.String("vpc-id"), 333 Values: []string{vpc_id}, 334 }, 335 }, 336 }) 337 338 if err != nil { 339 return nil, err 340 } 341 return &resp.NetworkACLs[0], nil 342 } 343 344 func findNetworkAclAssociation(subnetId string, ec2conn *ec2.EC2) (networkAclAssociation *ec2.NetworkACLAssociation, err error) { 345 resp, err := ec2conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsRequest{ 346 NetworkACLIDs: []string{}, 347 Filters: []ec2.Filter{ 348 ec2.Filter{ 349 Name: aws.String("association.subnet-id"), 350 Values: []string{subnetId}, 351 }, 352 }, 353 }) 354 355 if err != nil { 356 return nil, err 357 } 358 for _, association := range resp.NetworkACLs[0].Associations { 359 if *association.SubnetID == subnetId { 360 return &association, nil 361 } 362 } 363 return nil, fmt.Errorf("could not find association for subnet %s ", subnetId) 364 }