github.com/bradfeehan/terraform@v0.7.0-rc3.0.20170529055808-34b45c5ad841/builtin/providers/aws/resource_aws_subnet.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/aws/awserr" 10 "github.com/aws/aws-sdk-go/service/ec2" 11 "github.com/hashicorp/terraform/helper/resource" 12 "github.com/hashicorp/terraform/helper/schema" 13 ) 14 15 func resourceAwsSubnet() *schema.Resource { 16 return &schema.Resource{ 17 Create: resourceAwsSubnetCreate, 18 Read: resourceAwsSubnetRead, 19 Update: resourceAwsSubnetUpdate, 20 Delete: resourceAwsSubnetDelete, 21 Importer: &schema.ResourceImporter{ 22 State: schema.ImportStatePassthrough, 23 }, 24 25 SchemaVersion: 1, 26 MigrateState: resourceAwsSubnetMigrateState, 27 28 Schema: map[string]*schema.Schema{ 29 "vpc_id": { 30 Type: schema.TypeString, 31 Required: true, 32 ForceNew: true, 33 }, 34 35 "cidr_block": { 36 Type: schema.TypeString, 37 Required: true, 38 ForceNew: true, 39 }, 40 41 "ipv6_cidr_block": { 42 Type: schema.TypeString, 43 Optional: true, 44 Computed: true, 45 }, 46 47 "availability_zone": { 48 Type: schema.TypeString, 49 Optional: true, 50 Computed: true, 51 ForceNew: true, 52 }, 53 54 "map_public_ip_on_launch": { 55 Type: schema.TypeBool, 56 Optional: true, 57 Default: false, 58 }, 59 60 "assign_ipv6_address_on_creation": { 61 Type: schema.TypeBool, 62 Optional: true, 63 Default: false, 64 }, 65 66 "ipv6_cidr_block_association_id": { 67 Type: schema.TypeString, 68 Computed: true, 69 }, 70 71 "tags": tagsSchema(), 72 }, 73 } 74 } 75 76 func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error { 77 conn := meta.(*AWSClient).ec2conn 78 79 createOpts := &ec2.CreateSubnetInput{ 80 AvailabilityZone: aws.String(d.Get("availability_zone").(string)), 81 CidrBlock: aws.String(d.Get("cidr_block").(string)), 82 VpcId: aws.String(d.Get("vpc_id").(string)), 83 } 84 85 if v, ok := d.GetOk("ipv6_cidr_block"); ok { 86 createOpts.Ipv6CidrBlock = aws.String(v.(string)) 87 } 88 89 var err error 90 resp, err := conn.CreateSubnet(createOpts) 91 92 if err != nil { 93 return fmt.Errorf("Error creating subnet: %s", err) 94 } 95 96 // Get the ID and store it 97 subnet := resp.Subnet 98 d.SetId(*subnet.SubnetId) 99 log.Printf("[INFO] Subnet ID: %s", *subnet.SubnetId) 100 101 // Wait for the Subnet to become available 102 log.Printf("[DEBUG] Waiting for subnet (%s) to become available", *subnet.SubnetId) 103 stateConf := &resource.StateChangeConf{ 104 Pending: []string{"pending"}, 105 Target: []string{"available"}, 106 Refresh: SubnetStateRefreshFunc(conn, *subnet.SubnetId), 107 Timeout: 10 * time.Minute, 108 } 109 110 _, err = stateConf.WaitForState() 111 112 if err != nil { 113 return fmt.Errorf( 114 "Error waiting for subnet (%s) to become ready: %s", 115 d.Id(), err) 116 } 117 118 return resourceAwsSubnetUpdate(d, meta) 119 } 120 121 func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { 122 conn := meta.(*AWSClient).ec2conn 123 124 resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsInput{ 125 SubnetIds: []*string{aws.String(d.Id())}, 126 }) 127 128 if err != nil { 129 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" { 130 // Update state to indicate the subnet no longer exists. 131 d.SetId("") 132 return nil 133 } 134 return err 135 } 136 if resp == nil { 137 return nil 138 } 139 140 subnet := resp.Subnets[0] 141 142 d.Set("vpc_id", subnet.VpcId) 143 d.Set("availability_zone", subnet.AvailabilityZone) 144 d.Set("cidr_block", subnet.CidrBlock) 145 d.Set("map_public_ip_on_launch", subnet.MapPublicIpOnLaunch) 146 d.Set("assign_ipv6_address_on_creation", subnet.AssignIpv6AddressOnCreation) 147 for _, a := range subnet.Ipv6CidrBlockAssociationSet { 148 if *a.Ipv6CidrBlockState.State == "associated" { //we can only ever have 1 IPv6 block associated at once 149 d.Set("ipv6_cidr_block_association_id", a.AssociationId) 150 d.Set("ipv6_cidr_block", a.Ipv6CidrBlock) 151 break 152 } else { 153 d.Set("ipv6_cidr_block_association_id", "") // we blank these out to remove old entries 154 d.Set("ipv6_cidr_block", "") 155 } 156 } 157 d.Set("tags", tagsToMap(subnet.Tags)) 158 159 return nil 160 } 161 162 func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error { 163 conn := meta.(*AWSClient).ec2conn 164 165 d.Partial(true) 166 167 if err := setTags(conn, d); err != nil { 168 return err 169 } else { 170 d.SetPartial("tags") 171 } 172 173 if d.HasChange("map_public_ip_on_launch") { 174 modifyOpts := &ec2.ModifySubnetAttributeInput{ 175 SubnetId: aws.String(d.Id()), 176 MapPublicIpOnLaunch: &ec2.AttributeBooleanValue{ 177 Value: aws.Bool(d.Get("map_public_ip_on_launch").(bool)), 178 }, 179 } 180 181 log.Printf("[DEBUG] Subnet modify attributes: %#v", modifyOpts) 182 183 _, err := conn.ModifySubnetAttribute(modifyOpts) 184 185 if err != nil { 186 return err 187 } else { 188 d.SetPartial("map_public_ip_on_launch") 189 } 190 } 191 192 // We have to be careful here to not go through a change of association if this is a new resource 193 // A New resource here would denote that the Update func is called by the Create func 194 if d.HasChange("ipv6_cidr_block") && !d.IsNewResource() { 195 // We need to handle that we disassociate the IPv6 CIDR block before we try and associate the new one 196 // This could be an issue as, we could error out when we try and add the new one 197 // We may need to roll back the state and reattach the old one if this is the case 198 199 _, new := d.GetChange("ipv6_cidr_block") 200 201 if v, ok := d.GetOk("ipv6_cidr_block_association_id"); ok { 202 203 //Firstly we have to disassociate the old IPv6 CIDR Block 204 disassociateOps := &ec2.DisassociateSubnetCidrBlockInput{ 205 AssociationId: aws.String(v.(string)), 206 } 207 208 _, err := conn.DisassociateSubnetCidrBlock(disassociateOps) 209 if err != nil { 210 return err 211 } 212 213 // Wait for the CIDR to become disassociated 214 log.Printf( 215 "[DEBUG] Waiting for IPv6 CIDR (%s) to become disassociated", 216 d.Id()) 217 stateConf := &resource.StateChangeConf{ 218 Pending: []string{"disassociating", "associated"}, 219 Target: []string{"disassociated"}, 220 Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), d.Get("ipv6_cidr_block_association_id").(string)), 221 Timeout: 3 * time.Minute, 222 } 223 if _, err := stateConf.WaitForState(); err != nil { 224 return fmt.Errorf( 225 "Error waiting for IPv6 CIDR (%s) to become disassociated: %s", 226 d.Id(), err) 227 } 228 } 229 230 //Now we need to try and associate the new CIDR block 231 associatesOpts := &ec2.AssociateSubnetCidrBlockInput{ 232 SubnetId: aws.String(d.Id()), 233 Ipv6CidrBlock: aws.String(new.(string)), 234 } 235 236 resp, err := conn.AssociateSubnetCidrBlock(associatesOpts) 237 if err != nil { 238 //The big question here is, do we want to try and reassociate the old one?? 239 //If we have a failure here, then we may be in a situation that we have nothing associated 240 return err 241 } 242 243 // Wait for the CIDR to become associated 244 log.Printf( 245 "[DEBUG] Waiting for IPv6 CIDR (%s) to become associated", 246 d.Id()) 247 stateConf := &resource.StateChangeConf{ 248 Pending: []string{"associating", "disassociated"}, 249 Target: []string{"associated"}, 250 Refresh: SubnetIpv6CidrStateRefreshFunc(conn, d.Id(), *resp.Ipv6CidrBlockAssociation.AssociationId), 251 Timeout: 3 * time.Minute, 252 } 253 if _, err := stateConf.WaitForState(); err != nil { 254 return fmt.Errorf( 255 "Error waiting for IPv6 CIDR (%s) to become associated: %s", 256 d.Id(), err) 257 } 258 259 d.SetPartial("ipv6_cidr_block") 260 } 261 262 if d.HasChange("assign_ipv6_address_on_creation") { 263 modifyOpts := &ec2.ModifySubnetAttributeInput{ 264 SubnetId: aws.String(d.Id()), 265 AssignIpv6AddressOnCreation: &ec2.AttributeBooleanValue{ 266 Value: aws.Bool(d.Get("assign_ipv6_address_on_creation").(bool)), 267 }, 268 } 269 270 log.Printf("[DEBUG] Subnet modify attributes: %#v", modifyOpts) 271 272 _, err := conn.ModifySubnetAttribute(modifyOpts) 273 274 if err != nil { 275 return err 276 } else { 277 d.SetPartial("assign_ipv6_address_on_creation") 278 } 279 } 280 281 d.Partial(false) 282 283 return resourceAwsSubnetRead(d, meta) 284 } 285 286 func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error { 287 conn := meta.(*AWSClient).ec2conn 288 289 log.Printf("[INFO] Deleting subnet: %s", d.Id()) 290 req := &ec2.DeleteSubnetInput{ 291 SubnetId: aws.String(d.Id()), 292 } 293 294 wait := resource.StateChangeConf{ 295 Pending: []string{"pending"}, 296 Target: []string{"destroyed"}, 297 Timeout: 10 * time.Minute, 298 MinTimeout: 1 * time.Second, 299 Refresh: func() (interface{}, string, error) { 300 _, err := conn.DeleteSubnet(req) 301 if err != nil { 302 if apiErr, ok := err.(awserr.Error); ok { 303 if apiErr.Code() == "DependencyViolation" { 304 // There is some pending operation, so just retry 305 // in a bit. 306 return 42, "pending", nil 307 } 308 309 if apiErr.Code() == "InvalidSubnetID.NotFound" { 310 return 42, "destroyed", nil 311 } 312 } 313 314 return 42, "failure", err 315 } 316 317 return 42, "destroyed", nil 318 }, 319 } 320 321 if _, err := wait.WaitForState(); err != nil { 322 return fmt.Errorf("Error deleting subnet: %s", err) 323 } 324 325 return nil 326 } 327 328 // SubnetStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a Subnet. 329 func SubnetStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 330 return func() (interface{}, string, error) { 331 resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsInput{ 332 SubnetIds: []*string{aws.String(id)}, 333 }) 334 if err != nil { 335 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" { 336 resp = nil 337 } else { 338 log.Printf("Error on SubnetStateRefresh: %s", err) 339 return nil, "", err 340 } 341 } 342 343 if resp == nil { 344 // Sometimes AWS just has consistency issues and doesn't see 345 // our instance yet. Return an empty state. 346 return nil, "", nil 347 } 348 349 subnet := resp.Subnets[0] 350 return subnet, *subnet.State, nil 351 } 352 } 353 354 func SubnetIpv6CidrStateRefreshFunc(conn *ec2.EC2, id string, associationId string) resource.StateRefreshFunc { 355 return func() (interface{}, string, error) { 356 opts := &ec2.DescribeSubnetsInput{ 357 SubnetIds: []*string{aws.String(id)}, 358 } 359 resp, err := conn.DescribeSubnets(opts) 360 if err != nil { 361 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSubnetID.NotFound" { 362 resp = nil 363 } else { 364 log.Printf("Error on SubnetIpv6CidrStateRefreshFunc: %s", err) 365 return nil, "", err 366 } 367 } 368 369 if resp == nil { 370 // Sometimes AWS just has consistency issues and doesn't see 371 // our instance yet. Return an empty state. 372 return nil, "", nil 373 } 374 375 if resp.Subnets[0].Ipv6CidrBlockAssociationSet == nil { 376 return nil, "", nil 377 } 378 379 for _, association := range resp.Subnets[0].Ipv6CidrBlockAssociationSet { 380 if *association.AssociationId == associationId { 381 return association, *association.Ipv6CidrBlockState.State, nil 382 } 383 } 384 385 return nil, "", nil 386 } 387 }