github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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{"initiating-request", "provisioning", "pending"},
    88  		Target:  []string{"pending-acceptance", "active"},
    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  	client := meta.(*AWSClient)
   103  	conn := client.ec2conn
   104  
   105  	pcRaw, status, err := resourceAwsVPCPeeringConnectionStateRefreshFunc(conn, d.Id())()
   106  	// Allow a failed VPC Peering Connection to fallthrough,
   107  	// to allow rest of the logic below to do its work.
   108  	if err != nil && status != "failed" {
   109  		return err
   110  	}
   111  
   112  	if pcRaw == nil {
   113  		d.SetId("")
   114  		return nil
   115  	}
   116  
   117  	pc := pcRaw.(*ec2.VpcPeeringConnection)
   118  
   119  	// The failed status is a status that we can assume just means the
   120  	// connection is gone. Destruction isn't allowed, and it eventually
   121  	// just "falls off" the console. See GH-2322
   122  	if pc.Status != nil {
   123  		status := map[string]bool{
   124  			"deleted":  true,
   125  			"deleting": true,
   126  			"expired":  true,
   127  			"failed":   true,
   128  			"rejected": true,
   129  		}
   130  		if _, ok := status[*pc.Status.Code]; ok {
   131  			log.Printf("[DEBUG] VPC Peering Connection (%s) in state (%s), removing.",
   132  				d.Id(), *pc.Status.Code)
   133  			d.SetId("")
   134  			return nil
   135  		}
   136  	}
   137  	log.Printf("[DEBUG] VPC Peering Connection response: %#v", pc)
   138  
   139  	log.Printf("[DEBUG] Account ID %s, VPC PeerConn Requester %s, Accepter %s",
   140  		client.accountid, *pc.RequesterVpcInfo.OwnerId, *pc.AccepterVpcInfo.OwnerId)
   141  
   142  	if (client.accountid == *pc.AccepterVpcInfo.OwnerId) && (client.accountid != *pc.RequesterVpcInfo.OwnerId) {
   143  		// We're the accepter
   144  		d.Set("peer_owner_id", pc.RequesterVpcInfo.OwnerId)
   145  		d.Set("peer_vpc_id", pc.RequesterVpcInfo.VpcId)
   146  		d.Set("vpc_id", pc.AccepterVpcInfo.VpcId)
   147  	} else {
   148  		// We're the requester
   149  		d.Set("peer_owner_id", pc.AccepterVpcInfo.OwnerId)
   150  		d.Set("peer_vpc_id", pc.AccepterVpcInfo.VpcId)
   151  		d.Set("vpc_id", pc.RequesterVpcInfo.VpcId)
   152  	}
   153  
   154  	d.Set("accept_status", pc.Status.Code)
   155  
   156  	// When the VPC Peering Connection is pending acceptance,
   157  	// the details about accepter and/or requester peering
   158  	// options would not be included in the response.
   159  	if pc.AccepterVpcInfo.PeeringOptions != nil {
   160  		err := d.Set("accepter", flattenPeeringOptions(pc.AccepterVpcInfo.PeeringOptions))
   161  		if err != nil {
   162  			return errwrap.Wrapf("Error setting VPC Peering Connection accepter information: {{err}}", err)
   163  		}
   164  	}
   165  
   166  	if pc.RequesterVpcInfo.PeeringOptions != nil {
   167  		err := d.Set("requester", flattenPeeringOptions(pc.RequesterVpcInfo.PeeringOptions))
   168  		if err != nil {
   169  			return errwrap.Wrapf("Error setting VPC Peering Connection requester information: {{err}}", err)
   170  		}
   171  	}
   172  
   173  	err = d.Set("tags", tagsToMap(pc.Tags))
   174  	if err != nil {
   175  		return errwrap.Wrapf("Error setting VPC Peering Connection tags: {{err}}", err)
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func resourceVPCPeeringConnectionAccept(conn *ec2.EC2, id string) (string, error) {
   182  	log.Printf("[INFO] Accept VPC Peering Connection with ID: %s", id)
   183  
   184  	req := &ec2.AcceptVpcPeeringConnectionInput{
   185  		VpcPeeringConnectionId: aws.String(id),
   186  	}
   187  
   188  	resp, err := conn.AcceptVpcPeeringConnection(req)
   189  	if err != nil {
   190  		return "", err
   191  	}
   192  	pc := resp.VpcPeeringConnection
   193  
   194  	return *pc.Status.Code, nil
   195  }
   196  
   197  func resourceVPCPeeringConnectionOptionsModify(d *schema.ResourceData, meta interface{}) error {
   198  	conn := meta.(*AWSClient).ec2conn
   199  
   200  	modifyOpts := &ec2.ModifyVpcPeeringConnectionOptionsInput{
   201  		VpcPeeringConnectionId: aws.String(d.Id()),
   202  	}
   203  
   204  	if v, ok := d.GetOk("accepter"); ok {
   205  		if s := v.(*schema.Set); len(s.List()) > 0 {
   206  			co := s.List()[0].(map[string]interface{})
   207  			modifyOpts.AccepterPeeringConnectionOptions = expandPeeringOptions(co)
   208  		}
   209  	}
   210  
   211  	if v, ok := d.GetOk("requester"); ok {
   212  		if s := v.(*schema.Set); len(s.List()) > 0 {
   213  			co := s.List()[0].(map[string]interface{})
   214  			modifyOpts.RequesterPeeringConnectionOptions = expandPeeringOptions(co)
   215  		}
   216  	}
   217  
   218  	log.Printf("[DEBUG] VPC Peering Connection modify options: %#v", modifyOpts)
   219  	if _, err := conn.ModifyVpcPeeringConnectionOptions(modifyOpts); err != nil {
   220  		return err
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  func resourceAwsVPCPeeringUpdate(d *schema.ResourceData, meta interface{}) error {
   227  	conn := meta.(*AWSClient).ec2conn
   228  
   229  	if err := setTags(conn, d); err != nil {
   230  		return err
   231  	} else {
   232  		d.SetPartial("tags")
   233  	}
   234  
   235  	pcRaw, _, err := resourceAwsVPCPeeringConnectionStateRefreshFunc(conn, d.Id())()
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	if pcRaw == nil {
   241  		d.SetId("")
   242  		return nil
   243  	}
   244  	pc := pcRaw.(*ec2.VpcPeeringConnection)
   245  
   246  	if _, ok := d.GetOk("auto_accept"); ok {
   247  		if pc.Status != nil && *pc.Status.Code == "pending-acceptance" {
   248  			status, err := resourceVPCPeeringConnectionAccept(conn, d.Id())
   249  			if err != nil {
   250  				return errwrap.Wrapf("Unable to accept VPC Peering Connection: {{err}}", err)
   251  			}
   252  			log.Printf("[DEBUG] VPC Peering Connection accept status: %s", status)
   253  		}
   254  	}
   255  
   256  	if d.HasChange("accepter") || d.HasChange("requester") {
   257  		_, ok := d.GetOk("auto_accept")
   258  		if !ok && pc.Status != nil && *pc.Status.Code != "active" {
   259  			return fmt.Errorf("Unable to modify peering options. The VPC Peering Connection "+
   260  				"%q is not active. Please set `auto_accept` attribute to `true`, "+
   261  				"or activate VPC Peering Connection manually.", d.Id())
   262  		}
   263  
   264  		if err := resourceVPCPeeringConnectionOptionsModify(d, meta); err != nil {
   265  			return errwrap.Wrapf("Error modifying VPC Peering Connection options: {{err}}", err)
   266  		}
   267  	}
   268  
   269  	return resourceAwsVPCPeeringRead(d, meta)
   270  }
   271  
   272  func resourceAwsVPCPeeringDelete(d *schema.ResourceData, meta interface{}) error {
   273  	conn := meta.(*AWSClient).ec2conn
   274  
   275  	_, err := conn.DeleteVpcPeeringConnection(
   276  		&ec2.DeleteVpcPeeringConnectionInput{
   277  			VpcPeeringConnectionId: aws.String(d.Id()),
   278  		})
   279  
   280  	return err
   281  }
   282  
   283  // resourceAwsVPCPeeringConnectionStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   284  // a VPCPeeringConnection.
   285  func resourceAwsVPCPeeringConnectionStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   286  	return func() (interface{}, string, error) {
   287  		resp, err := conn.DescribeVpcPeeringConnections(&ec2.DescribeVpcPeeringConnectionsInput{
   288  			VpcPeeringConnectionIds: []*string{aws.String(id)},
   289  		})
   290  		if err != nil {
   291  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcPeeringConnectionID.NotFound" {
   292  				resp = nil
   293  			} else {
   294  				log.Printf("Error reading VPC Peering Connection details: %s", err)
   295  				return nil, "error", err
   296  			}
   297  		}
   298  
   299  		if resp == nil {
   300  			// Sometimes AWS just has consistency issues and doesn't see
   301  			// our instance yet. Return an empty state.
   302  			return nil, "", nil
   303  		}
   304  
   305  		pc := resp.VpcPeeringConnections[0]
   306  
   307  		// A VPC Peering Connection can exist in a failed state due to
   308  		// incorrect VPC ID, account ID, or overlapping IP address range,
   309  		// thus we short circuit before the time out would occur.
   310  		if pc != nil && *pc.Status.Code == "failed" {
   311  			return nil, "failed", errors.New(*pc.Status.Message)
   312  		}
   313  
   314  		return pc, *pc.Status.Code, nil
   315  	}
   316  }
   317  
   318  func vpcPeeringConnectionOptionsSchema() *schema.Schema {
   319  	return &schema.Schema{
   320  		Type:     schema.TypeSet,
   321  		Optional: true,
   322  		Computed: true,
   323  		MaxItems: 1,
   324  		Elem: &schema.Resource{
   325  			Schema: map[string]*schema.Schema{
   326  				"allow_remote_vpc_dns_resolution": {
   327  					Type:     schema.TypeBool,
   328  					Optional: true,
   329  					Default:  false,
   330  				},
   331  				"allow_classic_link_to_remote_vpc": {
   332  					Type:     schema.TypeBool,
   333  					Optional: true,
   334  					Default:  false,
   335  				},
   336  				"allow_vpc_to_remote_classic_link": {
   337  					Type:     schema.TypeBool,
   338  					Optional: true,
   339  					Default:  false,
   340  				},
   341  			},
   342  		},
   343  	}
   344  }
   345  
   346  func flattenPeeringOptions(options *ec2.VpcPeeringConnectionOptionsDescription) (results []map[string]interface{}) {
   347  	m := make(map[string]interface{})
   348  
   349  	if options.AllowDnsResolutionFromRemoteVpc != nil {
   350  		m["allow_remote_vpc_dns_resolution"] = *options.AllowDnsResolutionFromRemoteVpc
   351  	}
   352  
   353  	if options.AllowEgressFromLocalClassicLinkToRemoteVpc != nil {
   354  		m["allow_classic_link_to_remote_vpc"] = *options.AllowEgressFromLocalClassicLinkToRemoteVpc
   355  	}
   356  
   357  	if options.AllowEgressFromLocalVpcToRemoteClassicLink != nil {
   358  		m["allow_vpc_to_remote_classic_link"] = *options.AllowEgressFromLocalVpcToRemoteClassicLink
   359  	}
   360  
   361  	results = append(results, m)
   362  	return
   363  }
   364  
   365  func expandPeeringOptions(m map[string]interface{}) *ec2.PeeringConnectionOptionsRequest {
   366  	r := &ec2.PeeringConnectionOptionsRequest{}
   367  
   368  	if v, ok := m["allow_remote_vpc_dns_resolution"]; ok {
   369  		r.AllowDnsResolutionFromRemoteVpc = aws.Bool(v.(bool))
   370  	}
   371  
   372  	if v, ok := m["allow_classic_link_to_remote_vpc"]; ok {
   373  		r.AllowEgressFromLocalClassicLinkToRemoteVpc = aws.Bool(v.(bool))
   374  	}
   375  
   376  	if v, ok := m["allow_vpc_to_remote_classic_link"]; ok {
   377  		r.AllowEgressFromLocalVpcToRemoteClassicLink = aws.Bool(v.(bool))
   378  	}
   379  
   380  	return r
   381  }