Skip to content
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

Add support for shared VPC #572

Merged
merged 11 commits into from
Oct 11, 2017
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" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a comment as to why this error is special?

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

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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also check that delete works? 5f9ef48 may be of use here

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

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