Skip to content

Add new resource google_compute_network_peering #259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 28, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func Provider() terraform.ResourceProvider {
"google_compute_instance_group_manager": resourceComputeInstanceGroupManager(),
"google_compute_instance_template": resourceComputeInstanceTemplate(),
"google_compute_network": resourceComputeNetwork(),
"google_compute_network_peering": resourceComputeNetworkPeering(),
"google_compute_project_metadata": resourceComputeProjectMetadata(),
"google_compute_region_backend_service": resourceComputeRegionBackendService(),
"google_compute_route": resourceComputeRoute(),
Expand Down
192 changes: 192 additions & 0 deletions google/resource_compute_network_peering.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package google

import (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: standard imports should all live at the top of the import block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super minor: looks like sort snuck in afterwards.

"fmt"
"log"
"regexp"

"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
"sort"
)

const peerNetworkLinkRegex = "projects/(" + ProjectRegex + ")/global/networks/((?:[a-z](?:[-a-z0-9]*[a-z0-9])?))$"

func resourceComputeNetworkPeering() *schema.Resource {
return &schema.Resource{
Create: resourceComputeNetworkPeeringCreate,
Read: resourceComputeNetworkPeeringRead,
Delete: resourceComputeNetworkPeeringDelete,

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateGCPName,
},
"network": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateRegexp(peerNetworkLinkRegex),
DiffSuppressFunc: compareSelfLinkRelativePaths,
},
"peer_network": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateRegexp(peerNetworkLinkRegex),
DiffSuppressFunc: compareSelfLinkRelativePaths,
},
"auto_create_routes": &schema.Schema{
Type: schema.TypeBool,
ForceNew: true,
Optional: true,
Default: true,
},
"state": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"state_details": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceComputeNetworkPeeringCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

project, err := getProject(d, config)
if err != nil {
return err
}

name := d.Get("name").(string)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: name, peerNetworkLink, and autoCreateRoutes can be inlined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

networkLink := d.Get("network").(string)
peerNetworkLink := d.Get("peer_network").(string)
autoCreateRoutes := d.Get("auto_create_routes").(bool)
networkName := getNameFromNetworkLink(networkLink)

request := &compute.NetworksAddPeeringRequest{
Name: name,
PeerNetwork: peerNetworkLink,
AutoCreateRoutes: autoCreateRoutes,
}

addOp, err := config.clientCompute.Networks.AddPeering(project, networkName, request).Do()
if err != nil {
return fmt.Errorf("Error adding network peering: %s", err)
}

err = computeOperationWait(config, addOp, project, "Adding Network Peering")
if err != nil {
return err
}

d.SetId(fmt.Sprintf("%s/%s", networkName, name))

return resourceComputeNetworkPeeringRead(d, meta)
}

func resourceComputeNetworkPeeringRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

project, err := getProject(d, config)
if err != nil {
return err
}

peeringName := d.Get("name").(string)
networkLink := d.Get("network").(string)
networkName := getNameFromNetworkLink(networkLink)

network, err := config.clientCompute.Networks.Get(project, networkName).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Network %q", networkName))
}

peering := findPeeringFromNetwork(network, peeringName)
if peering == nil {
log.Printf("[WARN] Removing network peering %s from network %s because it's gone", peeringName, networkName)
d.SetId("")
return nil
}

d.Set("peer_network", peering.Network)
d.Set("auto_create_routes", peering.AutoCreateRoutes)
d.Set("state", peering.State)
d.Set("state_details", peering.StateDetails)

return nil
}

func resourceComputeNetworkPeeringDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

// Remove the `network` to `peer_network` peering
project, err := getProject(d, config)
if err != nil {
return err
}

name := d.Get("name").(string)
networkLink := d.Get("network").(string)
peerNetworkLink := d.Get("peer_network").(string)
networkName := getNameFromNetworkLink(networkLink)
peerNetworkName := getNameFromNetworkLink(peerNetworkLink)

request := &compute.NetworksRemovePeeringRequest{
Name: name,
}

// Only one delete peering operation at a time can be performed inside any peered VPCs.
peeringLockName := getNetworkPeeringLockName(networkName, peerNetworkName)
mutexKV.Lock(peeringLockName)
defer mutexKV.Unlock(peeringLockName)

removeOp, err := config.clientCompute.Networks.RemovePeering(project, networkName, request).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
log.Printf("[WARN] Peering `%s` already removed from network `%s`", name, networkName)
} else {
return fmt.Errorf("Error removing peering `%s` from network `%s`: %s", name, networkName, err)
}
} else {
err = computeOperationWait(config, removeOp, project, "Removing Network Peering")
if err != nil {
return err
}
}

return nil
}

func findPeeringFromNetwork(network *compute.Network, peeringName string) *compute.NetworkPeering {
for _, p := range network.Peerings {
if p.Name == peeringName {
return p
}
}
return nil
}

func getNameFromNetworkLink(network string) string {
r := regexp.MustCompile(peerNetworkLinkRegex)

m := r.FindStringSubmatch(network)
return m[2]
}

func getNetworkPeeringLockName(networkName, peerNetworkName string) string {
// Whether you delete the peering from network A to B or the one from B to A, they
// cannot happen at the same time.
networks := []string{networkName, peerNetworkName}
sort.Strings(networks)

return fmt.Sprintf("network_peering/%s/%s", networks[0], networks[1])
}
121 changes: 121 additions & 0 deletions google/resource_compute_network_peering_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package google

import (
"fmt"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/compute/v1"
"strings"
"testing"
)

func TestAccComputeNetworkPeering_basic(t *testing.T) {
var peering compute.NetworkPeering

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccComputeNetworkPeeringDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeNetworkPeering_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeNetworkPeeringExist("google_compute_network_peering.foo", &peering),
testAccCheckComputeNetworkPeeringAutoCreateRoutes(true, &peering),
testAccCheckComputeNetworkPeeringExist("google_compute_network_peering.bar", &peering),
testAccCheckComputeNetworkPeeringAutoCreateRoutes(true, &peering),
),
},
},
})

}

func testAccComputeNetworkPeeringDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)

for _, rs := range s.RootModule().Resources {
if rs.Type != "google_compute_network_peering" {
continue
}

_, err := config.clientCompute.Networks.Get(
config.Project, rs.Primary.ID).Do()
if err == nil {
return fmt.Errorf("Network peering still exists")
}
}

return nil
}

func testAccCheckComputeNetworkPeeringExist(n string, peering *compute.NetworkPeering) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}

config := testAccProvider.Meta().(*Config)

parts := strings.Split(rs.Primary.ID, "/")
if len(parts) != 2 {
return fmt.Errorf("Invalid network peering identifier: %s", rs.Primary.ID)
}

networkName, peeringName := parts[0], parts[1]

network, err := config.clientCompute.Networks.Get(config.Project, networkName).Do()
if err != nil {
return err
}

found := findPeeringFromNetwork(network, peeringName)
if found == nil {
return fmt.Errorf("Network peering '%s' not found in network '%s'", peeringName, network.Name)
}
*peering = *found

return nil
}
}

func testAccCheckComputeNetworkPeeringAutoCreateRoutes(v bool, peering *compute.NetworkPeering) resource.TestCheckFunc {
return func(s *terraform.State) error {
if peering.AutoCreateRoutes != v {
return fmt.Errorf("should AutoCreateRoutes set to %t", v)
}

return nil
}
}

var testAccComputeNetworkPeering_basic = fmt.Sprintf(`
resource "google_compute_network" "network1" {
name = "network-test-1-%s"
auto_create_subnetworks = false
}

resource "google_compute_network" "network2" {
name = "network-test-2-%s"
auto_create_subnetworks = false
}

resource "google_compute_network_peering" "foo" {
name = "peering-test-1-%s"
network = "${google_compute_network.network1.self_link}"
peer_network = "${google_compute_network.network2.self_link}"
}

resource "google_compute_network_peering" "bar" {
name = "peering-test-2-%s"
auto_create_routes = true
network = "${google_compute_network.network2.self_link}"
peer_network = "${google_compute_network.network1.self_link}"
}
`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10), acctest.RandString(10))
2 changes: 2 additions & 0 deletions google/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"regexp"
)

const ProjectRegex = "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Project IDs generated through Terraform will follow this form, but I'm not sure if those rules have been followed for the entire lifetime of project ids in general.

We should be able to match against [^/]* and capture anything inside the projects/ part of a self link without overmatching past a /.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got the regex for the project description in the google cloud compute generated client:

	//     "project": {
	//       "description": "Project ID for this request.",
	//       "location": "path",
	//       "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))",
	//       "required": true,
	//       "type": "string"
	//     }

My goal with a specific regex is to try to fail at plan instead of apply. I think we should start moving towards this when format for names are clearly defined.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh cool, I hadn't noticed that regex. Do you mind adding a comment of where you got it from? 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added


func validateGCPName(v interface{}, k string) (ws []string, errors []error) {
re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$`
return validateRegexp(re)(v, k)
Expand Down
66 changes: 66 additions & 0 deletions website/docs/r/compute_network_peering.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
layout: "google"
page_title: "Google: google_compute_network_peering"
sidebar_current: "docs-google-compute-network-peering"
description: |-
Manages a network peering within GCE.
---

# google\_compute\_network\_peering

Manages a network peering within GCE. For more information see
[the official documentation](https://cloud.google.com/compute/docs/vpc/vpc-peering)
and
[API](https://cloud.google.com/compute/docs/reference/latest/networks).

~> **Note:** Both network must create a peering with each other for the peering to be functional.

~> **Note:** Subnets IP ranges across peered VPC networks cannot overlap.

## Example Usage

```hcl
resource "google_compute_network_peering" "peering1" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's make the resource this page is about live at the top of the example config.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

name = "peering1"
network = "${google_compute_network.default.self_link}"
peer_network = "${google_compute_network.other.self_link}"
}

resource "google_compute_network_peering" "peering2" {
name = "peering2"
network = "${google_compute_network.other.self_link}"
peer_network = "${google_compute_network.default.self_link}"
}

resource "google_compute_network" "default" {
name = "foobar"
auto_create_subnetworks = "false"
}

resource "google_compute_network" "other" {
name = "other"
auto_create_subnetworks = "false"
}
```

## Argument Reference

The following arguments are supported:

* `name` - (Required) Name of the peering.

* `network` - (Required) Resource link of the network to add a peering to.

* `peer_network` - (Required) Resource link of the peer network.

* `auto_create_routes` - (Optional) If set to `true`, the routes between the two networks will
be created and managed automatically. Defaults to `true`.

## Attributes Reference

In addition to the arguments listed above, the following computed attributes are
exported:

* `state` - State for the peering.

* `state_details` - Details about the current state of the peering.