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

support billing_project for google_project_service #5215

Merged
37 changes: 29 additions & 8 deletions mmv1/third_party/terraform/resources/resource_google_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"net/http"
"regexp"
"strconv"
"strings"
Expand All @@ -17,6 +18,11 @@ import (
"google.golang.org/api/serviceusage/v1"
)

type ServicesCall interface {
Header() http.Header
Do(opts ...googleapi.CallOption) (*serviceusage.Operation, error)
}

// resourceGoogleProject returns a *schema.Resource that allows a customer
// to declare a Google Cloud Project resource.
func resourceGoogleProject() *schema.Resource {
Expand Down Expand Up @@ -179,7 +185,14 @@ func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error
// a network and deleting it in the background.
if !d.Get("auto_create_network").(bool) {
// The compute API has to be enabled before we can delete a network.
if err = enableServiceUsageProjectServices([]string{"compute.googleapis.com"}, project.ProjectId, userAgent, config, d.Timeout(schema.TimeoutCreate)); err != nil {

billingProject := project.ProjectId
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
// err == nil indicates that the billing_project value was found
if bp, err := getBillingProject(d, config); err == nil {
billingProject = bp
}

if err = enableServiceUsageProjectServices([]string{"compute.googleapis.com"}, project.ProjectId, billingProject, userAgent, config, d.Timeout(schema.TimeoutCreate)); err != nil {
return errwrap.Wrapf("Error enabling the Compute Engine API required to delete the default network: {{err}} ", err)
}

Expand Down Expand Up @@ -580,7 +593,7 @@ func readGoogleProject(d *schema.ResourceData, config *Config, userAgent string)
}

// Enables services. WARNING: Use BatchRequestEnableServices for better batching if possible.
func enableServiceUsageProjectServices(services []string, project, userAgent string, config *Config, timeout time.Duration) error {
func enableServiceUsageProjectServices(services []string, project, billingProject, userAgent string, config *Config, timeout time.Duration) error {
// ServiceUsage does not allow more than 20 services to be enabled per
// batchEnable API call. See
// https://cloud.google.com/service-usage/docs/reference/rest/v1/services/batchEnable
Expand All @@ -595,7 +608,7 @@ func enableServiceUsageProjectServices(services []string, project, userAgent str
return nil
}

if err := doEnableServicesRequest(nextBatch, project, userAgent, config, timeout); err != nil {
if err := doEnableServicesRequest(nextBatch, project, billingProject, userAgent, config, timeout); err != nil {
return err
}
log.Printf("[DEBUG] Finished enabling next batch of %d project services: %+v", len(nextBatch), nextBatch)
Expand All @@ -605,25 +618,33 @@ func enableServiceUsageProjectServices(services []string, project, userAgent str
return waitForServiceUsageEnabledServices(services, project, userAgent, config, timeout)
}

func doEnableServicesRequest(services []string, project, userAgent string, config *Config, timeout time.Duration) error {
func doEnableServicesRequest(services []string, project, billingProject, userAgent string, config *Config, timeout time.Duration) error {
var op *serviceusage.Operation

var call ServicesCall
err := retryTimeDuration(func() error {
var rerr error
if len(services) == 1 {
// BatchEnable returns an error for a single item, so just enable
// using service endpoint.
name := fmt.Sprintf("projects/%s/services/%s", project, services[0])
req := &serviceusage.EnableServiceRequest{}
op, rerr = config.NewServiceUsageClient(userAgent).Services.Enable(name, req).Do()
call = config.NewServiceUsageClient(userAgent).Services.Enable(name, req)
} else {
// Batch enable for multiple services.
name := fmt.Sprintf("projects/%s", project)
req := &serviceusage.BatchEnableServicesRequest{ServiceIds: services}
op, rerr = config.NewServiceUsageClient(userAgent).Services.BatchEnable(name, req).Do()
call = config.NewServiceUsageClient(userAgent).Services.BatchEnable(name, req)
}
if config.UserProjectOverride && billingProject != "" {
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
call.Header().Add("X-Goog-User-Project", billingProject)
}
op, rerr = call.Do()
return handleServiceUsageRetryableError(rerr)
}, timeout, serviceUsageServiceBeingActivated)
},
timeout,
serviceUsageServiceBeingActivated,
retryOn403NTimes(4), // retry 4 times due to self referential activation taking time to propagate tpg#9489
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
)
if err != nil {
return errwrap.Wrapf("failed to send enable services request: {{err}}", err)
}
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,13 @@ func resourceGoogleProjectServiceRead(d *schema.ResourceData, meta interface{})
// Verify project for services still exists
projectGetCall := config.NewResourceManagerClient(userAgent).Projects.Get(project)
if config.UserProjectOverride {
projectGetCall.Header().Add("X-Goog-User-Project", project)
billingProject := project

// err == nil indicates that the billing_project value was found
if bp, err := getBillingProject(d, config); err == nil {
billingProject = bp
}
projectGetCall.Header().Add("X-Goog-User-Project", billingProject)
}
p, err := projectGetCall.Do()

Expand Down Expand Up @@ -268,7 +274,14 @@ func disableServiceUsageProjectService(service, project string, d *schema.Resour
DisableDependentServices: disableDependentServices,
})
if config.UserProjectOverride {
servicesDisableCall.Header().Add("X-Goog-User-Project", project)
billingProject := project

// err == nil indicates that the billing_project value was found
if bp, err := getBillingProject(d, config); err == nil {
billingProject = bp
}

servicesDisableCall.Header().Add("X-Goog-User-Project", billingProject)
}
sop, err := servicesDisableCall.Do()
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions mmv1/third_party/terraform/utils/error_retry_predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,20 @@ func serviceUsageServiceBeingActivated(err error) (bool, string) {
return false, ""
}

func retryOn403NTimes(n int) func(error) (bool, string) {
count := 0

return func(err error) (bool, string) {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 403 {
ScottSuarez marked this conversation as resolved.
Show resolved Hide resolved
if count < n {
count += 1
return true, fmt.Sprintf("retrying on 403 %v more times", count-n-1)
}
}
return false, ""
}
}

// Retry if Bigquery operation returns a 403 with a specific message for
// concurrent operations (which are implemented in terms of 'edit quota').
func isBigqueryIAMQuotaError(err error) (bool, string) {
Expand Down
22 changes: 17 additions & 5 deletions mmv1/third_party/terraform/utils/serviceusage_batching.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@ func BatchRequestEnableService(service string, project string, d *schema.Resourc
return err
}

billingProject := project
// err == nil indicates that the billing_project value was found
if bp, err := getBillingProject(d, config); err == nil {
billingProject = bp
}

req := &BatchRequest{
ResourceName: project,
Body: []string{service},
CombineF: combineServiceUsageServicesBatches,
SendF: sendBatchFuncEnableServices(config, userAgent, d.Timeout(schema.TimeoutCreate)),
SendF: sendBatchFuncEnableServices(config, userAgent, billingProject, d.Timeout(schema.TimeoutCreate)),
DebugId: fmt.Sprintf("Enable Project Service %q for project %q", service, project),
}

Expand All @@ -51,11 +57,17 @@ func tryEnableRenamedService(service, altName string, project string, d *schema.
log.Printf("[DEBUG] found renamed service %s (with alternate name %s)", service, altName)
// use a short timeout- failures are likely

billingProject := project
// err == nil indicates that the billing_project value was found
if bp, err := getBillingProject(d, config); err == nil {
billingProject = bp
}

log.Printf("[DEBUG] attempting enabling service with user-specified name %s", service)
err = enableServiceUsageProjectServices([]string{service}, project, userAgent, config, 1*time.Minute)
err = enableServiceUsageProjectServices([]string{service}, project, billingProject, userAgent, config, 1*time.Minute)
if err != nil {
log.Printf("[DEBUG] saw error %s. attempting alternate name %v", err, altName)
err2 := enableServiceUsageProjectServices([]string{altName}, project, userAgent, config, 1*time.Minute)
err2 := enableServiceUsageProjectServices([]string{altName}, project, billingProject, userAgent, config, 1*time.Minute)
if err2 != nil {
return fmt.Errorf("Saw 2 subsequent errors attempting to enable a renamed service: %s / %s", err, err2)
}
Expand Down Expand Up @@ -97,13 +109,13 @@ func combineServiceUsageServicesBatches(srvsRaw interface{}, toAddRaw interface{
return append(srvs, toAdd...), nil
}

func sendBatchFuncEnableServices(config *Config, userAgent string, timeout time.Duration) BatcherSendFunc {
func sendBatchFuncEnableServices(config *Config, userAgent, billingProject string, timeout time.Duration) BatcherSendFunc {
return func(project string, toEnableRaw interface{}) (interface{}, error) {
toEnable, ok := toEnableRaw.([]string)
if !ok {
return nil, fmt.Errorf("Expected batch body type to be []string, got %v. This is a provider error.", toEnableRaw)
}
return nil, enableServiceUsageProjectServices(toEnable, project, userAgent, config, timeout)
return nil, enableServiceUsageProjectServices(toEnable, project, billingProject, userAgent, config, timeout)
}
}

Expand Down