Skip to content

Commit

Permalink
Add support for shared VPC (hashicorp#572)
Browse files Browse the repository at this point in the history
* Add VPC host project resource
* Add VPC service project resource
* Add combined acceptance test for shared VPC
* Add docs for shared VPC
* Increase deadline for project services operation
  • Loading branch information
rosbo authored Oct 11, 2017
1 parent e33eacd commit 33f37ef
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 1 deletion.
2 changes: 2 additions & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ func Provider() terraform.ResourceProvider {
"google_compute_router": resourceComputeRouter(),
"google_compute_router_interface": resourceComputeRouterInterface(),
"google_compute_router_peer": resourceComputeRouterPeer(),
"google_compute_shared_vpc_host_project": resourceComputeSharedVpcHostProject(),
"google_compute_shared_vpc_service_project": resourceComputeSharedVpcServiceProject(),
"google_compute_ssl_certificate": resourceComputeSslCertificate(),
"google_compute_subnetwork": resourceComputeSubnetwork(),
"google_compute_target_http_proxy": resourceComputeTargetHttpProxy(),
Expand Down
80 changes: 80 additions & 0 deletions google/resource_compute_shared_vpc_host_project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package google

import (
"fmt"
"log"

"github.com/hashicorp/terraform/helper/schema"
)

func resourceComputeSharedVpcHostProject() *schema.Resource {
return &schema.Resource{
Create: resourceComputeSharedVpcHostProjectCreate,
Read: resourceComputeSharedVpcHostProjectRead,
Delete: resourceComputeSharedVpcHostProjectDelete,

Schema: map[string]*schema.Schema{
"project": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

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

hostProject := d.Get("project").(string)
op, err := config.clientCompute.Projects.EnableXpnHost(hostProject).Do()
if err != nil {
return fmt.Errorf("Error enabling Shared VPC Host %q: %s", hostProject, err)
}

d.SetId(hostProject)

err = computeOperationWait(config, op, hostProject, "Enabling Shared VPC Host")
if err != nil {
d.SetId("")
return err
}

return nil
}

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

hostProject := d.Get("project").(string)

project, err := config.clientCompute.Projects.Get(hostProject).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Project data for project %q", hostProject))
}

if project.XpnProjectStatus != "HOST" {
log.Printf("[WARN] Removing Shared VPC host resource %q because it's not enabled server-side", hostProject)
d.SetId("")
}

return nil
}

func resourceComputeSharedVpcHostProjectDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
hostProject := d.Get("project").(string)

op, err := config.clientCompute.Projects.DisableXpnHost(hostProject).Do()
if err != nil {
return fmt.Errorf("Error disabling Shared VPC Host %q: %s", hostProject, err)
}

err = computeOperationWait(config, op, hostProject, "Disabling Shared VPC Host")
if err != nil {
return err
}

d.SetId("")
return nil
}
121 changes: 121 additions & 0 deletions google/resource_compute_shared_vpc_service_project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package google

import (
"fmt"

"google.golang.org/api/compute/v1"

"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/googleapi"
"log"
)

func resourceComputeSharedVpcServiceProject() *schema.Resource {
return &schema.Resource{
Create: resourceComputeSharedVpcServiceProjectCreate,
Read: resourceComputeSharedVpcServiceProjectRead,
Delete: resourceComputeSharedVpcServiceProjectDelete,

Schema: map[string]*schema.Schema{
"host_project": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"service_project": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

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

hostProject := d.Get("host_project").(string)
serviceProject := d.Get("service_project").(string)

req := &compute.ProjectsEnableXpnResourceRequest{
XpnResource: &compute.XpnResourceId{
Id: serviceProject,
Type: "PROJECT",
},
}
op, err := config.clientCompute.Projects.EnableXpnResource(hostProject, req).Do()
if err != nil {
return err
}
if err = computeOperationWait(config, op, hostProject, "Enabling Shared VPC Resource"); err != nil {
return err
}

d.SetId(fmt.Sprintf("%s/%s", hostProject, serviceProject))

return nil
}

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

hostProject := d.Get("host_project").(string)
serviceProject := d.Get("service_project").(string)

associatedHostProject, err := config.clientCompute.Projects.GetXpnHost(serviceProject).Do()
if err != nil {
log.Printf("[WARN] Removing shared VPC service. The service project is not associated with any host")

d.SetId("")
return nil
}

if hostProject != associatedHostProject.Name {
log.Printf("[WARN] Removing shared VPC service. Expected associated host project to be '%s', got '%s'", hostProject, associatedHostProject.Name)
d.SetId("")
return nil
}

return nil
}

func resourceComputeSharedVpcServiceProjectDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
hostProject := d.Get("host_project").(string)
serviceProject := d.Get("service_project").(string)

if err := disableXpnResource(config, hostProject, serviceProject); err != nil {
// Don't fail if the service project is already disabled.
if !isDisabledXpnResourceError(err) {
return fmt.Errorf("Error disabling Shared VPC Resource %q: %s", serviceProject, err)
}
}

return nil
}

func disableXpnResource(config *Config, hostProject, project string) error {
req := &compute.ProjectsDisableXpnResourceRequest{
XpnResource: &compute.XpnResourceId{
Id: project,
Type: "PROJECT",
},
}
op, err := config.clientCompute.Projects.DisableXpnResource(hostProject, req).Do()
if err != nil {
return err
}
if err = computeOperationWait(config, op, hostProject, "Disabling Shared VPC Resource"); err != nil {
return err
}
return nil
}

func isDisabledXpnResourceError(err error) bool {
if gerr, ok := err.(*googleapi.Error); ok {
if gerr.Code == 400 && len(gerr.Errors) > 0 && gerr.Errors[0].Reason == "invalidResourceUsage" {
return true
}
}
return false
}
147 changes: 147 additions & 0 deletions google/resource_compute_shared_vpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package google

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"os"
)

func TestAccComputeSharedVpc_basic(t *testing.T) {
skipIfEnvNotSet(t, "GOOGLE_ORG", "GOOGLE_BILLING_ACCOUNT")
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")

hostProject := "xpn-host-" + acctest.RandString(10)
serviceProject := "xpn-service-" + acctest.RandString(10)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeSharedVpc_basic(hostProject, serviceProject, org, billingId),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSharedVpcHostProject(hostProject, true),
testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject, true),
),
},
// Use a separate TestStep rather than a CheckDestroy because we need the project to still exist.
resource.TestStep{
Config: testAccComputeSharedVpc_disabled(hostProject, serviceProject, org, billingId),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeSharedVpcHostProject(hostProject, false),
testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject, false),
),
},
},
})
}

func testAccCheckComputeSharedVpcHostProject(hostProject string, enabled bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)

found, err := config.clientCompute.Projects.Get(hostProject).Do()
if err != nil {
return fmt.Errorf("Error reading project %s: %s", hostProject, err)
}

if found.Name != hostProject {
return fmt.Errorf("Project %s not found", hostProject)
}

if enabled != (found.XpnProjectStatus == "HOST") {
return fmt.Errorf("Project %q shared VPC status was not expected, got %q", hostProject, found.XpnProjectStatus)
}

return nil
}
}

func testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject string, enabled bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
serviceHostProject, err := config.clientCompute.Projects.GetXpnHost(serviceProject).Do()
if err != nil {
if enabled {
return fmt.Errorf("Expected service project to be enabled.")
}
return nil
}

if enabled != (serviceHostProject.Name == hostProject) {
return fmt.Errorf("Wrong host project for the given service project. Expected '%s', got '%s'", hostProject, serviceHostProject.Name)
}

return nil
}
}

func testAccComputeSharedVpc_basic(hostProject, serviceProject, org, billing string) string {
return fmt.Sprintf(`
resource "google_project" "host" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}
resource "google_project" "service" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}
resource "google_project_services" "host" {
project = "${google_project.host.project_id}"
services = ["compute.googleapis.com"]
}
resource "google_project_services" "service" {
project = "${google_project.service.project_id}"
services = ["compute.googleapis.com"]
}
resource "google_compute_shared_vpc_host_project" "host" {
project = "${google_project.host.project_id}"
depends_on = ["google_project_services.host"]
}
resource "google_compute_shared_vpc_service_project" "service" {
host_project = "${google_project.host.project_id}"
service_project = "${google_project.service.project_id}"
depends_on = ["google_compute_shared_vpc_host_project.host", "google_project_services.service"]
}`, hostProject, hostProject, org, billing, serviceProject, serviceProject, org, billing)
}

func testAccComputeSharedVpc_disabled(hostProject, serviceProject, org, billing string) string {
return fmt.Sprintf(`
resource "google_project" "host" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}
resource "google_project" "service" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}
resource "google_project_services" "host" {
project = "${google_project.host.project_id}"
services = ["compute.googleapis.com"]
}
resource "google_project_services" "service" {
project = "${google_project.service.project_id}"
services = ["compute.googleapis.com"]
}
`, hostProject, hostProject, org, billing, serviceProject, serviceProject, org, billing)
}
2 changes: 1 addition & 1 deletion google/serviceman_operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (w *ServiceManagementOperationWaiter) Conf() *resource.StateChangeConf {
}

func serviceManagementOperationWait(config *Config, op *servicemanagement.Operation, activity string) error {
return serviceManagementOperationWaitTime(config, op, activity, 4)
return serviceManagementOperationWaitTime(config, op, activity, 10)
}

func serviceManagementOperationWaitTime(config *Config, op *servicemanagement.Operation, activity string, timeoutMin int) error {
Expand Down
Loading

0 comments on commit 33f37ef

Please sign in to comment.