Skip to content

Commit

Permalink
google_project_services is deprecated - use google_project_service (#…
Browse files Browse the repository at this point in the history
…4599)

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored and nat-henderson committed Oct 2, 2019
1 parent 95e4db5 commit e116744
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 232 deletions.
67 changes: 67 additions & 0 deletions google/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 google/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 google/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) {
// 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

0 comments on commit e116744

Please sign in to comment.