github.com/i0n/terraform@v0.4.3-0.20150506151324-010a39a58ec1/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/awslabs/aws-sdk-go/aws" 10 "github.com/awslabs/aws-sdk-go/service/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 conn := meta.(*AWSClient).ec2conn 113 114 // Create the Network Acl 115 createOpts := &ec2.CreateNetworkACLInput{ 116 VPCID: aws.String(d.Get("vpc_id").(string)), 117 } 118 119 log.Printf("[DEBUG] Network Acl create config: %#v", createOpts) 120 resp, err := conn.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 conn := meta.(*AWSClient).ec2conn 136 137 resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{ 138 NetworkACLIDs: []*string{aws.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", tagsToMapSDK(networkAcl.Tags)) 165 166 return nil 167 } 168 169 func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error { 170 conn := meta.(*AWSClient).ec2conn 171 d.Partial(true) 172 173 if d.HasChange("ingress") { 174 err := updateNetworkAclEntries(d, "ingress", conn) 175 if err != nil { 176 return err 177 } 178 } 179 180 if d.HasChange("egress") { 181 err := updateNetworkAclEntries(d, "egress", conn) 182 if err != nil { 183 return err 184 } 185 } 186 187 if d.HasChange("subnet_id") { 188 //associate new subnet with the acl. 189 _, n := d.GetChange("subnet_id") 190 newSubnet := n.(string) 191 association, err := findNetworkAclAssociation(newSubnet, conn) 192 if err != nil { 193 return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err) 194 } 195 _, err = conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationInput{ 196 AssociationID: association.NetworkACLAssociationID, 197 NetworkACLID: aws.String(d.Id()), 198 }) 199 if err != nil { 200 return err 201 } 202 } 203 204 if err := setTagsSDK(conn, d); err != nil { 205 return err 206 } else { 207 d.SetPartial("tags") 208 } 209 210 d.Partial(false) 211 return resourceAwsNetworkAclRead(d, meta) 212 } 213 214 func updateNetworkAclEntries(d *schema.ResourceData, entryType string, conn *ec2.EC2) error { 215 216 o, n := d.GetChange(entryType) 217 218 if o == nil { 219 o = new(schema.Set) 220 } 221 if n == nil { 222 n = new(schema.Set) 223 } 224 225 os := o.(*schema.Set) 226 ns := n.(*schema.Set) 227 228 toBeDeleted, err := expandNetworkAclEntries(os.Difference(ns).List(), entryType) 229 if err != nil { 230 return err 231 } 232 for _, remove := range toBeDeleted { 233 // Delete old Acl 234 _, err := conn.DeleteNetworkACLEntry(&ec2.DeleteNetworkACLEntryInput{ 235 NetworkACLID: aws.String(d.Id()), 236 RuleNumber: remove.RuleNumber, 237 Egress: remove.Egress, 238 }) 239 if err != nil { 240 return fmt.Errorf("Error deleting %s entry: %s", entryType, err) 241 } 242 } 243 244 toBeCreated, err := expandNetworkAclEntries(ns.Difference(os).List(), entryType) 245 if err != nil { 246 return err 247 } 248 for _, add := range toBeCreated { 249 // Add new Acl entry 250 _, err := conn.CreateNetworkACLEntry(&ec2.CreateNetworkACLEntryInput{ 251 NetworkACLID: aws.String(d.Id()), 252 CIDRBlock: add.CIDRBlock, 253 Egress: add.Egress, 254 PortRange: add.PortRange, 255 Protocol: add.Protocol, 256 RuleAction: add.RuleAction, 257 RuleNumber: add.RuleNumber, 258 }) 259 if err != nil { 260 return fmt.Errorf("Error creating %s entry: %s", entryType, err) 261 } 262 } 263 return nil 264 } 265 266 func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error { 267 conn := meta.(*AWSClient).ec2conn 268 269 log.Printf("[INFO] Deleting Network Acl: %s", d.Id()) 270 return resource.Retry(5*time.Minute, func() error { 271 _, err := conn.DeleteNetworkACL(&ec2.DeleteNetworkACLInput{ 272 NetworkACLID: aws.String(d.Id()), 273 }) 274 if err != nil { 275 ec2err := err.(aws.APIError) 276 switch ec2err.Code { 277 case "InvalidNetworkAclID.NotFound": 278 return nil 279 case "DependencyViolation": 280 // In case of dependency violation, we remove the association between subnet and network acl. 281 // This means the subnet is attached to default acl of vpc. 282 association, err := findNetworkAclAssociation(d.Get("subnet_id").(string), conn) 283 if err != nil { 284 return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err) 285 } 286 defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), conn) 287 if err != nil { 288 return fmt.Errorf("Dependency violation: Cannot delete acl %s: %s", d.Id(), err) 289 } 290 _, err = conn.ReplaceNetworkACLAssociation(&ec2.ReplaceNetworkACLAssociationInput{ 291 AssociationID: association.NetworkACLAssociationID, 292 NetworkACLID: defaultAcl.NetworkACLID, 293 }) 294 return resource.RetryError{Err: err} 295 default: 296 // Any other error, we want to quit the retry loop immediately 297 return resource.RetryError{Err: err} 298 } 299 } 300 log.Printf("[Info] Deleted network ACL %s successfully", d.Id()) 301 return nil 302 }) 303 } 304 305 func resourceAwsNetworkAclEntryHash(v interface{}) int { 306 var buf bytes.Buffer 307 m := v.(map[string]interface{}) 308 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 309 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 310 buf.WriteString(fmt.Sprintf("%d-", m["rule_no"].(int))) 311 buf.WriteString(fmt.Sprintf("%s-", m["action"].(string))) 312 buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) 313 buf.WriteString(fmt.Sprintf("%s-", m["cidr_block"].(string))) 314 315 if v, ok := m["ssl_certificate_id"]; ok { 316 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 317 } 318 319 return hashcode.String(buf.String()) 320 } 321 322 func getDefaultNetworkAcl(vpc_id string, conn *ec2.EC2) (defaultAcl *ec2.NetworkACL, err error) { 323 resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{ 324 NetworkACLIDs: []*string{}, 325 Filters: []*ec2.Filter{ 326 &ec2.Filter{ 327 Name: aws.String("default"), 328 Values: []*string{aws.String("true")}, 329 }, 330 &ec2.Filter{ 331 Name: aws.String("vpc-id"), 332 Values: []*string{aws.String(vpc_id)}, 333 }, 334 }, 335 }) 336 337 if err != nil { 338 return nil, err 339 } 340 return resp.NetworkACLs[0], nil 341 } 342 343 func findNetworkAclAssociation(subnetId string, conn *ec2.EC2) (networkAclAssociation *ec2.NetworkACLAssociation, err error) { 344 resp, err := conn.DescribeNetworkACLs(&ec2.DescribeNetworkACLsInput{ 345 Filters: []*ec2.Filter{ 346 &ec2.Filter{ 347 Name: aws.String("association.subnet-id"), 348 Values: []*string{aws.String(subnetId)}, 349 }, 350 }, 351 }) 352 353 if err != nil { 354 return nil, err 355 } 356 if resp.NetworkACLs != nil && len(resp.NetworkACLs) > 0 { 357 for _, association := range resp.NetworkACLs[0].Associations { 358 if *association.SubnetID == subnetId { 359 return association, nil 360 } 361 } 362 } 363 return nil, fmt.Errorf("could not find association for subnet %s ", subnetId) 364 }