-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Changes from 3 commits
4441908
78f20a6
73ad7e3
7ad09a8
7199e84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
package google | ||
|
||
import ( | ||
"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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]) | ||
} |
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)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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])?))" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
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" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
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.