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

google_project_services is deprecated - use google_project_service #2411

Merged
merged 5 commits into from
Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/terraform
2 changes: 1 addition & 1 deletion build/terraform-beta
67 changes: 67 additions & 0 deletions third_party/terraform/resources/resource_google_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"google.golang.org/api/cloudbilling/v1"
"google.golang.org/api/cloudresourcemanager/v1"
Expand Down Expand Up @@ -579,3 +580,69 @@ func readGoogleProject(d *schema.ResourceData, config *Config) (*cloudresourcema
}, d.Timeout(schema.TimeoutRead))
return p, err
}

// Enables services. WARNING: Use BatchRequestEnableServices for better batching if possible.
func enableServiceUsageProjectServices(services []string, project 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
for i := 0; i < len(services); i += maxServiceUsageBatchSize {
j := i + maxServiceUsageBatchSize
if j > len(services) {
j = len(services)
}
nextBatch := services[i:j]
if len(nextBatch) == 0 {
// All batches finished, return.
return nil
}

if err := doEnableServicesRequest(nextBatch, project, config, timeout); err != nil {
return err
}
log.Printf("[DEBUG] Finished enabling next batch of %d project services: %+v", len(nextBatch), nextBatch)
}

log.Printf("[DEBUG] Verifying that all services are enabled")
return waitForServiceUsageEnabledServices(services, project, config, timeout)
}

// waitForServiceUsageEnabledServices doesn't resend enable requests - it just
// waits for service enablement status to propagate. Essentially, it waits until
// all services show up as enabled when listing services on the project.
func waitForServiceUsageEnabledServices(services []string, project string, config *Config, timeout time.Duration) error {
missing := make([]string, 0, len(services))
delay := time.Duration(0)
interval := time.Second
err := retryTimeDuration(func() error {
// Get the list of services that are enabled on the project
enabledServices, err := listCurrentlyEnabledServices(project, config, timeout)
if err != nil {
return err
}

missing := make([]string, 0, len(services))
for _, s := range services {
if _, ok := enabledServices[s]; !ok {
missing = append(missing, s)
}
}
if len(missing) > 0 {
log.Printf("[DEBUG] waiting %v before reading project %s services...", delay, project)
time.Sleep(delay)
delay += interval
interval += delay

// Spoof a googleapi Error so retryTime will try again
return &googleapi.Error{
Code: 503,
Message: fmt.Sprintf("The service(s) %q are still being enabled for project %s. This isn't a real API error, this is just eventual consistency.", missing, project),
}
}
return nil
}, timeout)
if err != nil {
return errwrap.Wrap(err, fmt.Errorf("failed to enable some service(s) %q for project %s", missing, project))
}
return nil
}
29 changes: 29 additions & 0 deletions third_party/terraform/resources/resource_google_project_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import (
"time"
)

var ignoredProjectServices = []string{"dataproc-control.googleapis.com", "source.googleapis.com", "stackdriverprovisioning.googleapis.com"}

// These services can only be enabled as a side-effect of enabling other services,
// so don't bother storing them in the config or using them for diffing.
var ignoredProjectServicesSet = golangSetFromStringSlice(ignoredProjectServices)

func resourceGoogleProjectService() *schema.Resource {
return &schema.Resource{
Create: resourceGoogleProjectServiceCreate,
Expand Down Expand Up @@ -171,3 +177,26 @@ func isServiceEnabled(project, serviceName string, config *Config) (bool, error)
}
return srv.State == "ENABLED", nil
}

// Disables a project service.
func disableServiceUsageProjectService(service, project string, d *schema.ResourceData, config *Config, disableDependentServices bool) error {
err := retryTimeDuration(func() error {
name := fmt.Sprintf("projects/%s/services/%s", project, service)
sop, err := config.clientServiceUsage.Services.Disable(name, &serviceusage.DisableServiceRequest{
DisableDependentServices: disableDependentServices,
}).Do()
if err != nil {
return err
}
// Wait for the operation to complete
waitErr := serviceUsageOperationWait(config, sop, "api to disable")
if waitErr != nil {
return waitErr
}
return nil
}, d.Timeout(schema.TimeoutDelete))
if err != nil {
return fmt.Errorf("Error disabling service %q for project %q: %v", service, project, err)
}
return nil
}
175 changes: 40 additions & 135 deletions third_party/terraform/resources/resource_google_project_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ import (

const maxServiceUsageBatchSize = 20

var ignoredProjectServices = []string{"dataproc-control.googleapis.com", "source.googleapis.com", "stackdriverprovisioning.googleapis.com"}

// These services can only be enabled as a side-effect of enabling other services,
// so don't bother storing them in the config or using them for diffing.
var ignoredProjectServicesSet = golangSetFromStringSlice(ignoredProjectServices)

func resourceGoogleProjectServices() *schema.Resource {
return &schema.Resource{
Create: resourceGoogleProjectServicesCreateUpdate,
Expand Down Expand Up @@ -164,135 +158,6 @@ func setServiceUsageProjectEnabledServices(services []string, project string, d
return nil
}

// Disables a project service.
func disableServiceUsageProjectService(service, project string, d *schema.ResourceData, config *Config, disableDependentServices bool) error {
err := retryTimeDuration(func() error {
name := fmt.Sprintf("projects/%s/services/%s", project, service)
sop, err := config.clientServiceUsage.Services.Disable(name, &serviceusage.DisableServiceRequest{
DisableDependentServices: disableDependentServices,
}).Do()
if err != nil {
return err
}
// Wait for the operation to complete
waitErr := serviceUsageOperationWait(config, sop, "api to disable")
if waitErr != nil {
return waitErr
}
return nil
}, d.Timeout(schema.TimeoutDelete))
if err != nil {
return fmt.Errorf("Error disabling service %q for project %q: %v", service, project, err)
}
return nil
}

// Retrieve a project's services from the API
func listCurrentlyEnabledServices(project string, config *Config, timeout time.Duration) (map[string]struct{}, error) {
// Verify project for services still exists
p, err := config.clientResourceManager.Projects.Get(project).Do()
if err != nil {
return nil, err
}
if p.LifecycleState == "DELETE_REQUESTED" {
// Construct a 404 error for handleNotFoundError
return nil, &googleapi.Error{
Code: 404,
Message: "Project deletion was requested",
}
}

log.Printf("[DEBUG] Listing enabled services for project %s", project)
apiServices := make(map[string]struct{})
err = retryTimeDuration(func() error {
ctx := context.Background()
return config.clientServiceUsage.Services.
List(fmt.Sprintf("projects/%s", project)).
Fields("services/name,nextPageToken").
Filter("state:ENABLED").
Pages(ctx, func(r *serviceusage.ListServicesResponse) error {
for _, v := range r.Services {
// services are returned as "projects/PROJECT/services/NAME"
name := GetResourceNameFromSelfLink(v.Name)
if _, ok := ignoredProjectServicesSet[name]; !ok {
apiServices[name] = struct{}{}
}
}
return nil
})
}, timeout)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Failed to list enabled services for project %s: {{err}}", project), err)
}
return apiServices, nil
}

// Enables services. WARNING: Use BatchRequestEnableServices for better batching if possible.
func enableServiceUsageProjectServices(services []string, project 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
for i := 0; i < len(services); i += maxServiceUsageBatchSize {
j := i + maxServiceUsageBatchSize
if j > len(services) {
j = len(services)
}
nextBatch := services[i:j]
if len(nextBatch) == 0 {
// All batches finished, return.
return nil
}

if err := doEnableServicesRequest(nextBatch, project, config, timeout); err != nil {
return err
}
log.Printf("[DEBUG] Finished enabling next batch of %d project services: %+v", len(nextBatch), nextBatch)
}

log.Printf("[DEBUG] Verifying that all services are enabled")
return waitForServiceUsageEnabledServices(services, project, config, timeout)
}

// waitForServiceUsageEnabledServices doesn't resend enable requests - it just
// waits for service enablement status to propagate. Essentially, it waits until
// all services show up as enabled when listing services on the project.
func waitForServiceUsageEnabledServices(services []string, project string, config *Config, timeout time.Duration) error {
missing := make([]string, 0, len(services))
delay := time.Duration(0)
interval := time.Second
err := retryTimeDuration(func() error {
// Get the list of services that are enabled on the project
enabledServices, err := listCurrentlyEnabledServices(project, config, timeout)
if err != nil {
return err
}

missing := make([]string, 0, len(services))
for _, s := range services {
if _, ok := enabledServices[s]; !ok {
missing = append(missing, s)
}
}
if len(missing) > 0 {
log.Printf("[DEBUG] waiting %v before reading project %s services...", delay, project)
time.Sleep(delay)
delay += interval
interval += delay

// Spoof a googleapi Error so retryTime will try again
return &googleapi.Error{
Code: 503,
Message: fmt.Sprintf("The service(s) %q are still being enabled for project %s. This isn't a real API error, this is just eventual consistency.", missing, project),
}
}
return nil
}, timeout)
if err != nil {
return errwrap.Wrap(err, fmt.Errorf("failed to enable some service(s) %q for project %s", missing, project))
}
return nil
}

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

Expand Down Expand Up @@ -355,3 +220,43 @@ func expandServiceUsageProjectServicesServices(v interface{}, d TerraformResourc
}
return convertStringArr(v.(*schema.Set).List()), nil
}

// Retrieve a project's services from the API
func listCurrentlyEnabledServices(project string, config *Config, timeout time.Duration) (map[string]struct{}, error) {
Copy link
Member

Choose a reason for hiding this comment

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

We expect this to move within the same file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I moved it and then moved it back, and mistargeted where it was supposed to go. NBD either way.

// Verify project for services still exists
p, err := config.clientResourceManager.Projects.Get(project).Do()
if err != nil {
return nil, err
}
if p.LifecycleState == "DELETE_REQUESTED" {
// Construct a 404 error for handleNotFoundError
return nil, &googleapi.Error{
Code: 404,
Message: "Project deletion was requested",
}
}

log.Printf("[DEBUG] Listing enabled services for project %s", project)
apiServices := make(map[string]struct{})
err = retryTimeDuration(func() error {
ctx := context.Background()
return config.clientServiceUsage.Services.
List(fmt.Sprintf("projects/%s", project)).
Fields("services/name,nextPageToken").
Filter("state:ENABLED").
Pages(ctx, func(r *serviceusage.ListServicesResponse) error {
for _, v := range r.Services {
// services are returned as "projects/PROJECT/services/NAME"
name := GetResourceNameFromSelfLink(v.Name)
if _, ok := ignoredProjectServicesSet[name]; !ok {
apiServices[name] = struct{}{}
}
}
return nil
})
}, timeout)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Failed to list enabled services for project %s: {{err}}", project), err)
}
return apiServices, nil
}
Loading