terraform-provider-google/google/resource_google_project_services.go
2017-06-09 16:50:30 +00:00

230 lines
6.2 KiB
Go

package google
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/servicemanagement/v1"
)
func resourceGoogleProjectServices() *schema.Resource {
return &schema.Resource{
Create: resourceGoogleProjectServicesCreate,
Read: resourceGoogleProjectServicesRead,
Update: resourceGoogleProjectServicesUpdate,
Delete: resourceGoogleProjectServicesDelete,
Schema: map[string]*schema.Schema{
"project": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"services": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}
// 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 ignore = map[string]struct{}{
"containeranalysis.googleapis.com": struct{}{},
"dataproc-control.googleapis.com": struct{}{},
"source.googleapis.com": struct{}{},
}
func resourceGoogleProjectServicesCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid := d.Get("project").(string)
// Get services from config
cfgServices := getConfigServices(d)
// Get services from API
apiServices, err := getApiServices(pid, config)
if err != nil {
return fmt.Errorf("Error creating services: %v", err)
}
// This call disables any APIs that aren't defined in cfgServices,
// and enables all of those that are
err = reconcileServices(cfgServices, apiServices, config, pid)
if err != nil {
return fmt.Errorf("Error creating services: %v", err)
}
d.SetId(pid)
return resourceGoogleProjectServicesRead(d, meta)
}
func resourceGoogleProjectServicesRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
services, err := getApiServices(d.Id(), config)
if err != nil {
return err
}
d.Set("services", services)
return nil
}
func resourceGoogleProjectServicesUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG]: Updating google_project_services")
config := meta.(*Config)
pid := d.Get("project").(string)
// Get services from config
cfgServices := getConfigServices(d)
// Get services from API
apiServices, err := getApiServices(pid, config)
if err != nil {
return fmt.Errorf("Error updating services: %v", err)
}
// This call disables any APIs that aren't defined in cfgServices,
// and enables all of those that are
err = reconcileServices(cfgServices, apiServices, config, pid)
if err != nil {
return fmt.Errorf("Error updating services: %v", err)
}
return resourceGoogleProjectServicesRead(d, meta)
}
func resourceGoogleProjectServicesDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG]: Deleting google_project_services")
config := meta.(*Config)
services := resourceServices(d)
for _, s := range services {
disableService(s, d.Id(), config)
}
d.SetId("")
return nil
}
// This function ensures that the services enabled for a project exactly match that
// in a config by disabling any services that are returned by the API but not present
// in the config
func reconcileServices(cfgServices, apiServices []string, config *Config, pid string) error {
// Helper to convert slice to map
m := func(vals []string) map[string]struct{} {
sm := make(map[string]struct{})
for _, s := range vals {
sm[s] = struct{}{}
}
return sm
}
cfgMap := m(cfgServices)
apiMap := m(apiServices)
for k, _ := range apiMap {
if _, ok := cfgMap[k]; !ok {
// The service in the API is not in the config; disable it.
err := disableService(k, pid, config)
if err != nil {
return err
}
} else {
// The service exists in the config and the API, so we don't need
// to re-enable it
delete(cfgMap, k)
}
}
for k, _ := range cfgMap {
err := enableService(k, pid, config)
if err != nil {
return err
}
}
return nil
}
// Retrieve services defined in a config
func getConfigServices(d *schema.ResourceData) (services []string) {
if v, ok := d.GetOk("services"); ok {
for _, svc := range v.(*schema.Set).List() {
services = append(services, svc.(string))
}
}
return
}
// Retrieve a project's services from the API
func getApiServices(pid string, config *Config) ([]string, error) {
apiServices := make([]string, 0)
// Get services from the API
token := ""
for paginate := true; paginate; {
svcResp, err := config.clientServiceMan.Services.List().ConsumerId("project:" + pid).PageToken(token).Do()
if err != nil {
return apiServices, err
}
for _, v := range svcResp.Services {
if _, ok := ignore[v.ServiceName]; !ok {
apiServices = append(apiServices, v.ServiceName)
}
}
token = svcResp.NextPageToken
paginate = token != ""
}
return apiServices, nil
}
func enableService(s, pid string, config *Config) error {
esr := newEnableServiceRequest(pid)
sop, err := config.clientServiceMan.Services.Enable(s, esr).Do()
if err != nil {
return fmt.Errorf("Error enabling service %q for project %q: %v", s, pid, err)
}
// Wait for the operation to complete
waitErr := serviceManagementOperationWait(config, sop, "api to enable")
if waitErr != nil {
return waitErr
}
return nil
}
func disableService(s, pid string, config *Config) error {
dsr := newDisableServiceRequest(pid)
sop, err := config.clientServiceMan.Services.Disable(s, dsr).Do()
if err != nil {
return fmt.Errorf("Error disabling service %q for project %q: %v", s, pid, err)
}
// Wait for the operation to complete
waitErr := serviceManagementOperationWait(config, sop, "api to disable")
if waitErr != nil {
return waitErr
}
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
if s := d.Get("services"); s != nil {
ss := s.(*schema.Set)
services = make([]string, ss.Len())
for i, v := range ss.List() {
services[i] = v.(string)
}
}
return services
}