Skip to content

Commit 33f37ef

Browse files
authored
Add support for shared VPC (hashicorp#572)
* 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
1 parent e33eacd commit 33f37ef

8 files changed

+429
-1
lines changed

google/provider.go

+2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ func Provider() terraform.ResourceProvider {
9494
"google_compute_router": resourceComputeRouter(),
9595
"google_compute_router_interface": resourceComputeRouterInterface(),
9696
"google_compute_router_peer": resourceComputeRouterPeer(),
97+
"google_compute_shared_vpc_host_project": resourceComputeSharedVpcHostProject(),
98+
"google_compute_shared_vpc_service_project": resourceComputeSharedVpcServiceProject(),
9799
"google_compute_ssl_certificate": resourceComputeSslCertificate(),
98100
"google_compute_subnetwork": resourceComputeSubnetwork(),
99101
"google_compute_target_http_proxy": resourceComputeTargetHttpProxy(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/hashicorp/terraform/helper/schema"
8+
)
9+
10+
func resourceComputeSharedVpcHostProject() *schema.Resource {
11+
return &schema.Resource{
12+
Create: resourceComputeSharedVpcHostProjectCreate,
13+
Read: resourceComputeSharedVpcHostProjectRead,
14+
Delete: resourceComputeSharedVpcHostProjectDelete,
15+
16+
Schema: map[string]*schema.Schema{
17+
"project": &schema.Schema{
18+
Type: schema.TypeString,
19+
Required: true,
20+
ForceNew: true,
21+
},
22+
},
23+
}
24+
}
25+
26+
func resourceComputeSharedVpcHostProjectCreate(d *schema.ResourceData, meta interface{}) error {
27+
config := meta.(*Config)
28+
29+
hostProject := d.Get("project").(string)
30+
op, err := config.clientCompute.Projects.EnableXpnHost(hostProject).Do()
31+
if err != nil {
32+
return fmt.Errorf("Error enabling Shared VPC Host %q: %s", hostProject, err)
33+
}
34+
35+
d.SetId(hostProject)
36+
37+
err = computeOperationWait(config, op, hostProject, "Enabling Shared VPC Host")
38+
if err != nil {
39+
d.SetId("")
40+
return err
41+
}
42+
43+
return nil
44+
}
45+
46+
func resourceComputeSharedVpcHostProjectRead(d *schema.ResourceData, meta interface{}) error {
47+
config := meta.(*Config)
48+
49+
hostProject := d.Get("project").(string)
50+
51+
project, err := config.clientCompute.Projects.Get(hostProject).Do()
52+
if err != nil {
53+
return handleNotFoundError(err, d, fmt.Sprintf("Project data for project %q", hostProject))
54+
}
55+
56+
if project.XpnProjectStatus != "HOST" {
57+
log.Printf("[WARN] Removing Shared VPC host resource %q because it's not enabled server-side", hostProject)
58+
d.SetId("")
59+
}
60+
61+
return nil
62+
}
63+
64+
func resourceComputeSharedVpcHostProjectDelete(d *schema.ResourceData, meta interface{}) error {
65+
config := meta.(*Config)
66+
hostProject := d.Get("project").(string)
67+
68+
op, err := config.clientCompute.Projects.DisableXpnHost(hostProject).Do()
69+
if err != nil {
70+
return fmt.Errorf("Error disabling Shared VPC Host %q: %s", hostProject, err)
71+
}
72+
73+
err = computeOperationWait(config, op, hostProject, "Disabling Shared VPC Host")
74+
if err != nil {
75+
return err
76+
}
77+
78+
d.SetId("")
79+
return nil
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
6+
"google.golang.org/api/compute/v1"
7+
8+
"github.com/hashicorp/terraform/helper/schema"
9+
"google.golang.org/api/googleapi"
10+
"log"
11+
)
12+
13+
func resourceComputeSharedVpcServiceProject() *schema.Resource {
14+
return &schema.Resource{
15+
Create: resourceComputeSharedVpcServiceProjectCreate,
16+
Read: resourceComputeSharedVpcServiceProjectRead,
17+
Delete: resourceComputeSharedVpcServiceProjectDelete,
18+
19+
Schema: map[string]*schema.Schema{
20+
"host_project": &schema.Schema{
21+
Type: schema.TypeString,
22+
Required: true,
23+
ForceNew: true,
24+
},
25+
"service_project": &schema.Schema{
26+
Type: schema.TypeString,
27+
Required: true,
28+
ForceNew: true,
29+
},
30+
},
31+
}
32+
}
33+
34+
func resourceComputeSharedVpcServiceProjectCreate(d *schema.ResourceData, meta interface{}) error {
35+
config := meta.(*Config)
36+
37+
hostProject := d.Get("host_project").(string)
38+
serviceProject := d.Get("service_project").(string)
39+
40+
req := &compute.ProjectsEnableXpnResourceRequest{
41+
XpnResource: &compute.XpnResourceId{
42+
Id: serviceProject,
43+
Type: "PROJECT",
44+
},
45+
}
46+
op, err := config.clientCompute.Projects.EnableXpnResource(hostProject, req).Do()
47+
if err != nil {
48+
return err
49+
}
50+
if err = computeOperationWait(config, op, hostProject, "Enabling Shared VPC Resource"); err != nil {
51+
return err
52+
}
53+
54+
d.SetId(fmt.Sprintf("%s/%s", hostProject, serviceProject))
55+
56+
return nil
57+
}
58+
59+
func resourceComputeSharedVpcServiceProjectRead(d *schema.ResourceData, meta interface{}) error {
60+
config := meta.(*Config)
61+
62+
hostProject := d.Get("host_project").(string)
63+
serviceProject := d.Get("service_project").(string)
64+
65+
associatedHostProject, err := config.clientCompute.Projects.GetXpnHost(serviceProject).Do()
66+
if err != nil {
67+
log.Printf("[WARN] Removing shared VPC service. The service project is not associated with any host")
68+
69+
d.SetId("")
70+
return nil
71+
}
72+
73+
if hostProject != associatedHostProject.Name {
74+
log.Printf("[WARN] Removing shared VPC service. Expected associated host project to be '%s', got '%s'", hostProject, associatedHostProject.Name)
75+
d.SetId("")
76+
return nil
77+
}
78+
79+
return nil
80+
}
81+
82+
func resourceComputeSharedVpcServiceProjectDelete(d *schema.ResourceData, meta interface{}) error {
83+
config := meta.(*Config)
84+
hostProject := d.Get("host_project").(string)
85+
serviceProject := d.Get("service_project").(string)
86+
87+
if err := disableXpnResource(config, hostProject, serviceProject); err != nil {
88+
// Don't fail if the service project is already disabled.
89+
if !isDisabledXpnResourceError(err) {
90+
return fmt.Errorf("Error disabling Shared VPC Resource %q: %s", serviceProject, err)
91+
}
92+
}
93+
94+
return nil
95+
}
96+
97+
func disableXpnResource(config *Config, hostProject, project string) error {
98+
req := &compute.ProjectsDisableXpnResourceRequest{
99+
XpnResource: &compute.XpnResourceId{
100+
Id: project,
101+
Type: "PROJECT",
102+
},
103+
}
104+
op, err := config.clientCompute.Projects.DisableXpnResource(hostProject, req).Do()
105+
if err != nil {
106+
return err
107+
}
108+
if err = computeOperationWait(config, op, hostProject, "Disabling Shared VPC Resource"); err != nil {
109+
return err
110+
}
111+
return nil
112+
}
113+
114+
func isDisabledXpnResourceError(err error) bool {
115+
if gerr, ok := err.(*googleapi.Error); ok {
116+
if gerr.Code == 400 && len(gerr.Errors) > 0 && gerr.Errors[0].Reason == "invalidResourceUsage" {
117+
return true
118+
}
119+
}
120+
return false
121+
}
+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform/helper/acctest"
8+
"github.com/hashicorp/terraform/helper/resource"
9+
"github.com/hashicorp/terraform/terraform"
10+
"os"
11+
)
12+
13+
func TestAccComputeSharedVpc_basic(t *testing.T) {
14+
skipIfEnvNotSet(t, "GOOGLE_ORG", "GOOGLE_BILLING_ACCOUNT")
15+
billingId := os.Getenv("GOOGLE_BILLING_ACCOUNT")
16+
17+
hostProject := "xpn-host-" + acctest.RandString(10)
18+
serviceProject := "xpn-service-" + acctest.RandString(10)
19+
20+
resource.Test(t, resource.TestCase{
21+
PreCheck: func() { testAccPreCheck(t) },
22+
Providers: testAccProviders,
23+
Steps: []resource.TestStep{
24+
resource.TestStep{
25+
Config: testAccComputeSharedVpc_basic(hostProject, serviceProject, org, billingId),
26+
Check: resource.ComposeTestCheckFunc(
27+
testAccCheckComputeSharedVpcHostProject(hostProject, true),
28+
testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject, true),
29+
),
30+
},
31+
// Use a separate TestStep rather than a CheckDestroy because we need the project to still exist.
32+
resource.TestStep{
33+
Config: testAccComputeSharedVpc_disabled(hostProject, serviceProject, org, billingId),
34+
Check: resource.ComposeTestCheckFunc(
35+
testAccCheckComputeSharedVpcHostProject(hostProject, false),
36+
testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject, false),
37+
),
38+
},
39+
},
40+
})
41+
}
42+
43+
func testAccCheckComputeSharedVpcHostProject(hostProject string, enabled bool) resource.TestCheckFunc {
44+
return func(s *terraform.State) error {
45+
config := testAccProvider.Meta().(*Config)
46+
47+
found, err := config.clientCompute.Projects.Get(hostProject).Do()
48+
if err != nil {
49+
return fmt.Errorf("Error reading project %s: %s", hostProject, err)
50+
}
51+
52+
if found.Name != hostProject {
53+
return fmt.Errorf("Project %s not found", hostProject)
54+
}
55+
56+
if enabled != (found.XpnProjectStatus == "HOST") {
57+
return fmt.Errorf("Project %q shared VPC status was not expected, got %q", hostProject, found.XpnProjectStatus)
58+
}
59+
60+
return nil
61+
}
62+
}
63+
64+
func testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject string, enabled bool) resource.TestCheckFunc {
65+
return func(s *terraform.State) error {
66+
config := testAccProvider.Meta().(*Config)
67+
serviceHostProject, err := config.clientCompute.Projects.GetXpnHost(serviceProject).Do()
68+
if err != nil {
69+
if enabled {
70+
return fmt.Errorf("Expected service project to be enabled.")
71+
}
72+
return nil
73+
}
74+
75+
if enabled != (serviceHostProject.Name == hostProject) {
76+
return fmt.Errorf("Wrong host project for the given service project. Expected '%s', got '%s'", hostProject, serviceHostProject.Name)
77+
}
78+
79+
return nil
80+
}
81+
}
82+
83+
func testAccComputeSharedVpc_basic(hostProject, serviceProject, org, billing string) string {
84+
return fmt.Sprintf(`
85+
resource "google_project" "host" {
86+
project_id = "%s"
87+
name = "%s"
88+
org_id = "%s"
89+
billing_account = "%s"
90+
}
91+
92+
resource "google_project" "service" {
93+
project_id = "%s"
94+
name = "%s"
95+
org_id = "%s"
96+
billing_account = "%s"
97+
}
98+
99+
resource "google_project_services" "host" {
100+
project = "${google_project.host.project_id}"
101+
services = ["compute.googleapis.com"]
102+
}
103+
104+
resource "google_project_services" "service" {
105+
project = "${google_project.service.project_id}"
106+
services = ["compute.googleapis.com"]
107+
}
108+
109+
resource "google_compute_shared_vpc_host_project" "host" {
110+
project = "${google_project.host.project_id}"
111+
depends_on = ["google_project_services.host"]
112+
}
113+
114+
resource "google_compute_shared_vpc_service_project" "service" {
115+
host_project = "${google_project.host.project_id}"
116+
service_project = "${google_project.service.project_id}"
117+
depends_on = ["google_compute_shared_vpc_host_project.host", "google_project_services.service"]
118+
}`, hostProject, hostProject, org, billing, serviceProject, serviceProject, org, billing)
119+
}
120+
121+
func testAccComputeSharedVpc_disabled(hostProject, serviceProject, org, billing string) string {
122+
return fmt.Sprintf(`
123+
resource "google_project" "host" {
124+
project_id = "%s"
125+
name = "%s"
126+
org_id = "%s"
127+
billing_account = "%s"
128+
}
129+
130+
resource "google_project" "service" {
131+
project_id = "%s"
132+
name = "%s"
133+
org_id = "%s"
134+
billing_account = "%s"
135+
}
136+
137+
resource "google_project_services" "host" {
138+
project = "${google_project.host.project_id}"
139+
services = ["compute.googleapis.com"]
140+
}
141+
142+
resource "google_project_services" "service" {
143+
project = "${google_project.service.project_id}"
144+
services = ["compute.googleapis.com"]
145+
}
146+
`, hostProject, hostProject, org, billing, serviceProject, serviceProject, org, billing)
147+
}

google/serviceman_operation.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (w *ServiceManagementOperationWaiter) Conf() *resource.StateChangeConf {
4040
}
4141

4242
func serviceManagementOperationWait(config *Config, op *servicemanagement.Operation, activity string) error {
43-
return serviceManagementOperationWaitTime(config, op, activity, 4)
43+
return serviceManagementOperationWaitTime(config, op, activity, 10)
4444
}
4545

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

0 commit comments

Comments
 (0)