github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/builtin/providers/aws/resource_aws_vpc_peering_connection.go (about) 1 package aws 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "time" 8 9 "github.com/aws/aws-sdk-go/aws" 10 "github.com/aws/aws-sdk-go/aws/awserr" 11 "github.com/aws/aws-sdk-go/service/ec2" 12 "github.com/hashicorp/errwrap" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsVpcPeeringConnection() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsVPCPeeringCreate, 20 Read: resourceAwsVPCPeeringRead, 21 Update: resourceAwsVPCPeeringUpdate, 22 Delete: resourceAwsVPCPeeringDelete, 23 Importer: &schema.ResourceImporter{ 24 State: schema.ImportStatePassthrough, 25 }, 26 27 Schema: map[string]*schema.Schema{ 28 "peer_owner_id": { 29 Type: schema.TypeString, 30 Optional: true, 31 ForceNew: true, 32 Computed: true, 33 }, 34 "peer_vpc_id": { 35 Type: schema.TypeString, 36 Required: true, 37 ForceNew: true, 38 }, 39 "vpc_id": { 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: true, 43 }, 44 "auto_accept": { 45 Type: schema.TypeBool, 46 Optional: true, 47 }, 48 "accept_status": { 49 Type: schema.TypeString, 50 Computed: true, 51 }, 52 "accepter": vpcPeeringConnectionOptionsSchema(), 53 "requester": vpcPeeringConnectionOptionsSchema(), 54 "tags": tagsSchema(), 55 }, 56 } 57 } 58 59 func resourceAwsVPCPeeringCreate(d *schema.ResourceData, meta interface{}) error { 60 conn := meta.(*AWSClient).ec2conn 61 62 // Create the vpc peering connection 63 createOpts := &ec2.CreateVpcPeeringConnectionInput{ 64 PeerVpcId: aws.String(d.Get("peer_vpc_id").(string)), 65 VpcId: aws.String(d.Get("vpc_id").(string)), 66 } 67 68 if v, ok := d.GetOk("peer_owner_id"); ok { 69 createOpts.PeerOwnerId = aws.String(v.(string)) 70 } 71 72 log.Printf("[DEBUG] VPC Peering Create options: %#v", createOpts) 73 74 resp, err := conn.CreateVpcPeeringConnection(createOpts) 75 if err != nil { 76 return errwrap.Wrapf("Error creating VPC Peering Connection: {{err}}", err) 77 } 78 79 // Get the ID and store it 80 rt := resp.VpcPeeringConnection 81 d.SetId(*rt.VpcPeeringConnectionId) 82 log.Printf("[INFO] VPC Peering Connection ID: %s", d.Id()) 83 84 // Wait for the vpc peering connection to become available 85 log.Printf("[DEBUG] Waiting for VPC Peering Connection (%s) to become available.", d.Id()) 86 stateConf := &resource.StateChangeConf{ 87 Pending: []string{"pending"}, 88 Target: []string{"pending-acceptance"}, 89 Refresh: resourceAwsVPCPeeringConnectionStateRefreshFunc(conn, d.Id()), 90 Timeout: 1 * time.Minute, 91 } 92 if _, err := stateConf.WaitForState(); err != nil { 93 return errwrap.Wrapf(fmt.Sprintf( 94 "Error waiting for VPC Peering Connection (%s) to become available: {{err}}", 95 d.Id()), err) 96 } 97 98 return resourceAwsVPCPeeringUpdate(d, meta) 99 } 100 101 func resourceAwsVPCPeeringRead(d *schema.ResourceData, meta interface{}) error { 102 conn := meta.(*AWSClient).ec2conn 103 pcRaw, status, err := resourceAwsVPCPeeringConnectionStateRefreshFunc(conn, d.Id())() 104 // Allow a failed VPC Peering Connection to fallthrough, 105 // to allow rest of the logic below to do its work. 106 if err != nil && status != "failed" { 107 return err 108 } 109 110 if pcRaw == nil { 111 d.SetId("") 112 return nil 113 } 114 115 pc := pcRaw.(*ec2.VpcPeeringConnection) 116 117 // The failed status is a status that we can assume just means the 118 // connection is gone. Destruction isn't allowed, and it eventually 119 // just "falls off" the console. See GH-2322 120 if pc.Status != nil { 121 status := map[string]bool{ 122 "deleted": true, 123 "deleting": true, 124 "expired": true, 125 "failed": true, 126 "rejected": true, 127 } 128 if _, ok := status[*pc.Status.Code]; ok { 129 log.Printf("[DEBUG] VPC Peering Connection (%s) in state (%s), removing.", 130 d.Id(), *pc.Status.Code) 131 d.SetId("") 132 return nil 133 } 134 } 135 log.Printf("[DEBUG] VPC Peering Connection response: %#v", pc) 136 137 d.Set("accept_status", pc.Status.Code) 138 d.Set("peer_owner_id", pc.AccepterVpcInfo.OwnerId) 139 d.Set("peer_vpc_id", pc.AccepterVpcInfo.VpcId) 140 d.Set("vpc_id", pc.RequesterVpcInfo.VpcId) 141 142 // When the VPC Peering Connection is pending acceptance, 143 // the details about accepter and/or requester peering 144 // options would not be included in the response. 145 if pc.AccepterVpcInfo.PeeringOptions != nil { 146 err := d.Set("accepter", flattenPeeringOptions(pc.AccepterVpcInfo.PeeringOptions)) 147 if err != nil { 148 return errwrap.Wrapf("Error setting VPC Peering Connection accepter information: {{err}}", err) 149 } 150 } 151 152 if pc.RequesterVpcInfo.PeeringOptions != nil { 153 err := d.Set("requester", flattenPeeringOptions(pc.RequesterVpcInfo.PeeringOptions)) 154 if err != nil { 155 return errwrap.Wrapf("Error setting VPC Peering Connection requester information: {{err}}", err) 156 } 157 } 158 159 err = d.Set("tags", tagsToMap(pc.Tags)) 160 if err != nil { 161 return errwrap.Wrapf("Error setting VPC Peering Connection tags: {{err}}", err) 162 } 163 164 return nil 165 } 166 167 func resourceVPCPeeringConnectionAccept(conn *ec2.EC2, id string) (string, error) { 168 log.Printf("[INFO] Accept VPC Peering Connection with ID: %s", id) 169 170 req := &ec2.AcceptVpcPeeringConnectionInput{ 171 VpcPeeringConnectionId: aws.String(id), 172 } 173 174 resp, err := conn.AcceptVpcPeeringConnection(req) 175 if err != nil { 176 return "", err 177 } 178 pc := resp.VpcPeeringConnection 179 180 return *pc.Status.Code, nil 181 } 182 183 func resourceVPCPeeringConnectionOptionsModify(d *schema.ResourceData, meta interface{}) error { 184 conn := meta.(*AWSClient).ec2conn 185 186 modifyOpts := &ec2.ModifyVpcPeeringConnectionOptionsInput{ 187 VpcPeeringConnectionId: aws.String(d.Id()), 188 } 189 190 if v, ok := d.GetOk("accepter"); ok { 191 if s := v.(*schema.Set); len(s.List()) > 0 { 192 co := s.List()[0].(map[string]interface{}) 193 modifyOpts.AccepterPeeringConnectionOptions = expandPeeringOptions(co) 194 } 195 } 196 197 if v, ok := d.GetOk("requester"); ok { 198 if s := v.(*schema.Set); len(s.List()) > 0 { 199 co := s.List()[0].(map[string]interface{}) 200 modifyOpts.RequesterPeeringConnectionOptions = expandPeeringOptions(co) 201 } 202 } 203 204 log.Printf("[DEBUG] VPC Peering Connection modify options: %#v", modifyOpts) 205 if _, err := conn.ModifyVpcPeeringConnectionOptions(modifyOpts); err != nil { 206 return err 207 } 208 209 return nil 210 } 211 212 func resourceAwsVPCPeeringUpdate(d *schema.ResourceData, meta interface{}) error { 213 conn := meta.(*AWSClient).ec2conn 214 215 if err := setTags(conn, d); err != nil { 216 return err 217 } else { 218 d.SetPartial("tags") 219 } 220 221 pcRaw, _, err := resourceAwsVPCPeeringConnectionStateRefreshFunc(conn, d.Id())() 222 if err != nil { 223 return err 224 } 225 226 if pcRaw == nil { 227 d.SetId("") 228 return nil 229 } 230 pc := pcRaw.(*ec2.VpcPeeringConnection) 231 232 if _, ok := d.GetOk("auto_accept"); ok { 233 if pc.Status != nil && *pc.Status.Code == "pending-acceptance" { 234 status, err := resourceVPCPeeringConnectionAccept(conn, d.Id()) 235 if err != nil { 236 return errwrap.Wrapf("Unable to accept VPC Peering Connection: {{err}}", err) 237 } 238 log.Printf("[DEBUG] VPC Peering Connection accept status: %s", status) 239 } 240 } 241 242 if d.HasChange("accepter") || d.HasChange("requester") { 243 _, ok := d.GetOk("auto_accept") 244 if !ok && pc.Status != nil && *pc.Status.Code != "active" { 245 return fmt.Errorf("Unable to modify peering options. The VPC Peering Connection "+ 246 "%q is not active. Please set `auto_accept` attribute to `true`, "+ 247 "or activate VPC Peering Connection manually.", d.Id()) 248 } 249 250 if err := resourceVPCPeeringConnectionOptionsModify(d, meta); err != nil { 251 return errwrap.Wrapf("Error modifying VPC Peering Connection options: {{err}}", err) 252 } 253 } 254 255 return resourceAwsVPCPeeringRead(d, meta) 256 } 257 258 func resourceAwsVPCPeeringDelete(d *schema.ResourceData, meta interface{}) error { 259 conn := meta.(*AWSClient).ec2conn 260 261 _, err := conn.DeleteVpcPeeringConnection( 262 &ec2.DeleteVpcPeeringConnectionInput{ 263 VpcPeeringConnectionId: aws.String(d.Id()), 264 }) 265 266 return err 267 } 268 269 // resourceAwsVPCPeeringConnectionStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 270 // a VPCPeeringConnection. 271 func resourceAwsVPCPeeringConnectionStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 272 return func() (interface{}, string, error) { 273 resp, err := conn.DescribeVpcPeeringConnections(&ec2.DescribeVpcPeeringConnectionsInput{ 274 VpcPeeringConnectionIds: []*string{aws.String(id)}, 275 }) 276 if err != nil { 277 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcPeeringConnectionID.NotFound" { 278 resp = nil 279 } else { 280 log.Printf("Error reading VPC Peering Connection details: %s", err) 281 return nil, "error", err 282 } 283 } 284 285 if resp == nil { 286 // Sometimes AWS just has consistency issues and doesn't see 287 // our instance yet. Return an empty state. 288 return nil, "", nil 289 } 290 291 pc := resp.VpcPeeringConnections[0] 292 293 // A VPC Peering Connection can exist in a failed state due to 294 // incorrect VPC ID, account ID, or overlapping IP address range, 295 // thus we short circuit before the time out would occur. 296 if pc != nil && *pc.Status.Code == "failed" { 297 return nil, "failed", errors.New(*pc.Status.Message) 298 } 299 300 return pc, *pc.Status.Code, nil 301 } 302 } 303 304 func vpcPeeringConnectionOptionsSchema() *schema.Schema { 305 return &schema.Schema{ 306 Type: schema.TypeSet, 307 Optional: true, 308 Computed: true, 309 MaxItems: 1, 310 Elem: &schema.Resource{ 311 Schema: map[string]*schema.Schema{ 312 "allow_remote_vpc_dns_resolution": { 313 Type: schema.TypeBool, 314 Optional: true, 315 Default: false, 316 }, 317 "allow_classic_link_to_remote_vpc": { 318 Type: schema.TypeBool, 319 Optional: true, 320 Default: false, 321 }, 322 "allow_vpc_to_remote_classic_link": { 323 Type: schema.TypeBool, 324 Optional: true, 325 Default: false, 326 }, 327 }, 328 }, 329 } 330 } 331 332 func flattenPeeringOptions(options *ec2.VpcPeeringConnectionOptionsDescription) (results []map[string]interface{}) { 333 m := make(map[string]interface{}) 334 335 if options.AllowDnsResolutionFromRemoteVpc != nil { 336 m["allow_remote_vpc_dns_resolution"] = *options.AllowDnsResolutionFromRemoteVpc 337 } 338 339 if options.AllowEgressFromLocalClassicLinkToRemoteVpc != nil { 340 m["allow_classic_link_to_remote_vpc"] = *options.AllowEgressFromLocalClassicLinkToRemoteVpc 341 } 342 343 if options.AllowEgressFromLocalVpcToRemoteClassicLink != nil { 344 m["allow_vpc_to_remote_classic_link"] = *options.AllowEgressFromLocalVpcToRemoteClassicLink 345 } 346 347 results = append(results, m) 348 return 349 } 350 351 func expandPeeringOptions(m map[string]interface{}) *ec2.PeeringConnectionOptionsRequest { 352 r := &ec2.PeeringConnectionOptionsRequest{} 353 354 if v, ok := m["allow_remote_vpc_dns_resolution"]; ok { 355 r.AllowDnsResolutionFromRemoteVpc = aws.Bool(v.(bool)) 356 } 357 358 if v, ok := m["allow_classic_link_to_remote_vpc"]; ok { 359 r.AllowEgressFromLocalClassicLinkToRemoteVpc = aws.Bool(v.(bool)) 360 } 361 362 if v, ok := m["allow_vpc_to_remote_classic_link"]; ok { 363 r.AllowEgressFromLocalVpcToRemoteClassicLink = aws.Bool(v.(bool)) 364 } 365 366 return r 367 }