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