Use new serviceusage API for google_project_service[s] (#1522)

* vendor service usage api

* use serviceusage api instead of servicemanagement for project services

* add bigquery-json to test

* add import for project service

* add serviceusage_operation.go
This commit is contained in:
Dana Hoffman 2018-05-22 17:45:22 -07:00 committed by GitHub
parent ed36405b02
commit 20616e424d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 8257 additions and 34 deletions

View File

@ -37,6 +37,7 @@ import (
"google.golang.org/api/redis/v1beta1"
"google.golang.org/api/runtimeconfig/v1beta1"
"google.golang.org/api/servicemanagement/v1"
"google.golang.org/api/serviceusage/v1beta1"
"google.golang.org/api/sourcerepo/v1"
"google.golang.org/api/spanner/v1"
"google.golang.org/api/sqladmin/v1beta4"
@ -78,6 +79,7 @@ type Config struct {
clientSqlAdmin *sqladmin.Service
clientIAM *iam.Service
clientServiceMan *servicemanagement.APIService
clientServiceUsage *serviceusage.APIService
clientBigQuery *bigquery.Service
clientCloudFunctions *cloudfunctions.Service
clientCloudIoT *cloudiot.Service
@ -274,6 +276,13 @@ func (c *Config) loadAndValidate() error {
}
c.clientServiceMan.UserAgent = userAgent
log.Printf("[INFO] Instantiating Google Cloud Service Usage Client...")
c.clientServiceUsage, err = serviceusage.New(client)
if err != nil {
return err
}
c.clientServiceUsage.UserAgent = userAgent
log.Printf("[INFO] Instantiating Google Cloud Billing Client...")
c.clientBilling, err = cloudbilling.New(client)
if err != nil {

View File

@ -15,6 +15,10 @@ func resourceGoogleProjectService() *schema.Resource {
Delete: resourceGoogleProjectServiceDelete,
Update: resourceGoogleProjectServiceUpdate,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"service": {
Type: schema.TypeString,
@ -57,22 +61,17 @@ func resourceGoogleProjectServiceCreate(d *schema.ResourceData, meta interface{}
func resourceGoogleProjectServiceRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
id, err := parseProjectServiceId(d.Id())
if err != nil {
return err
}
services, err := getApiServices(project, config, map[string]struct{}{})
services, err := getApiServices(id.project, config, map[string]struct{}{})
if err != nil {
return err
}
d.Set("project", project)
d.Set("project", id.project)
for _, s := range services {
if s == id.service {
@ -94,17 +93,13 @@ func resourceGoogleProjectServiceDelete(d *schema.ResourceData, meta interface{}
d.SetId("")
return nil
}
project, err := getProject(d, config)
if err != nil {
return err
}
id, err := parseProjectServiceId(d.Id())
if err != nil {
return err
}
if err = disableService(id.service, project, config); err != nil {
if err = disableService(id.service, id.project, config); err != nil {
return fmt.Errorf("Error disabling service: %s", err)
}

View File

@ -26,6 +26,18 @@ func TestAccProjectService_basic(t *testing.T) {
testAccCheckProjectService(services, pid, true),
),
},
resource.TestStep{
ResourceName: "google_project_service.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"disable_on_destroy"},
},
resource.TestStep{
ResourceName: "google_project_service.test2",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"disable_on_destroy"},
},
// Use a separate TestStep rather than a CheckDestroy because we need the project to still exist.
resource.TestStep{
Config: testAccProject_create(pid, pname, org),

View File

@ -3,9 +3,10 @@ package google
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/servicemanagement/v1"
"google.golang.org/api/serviceusage/v1beta1"
)
func resourceGoogleProjectServices() *schema.Resource {
@ -156,11 +157,13 @@ func reconcileServices(cfgServices, apiServices []string, config *Config, pid st
}
}
keys := make([]string, 0, len(cfgMap))
for k, _ := range cfgMap {
err := enableService(k, pid, config)
if err != nil {
return err
}
keys = append(keys, k)
}
err := enableServices(keys, pid, config)
if err != nil {
return err
}
return nil
}
@ -181,13 +184,16 @@ func getApiServices(pid string, config *Config, ignore map[string]struct{}) ([]s
// Get services from the API
token := ""
for paginate := true; paginate; {
svcResp, err := config.clientServiceMan.Services.List().ConsumerId("project:" + pid).PageToken(token).Do()
svcResp, err := config.clientServiceUsage.Services.List("projects/" + pid).PageToken(token).Filter("state:ENABLED").Do()
if err != nil {
return apiServices, err
}
for _, v := range svcResp.Services {
if _, ok := ignore[v.ServiceName]; !ok {
apiServices = append(apiServices, v.ServiceName)
// names are returned as projects/{project-number}/services/{service-name}
nameParts := strings.Split(v.Name, "/")
name := nameParts[len(nameParts)-1]
if _, ok := ignore[name]; !ok {
apiServices = append(apiServices, name)
}
}
token = svcResp.NextPageToken
@ -197,13 +203,27 @@ func getApiServices(pid string, config *Config, ignore map[string]struct{}) ([]s
}
func enableService(s, pid string, config *Config) error {
esr := newEnableServiceRequest(pid)
return enableServices([]string{s}, pid, config)
}
func enableServices(s []string, pid string, config *Config) error {
err := retryTime(func() error {
sop, err := config.clientServiceMan.Services.Enable(s, esr).Do()
var sop *serviceusage.Operation
var err error
if len(s) > 1 {
req := &serviceusage.BatchEnableServicesRequest{ServiceIds: s}
sop, err = config.clientServiceUsage.Services.BatchEnable("projects/"+pid, req).Do()
} else if len(s) == 1 {
name := fmt.Sprintf("projects/%s/services/%s", pid, s[0])
sop, err = config.clientServiceUsage.Services.Enable(name, &serviceusage.EnableServiceRequest{}).Do()
} else {
// No services to enable
return nil
}
if err != nil {
return err
}
_, waitErr := serviceManagementOperationWait(config, sop, "api to enable")
_, waitErr := serviceUsageOperationWait(config, sop, "api to enable")
if waitErr != nil {
return waitErr
}
@ -216,14 +236,14 @@ func enableService(s, pid string, config *Config) error {
}
func disableService(s, pid string, config *Config) error {
dsr := newDisableServiceRequest(pid)
err := retryTime(func() error {
sop, err := config.clientServiceMan.Services.Disable(s, dsr).Do()
name := fmt.Sprintf("projects/%s/services/%s", pid, s)
sop, err := config.clientServiceUsage.Services.Disable(name, &serviceusage.DisableServiceRequest{}).Do()
if err != nil {
return err
}
// Wait for the operation to complete
_, waitErr := serviceManagementOperationWait(config, sop, "api to disable")
_, waitErr := serviceUsageOperationWait(config, sop, "api to disable")
if waitErr != nil {
return waitErr
}
@ -235,14 +255,6 @@ func disableService(s, pid string, config *Config) error {
return nil
}
func newEnableServiceRequest(pid string) *servicemanagement.EnableServiceRequest {
return &servicemanagement.EnableServiceRequest{ConsumerId: "project:" + pid}
}
func newDisableServiceRequest(pid string) *servicemanagement.DisableServiceRequest {
return &servicemanagement.DisableServiceRequest{ConsumerId: "project:" + pid}
}
func resourceServices(d *schema.ResourceData) []string {
// Calculate the tags
var services []string

View File

@ -161,6 +161,7 @@ func TestAccProjectServices_ignoreUnenablableServices(t *testing.T) {
"storage-api.googleapis.com",
"pubsub.googleapis.com",
"oslogin.googleapis.com",
"bigquery-json.googleapis.com",
}
resource.Test(t, resource.TestCase{

View File

@ -0,0 +1,68 @@
package google
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/resource"
"google.golang.org/api/googleapi"
"google.golang.org/api/serviceusage/v1beta1"
)
type serviceUsageOperationWaiter struct {
Service *serviceusage.APIService
Op *serviceusage.Operation
}
func (w *serviceUsageOperationWaiter) RefreshFunc() resource.StateRefreshFunc {
return func() (interface{}, string, error) {
var op *serviceusage.Operation
var err error
op, err = w.Service.Operations.Get(w.Op.Name).Do()
if err != nil {
return nil, "", err
}
log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name)
return op, fmt.Sprint(op.Done), nil
}
}
func (w *serviceUsageOperationWaiter) Conf() *resource.StateChangeConf {
return &resource.StateChangeConf{
Pending: []string{"false"},
Target: []string{"true"},
Refresh: w.RefreshFunc(),
}
}
func serviceUsageOperationWait(config *Config, op *serviceusage.Operation, activity string) (googleapi.RawMessage, error) {
return serviceUsageOperationWaitTime(config, op, activity, 10)
}
func serviceUsageOperationWaitTime(config *Config, op *serviceusage.Operation, activity string, timeoutMin int) (googleapi.RawMessage, error) {
w := &serviceUsageOperationWaiter{
Service: config.clientServiceUsage,
Op: op,
}
state := w.Conf()
state.Delay = 10 * time.Second
state.Timeout = time.Duration(timeoutMin) * time.Minute
state.MinTimeout = 2 * time.Second
opRaw, err := state.WaitForState()
if err != nil {
return nil, fmt.Errorf("Error waiting for %s: %s", activity, err)
}
op = opRaw.(*serviceusage.Operation)
if op.Error != nil {
return nil, fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message)
}
return op.Response, nil
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

6
vendor/vendor.json vendored
View File

@ -1410,6 +1410,12 @@
"revision": "2c6d8d196bf34219b5c8f453dd2ec079b7f1ab0c",
"revisionTime": "2018-01-05T00:03:37Z"
},
{
"checksumSHA1": "Ld0E3QmFqSpUWOWccbP2Sy29YeA=",
"path": "google.golang.org/api/serviceusage/v1beta1",
"revision": "4f7dd2b006a4ffd9fd683c1c734d2fe91ca0ea1c",
"revisionTime": "2018-05-19T00:08:34Z"
},
{
"checksumSHA1": "ZfAt+uQ95CQfoJZEjmuzgx+bAIg=",
"path": "google.golang.org/api/sourcerepo/v1",

View File

@ -34,3 +34,11 @@ The following arguments are supported:
* `project` - (Optional) The project ID. If not provided, the provider project is used.
* `disable_on_destroy` - (Optional) If true, disable the service when the terraform resource is destroyed. Defaults to true. May be useful in the event that a project is long-lived but the infrastructure running in that project changes frequently.
## Import
Project services can be imported using the `project_id` and `service`, e.g.
```
$ terraform import google_project_services.my_project your-project-id/iam.googleapis.com
```