From 56481d831655bd1f71b2f36bcd917a894748faa8 Mon Sep 17 00:00:00 2001 From: The Magician Date: Thu, 17 May 2018 16:33:30 -0700 Subject: [PATCH] Add liens resource. (#1484) --- google/provider.go | 1 + google/provider_resourcemanager_gen.go | 21 ++ google/resource_compute_backend_bucket.go | 4 + google/resource_compute_global_address.go | 4 + google/resource_compute_http_health_check.go | 4 + google/resource_compute_https_health_check.go | 4 + google/resource_compute_ssl_policy.go | 4 + google/resource_compute_target_http_proxy.go | 4 + google/resource_compute_target_https_proxy.go | 4 + google/resource_compute_target_ssl_proxy.go | 4 + google/resource_compute_target_tcp_proxy.go | 4 + google/resource_compute_vpn_gateway.go | 4 + google/resource_resourcemanager_lien.go | 336 ++++++++++++++++++ google/resource_resourcemanager_lien_test.go | 108 ++++++ google/transport.go | 29 +- google/utils.go | 12 +- .../docs/r/resourcemanager_lien.html.markdown | 103 ++++++ 17 files changed, 633 insertions(+), 17 deletions(-) create mode 100644 google/provider_resourcemanager_gen.go create mode 100644 google/resource_resourcemanager_lien.go create mode 100644 google/resource_resourcemanager_lien_test.go create mode 100644 website/docs/r/resourcemanager_lien.html.markdown diff --git a/google/provider.go b/google/provider.go index 4966131b..1e2e5ccc 100644 --- a/google/provider.go +++ b/google/provider.go @@ -95,6 +95,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: mergeResourceMaps( GeneratedComputeResourcesMap, + GeneratedResourceManagerResourcesMap, map[string]*schema.Resource{ "google_bigquery_dataset": resourceBigQueryDataset(), "google_bigquery_table": resourceBigQueryTable(), diff --git a/google/provider_resourcemanager_gen.go b/google/provider_resourcemanager_gen.go new file mode 100644 index 00000000..a2a47473 --- /dev/null +++ b/google/provider_resourcemanager_gen.go @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import "github.com/hashicorp/terraform/helper/schema" + +var GeneratedResourceManagerResourcesMap = map[string]*schema.Resource{ + "google_resource_manager_lien": resourceResourceManagerLien(), +} diff --git a/google/resource_compute_backend_bucket.go b/google/resource_compute_backend_bucket.go index 19b18e7a..02566cfb 100644 --- a/google/resource_compute_backend_bucket.go +++ b/google/resource_compute_backend_bucket.go @@ -143,6 +143,8 @@ func resourceComputeBackendBucketCreate(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error waiting to create BackendBucket: %s", waitErr) } + log.Printf("[DEBUG] Finished creating BackendBucket %q: %#v", d.Id(), res) + return resourceComputeBackendBucketRead(d, meta) } @@ -163,6 +165,7 @@ func resourceComputeBackendBucketRead(d *schema.ResourceData, meta interface{}) if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeBackendBucket %q", d.Id())) } + if err := d.Set("bucket_name", flattenComputeBackendBucketBucketName(res["bucketName"])); err != nil { return fmt.Errorf("Error reading BackendBucket: %s", err) } @@ -282,6 +285,7 @@ func resourceComputeBackendBucketDelete(d *schema.ResourceData, meta interface{} return err } + log.Printf("[DEBUG] Finished deleting BackendBucket %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_global_address.go b/google/resource_compute_global_address.go index 5f7d4045..2861ea1e 100644 --- a/google/resource_compute_global_address.go +++ b/google/resource_compute_global_address.go @@ -139,6 +139,8 @@ func resourceComputeGlobalAddressCreate(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error waiting to create GlobalAddress: %s", waitErr) } + log.Printf("[DEBUG] Finished creating GlobalAddress %q: %#v", d.Id(), res) + return resourceComputeGlobalAddressRead(d, meta) } @@ -159,6 +161,7 @@ func resourceComputeGlobalAddressRead(d *schema.ResourceData, meta interface{}) if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeGlobalAddress %q", d.Id())) } + if err := d.Set("address", flattenComputeGlobalAddressAddress(res["address"])); err != nil { return fmt.Errorf("Error reading GlobalAddress: %s", err) } @@ -217,6 +220,7 @@ func resourceComputeGlobalAddressDelete(d *schema.ResourceData, meta interface{} return err } + log.Printf("[DEBUG] Finished deleting GlobalAddress %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_http_health_check.go b/google/resource_compute_http_health_check.go index 50265b40..3ca4fb86 100644 --- a/google/resource_compute_http_health_check.go +++ b/google/resource_compute_http_health_check.go @@ -194,6 +194,8 @@ func resourceComputeHttpHealthCheckCreate(d *schema.ResourceData, meta interface return fmt.Errorf("Error waiting to create HttpHealthCheck: %s", waitErr) } + log.Printf("[DEBUG] Finished creating HttpHealthCheck %q: %#v", d.Id(), res) + return resourceComputeHttpHealthCheckRead(d, meta) } @@ -214,6 +216,7 @@ func resourceComputeHttpHealthCheckRead(d *schema.ResourceData, meta interface{} if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeHttpHealthCheck %q", d.Id())) } + if err := d.Set("check_interval_sec", flattenComputeHttpHealthCheckCheckIntervalSec(res["checkIntervalSec"])); err != nil { return fmt.Errorf("Error reading HttpHealthCheck: %s", err) } @@ -373,6 +376,7 @@ func resourceComputeHttpHealthCheckDelete(d *schema.ResourceData, meta interface return err } + log.Printf("[DEBUG] Finished deleting HttpHealthCheck %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_https_health_check.go b/google/resource_compute_https_health_check.go index ed8dece1..8ece63e2 100644 --- a/google/resource_compute_https_health_check.go +++ b/google/resource_compute_https_health_check.go @@ -194,6 +194,8 @@ func resourceComputeHttpsHealthCheckCreate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error waiting to create HttpsHealthCheck: %s", waitErr) } + log.Printf("[DEBUG] Finished creating HttpsHealthCheck %q: %#v", d.Id(), res) + return resourceComputeHttpsHealthCheckRead(d, meta) } @@ -214,6 +216,7 @@ func resourceComputeHttpsHealthCheckRead(d *schema.ResourceData, meta interface{ if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeHttpsHealthCheck %q", d.Id())) } + if err := d.Set("check_interval_sec", flattenComputeHttpsHealthCheckCheckIntervalSec(res["checkIntervalSec"])); err != nil { return fmt.Errorf("Error reading HttpsHealthCheck: %s", err) } @@ -373,6 +376,7 @@ func resourceComputeHttpsHealthCheckDelete(d *schema.ResourceData, meta interfac return err } + log.Printf("[DEBUG] Finished deleting HttpsHealthCheck %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_ssl_policy.go b/google/resource_compute_ssl_policy.go index 573ab6fc..fc15df2c 100644 --- a/google/resource_compute_ssl_policy.go +++ b/google/resource_compute_ssl_policy.go @@ -195,6 +195,8 @@ func resourceComputeSslPolicyCreate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error waiting to create SslPolicy: %s", waitErr) } + log.Printf("[DEBUG] Finished creating SslPolicy %q: %#v", d.Id(), res) + return resourceComputeSslPolicyRead(d, meta) } @@ -215,6 +217,7 @@ func resourceComputeSslPolicyRead(d *schema.ResourceData, meta interface{}) erro if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeSslPolicy %q", d.Id())) } + if err := d.Set("creation_timestamp", flattenComputeSslPolicyCreationTimestamp(res["creationTimestamp"])); err != nil { return fmt.Errorf("Error reading SslPolicy: %s", err) } @@ -349,6 +352,7 @@ func resourceComputeSslPolicyDelete(d *schema.ResourceData, meta interface{}) er return err } + log.Printf("[DEBUG] Finished deleting SslPolicy %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_target_http_proxy.go b/google/resource_compute_target_http_proxy.go index 23db30a8..9b441705 100644 --- a/google/resource_compute_target_http_proxy.go +++ b/google/resource_compute_target_http_proxy.go @@ -140,6 +140,8 @@ func resourceComputeTargetHttpProxyCreate(d *schema.ResourceData, meta interface return fmt.Errorf("Error waiting to create TargetHttpProxy: %s", waitErr) } + log.Printf("[DEBUG] Finished creating TargetHttpProxy %q: %#v", d.Id(), res) + return resourceComputeTargetHttpProxyRead(d, meta) } @@ -160,6 +162,7 @@ func resourceComputeTargetHttpProxyRead(d *schema.ResourceData, meta interface{} if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeTargetHttpProxy %q", d.Id())) } + if err := d.Set("creation_timestamp", flattenComputeTargetHttpProxyCreationTimestamp(res["creationTimestamp"])); err != nil { return fmt.Errorf("Error reading TargetHttpProxy: %s", err) } @@ -272,6 +275,7 @@ func resourceComputeTargetHttpProxyDelete(d *schema.ResourceData, meta interface return err } + log.Printf("[DEBUG] Finished deleting TargetHttpProxy %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_target_https_proxy.go b/google/resource_compute_target_https_proxy.go index cf9f9e7e..7b99c164 100644 --- a/google/resource_compute_target_https_proxy.go +++ b/google/resource_compute_target_https_proxy.go @@ -163,6 +163,8 @@ func resourceComputeTargetHttpsProxyCreate(d *schema.ResourceData, meta interfac return fmt.Errorf("Error waiting to create TargetHttpsProxy: %s", waitErr) } + log.Printf("[DEBUG] Finished creating TargetHttpsProxy %q: %#v", d.Id(), res) + return resourceComputeTargetHttpsProxyRead(d, meta) } @@ -183,6 +185,7 @@ func resourceComputeTargetHttpsProxyRead(d *schema.ResourceData, meta interface{ if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeTargetHttpsProxy %q", d.Id())) } + if err := d.Set("creation_timestamp", flattenComputeTargetHttpsProxyCreationTimestamp(res["creationTimestamp"])); err != nil { return fmt.Errorf("Error reading TargetHttpsProxy: %s", err) } @@ -367,6 +370,7 @@ func resourceComputeTargetHttpsProxyDelete(d *schema.ResourceData, meta interfac return err } + log.Printf("[DEBUG] Finished deleting TargetHttpsProxy %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_target_ssl_proxy.go b/google/resource_compute_target_ssl_proxy.go index 3f698a4d..6dbe1d45 100644 --- a/google/resource_compute_target_ssl_proxy.go +++ b/google/resource_compute_target_ssl_proxy.go @@ -166,6 +166,8 @@ func resourceComputeTargetSslProxyCreate(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error waiting to create TargetSslProxy: %s", waitErr) } + log.Printf("[DEBUG] Finished creating TargetSslProxy %q: %#v", d.Id(), res) + return resourceComputeTargetSslProxyRead(d, meta) } @@ -186,6 +188,7 @@ func resourceComputeTargetSslProxyRead(d *schema.ResourceData, meta interface{}) if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeTargetSslProxy %q", d.Id())) } + if err := d.Set("creation_timestamp", flattenComputeTargetSslProxyCreationTimestamp(res["creationTimestamp"])); err != nil { return fmt.Errorf("Error reading TargetSslProxy: %s", err) } @@ -370,6 +373,7 @@ func resourceComputeTargetSslProxyDelete(d *schema.ResourceData, meta interface{ return err } + log.Printf("[DEBUG] Finished deleting TargetSslProxy %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_target_tcp_proxy.go b/google/resource_compute_target_tcp_proxy.go index 146c4660..21cc13c6 100644 --- a/google/resource_compute_target_tcp_proxy.go +++ b/google/resource_compute_target_tcp_proxy.go @@ -152,6 +152,8 @@ func resourceComputeTargetTcpProxyCreate(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error waiting to create TargetTcpProxy: %s", waitErr) } + log.Printf("[DEBUG] Finished creating TargetTcpProxy %q: %#v", d.Id(), res) + return resourceComputeTargetTcpProxyRead(d, meta) } @@ -172,6 +174,7 @@ func resourceComputeTargetTcpProxyRead(d *schema.ResourceData, meta interface{}) if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeTargetTcpProxy %q", d.Id())) } + if err := d.Set("creation_timestamp", flattenComputeTargetTcpProxyCreationTimestamp(res["creationTimestamp"])); err != nil { return fmt.Errorf("Error reading TargetTcpProxy: %s", err) } @@ -320,6 +323,7 @@ func resourceComputeTargetTcpProxyDelete(d *schema.ResourceData, meta interface{ return err } + log.Printf("[DEBUG] Finished deleting TargetTcpProxy %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_compute_vpn_gateway.go b/google/resource_compute_vpn_gateway.go index 24b31d9d..c947cfb6 100644 --- a/google/resource_compute_vpn_gateway.go +++ b/google/resource_compute_vpn_gateway.go @@ -146,6 +146,8 @@ func resourceComputeVpnGatewayCreate(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error waiting to create VpnGateway: %s", waitErr) } + log.Printf("[DEBUG] Finished creating VpnGateway %q: %#v", d.Id(), res) + return resourceComputeVpnGatewayRead(d, meta) } @@ -166,6 +168,7 @@ func resourceComputeVpnGatewayRead(d *schema.ResourceData, meta interface{}) err if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("ComputeVpnGateway %q", d.Id())) } + if err := d.Set("creation_timestamp", flattenComputeVpnGatewayCreationTimestamp(res["creationTimestamp"])); err != nil { return fmt.Errorf("Error reading VpnGateway: %s", err) } @@ -224,6 +227,7 @@ func resourceComputeVpnGatewayDelete(d *schema.ResourceData, meta interface{}) e return err } + log.Printf("[DEBUG] Finished deleting VpnGateway %q: %#v", d.Id(), res) return nil } diff --git a/google/resource_resourcemanager_lien.go b/google/resource_resourcemanager_lien.go new file mode 100644 index 00000000..aa00ec9d --- /dev/null +++ b/google/resource_resourcemanager_lien.go @@ -0,0 +1,336 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strconv" + "strings" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceResourceManagerLien() *schema.Resource { + return &schema.Resource{ + Create: resourceResourceManagerLienCreate, + Read: resourceResourceManagerLienRead, + Delete: resourceResourceManagerLienDelete, + + Importer: &schema.ResourceImporter{ + State: resourceResourceManagerLienImport, + }, + + Schema: map[string]*schema.Schema{ + "origin": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "parent": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "reason": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "restrictions": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceResourceManagerLienCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + reasonProp, err := expandResourceManagerLienReason(d.Get("reason"), d, config) + if err != nil { + return err + } + originProp, err := expandResourceManagerLienOrigin(d.Get("origin"), d, config) + if err != nil { + return err + } + parentProp, err := expandResourceManagerLienParent(d.Get("parent"), d, config) + if err != nil { + return err + } + restrictionsProp, err := expandResourceManagerLienRestrictions(d.Get("restrictions"), d, config) + if err != nil { + return err + } + + obj := map[string]interface{}{ + "reason": reasonProp, + "origin": originProp, + "parent": parentProp, + "restrictions": restrictionsProp, + } + + url, err := replaceVars(d, config, "https://cloudresourcemanager.googleapis.com/v1/liens") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Lien: %#v", obj) + res, err := Post(config, url, obj) + if err != nil { + return fmt.Errorf("Error creating Lien: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Lien %q: %#v", d.Id(), res) + + // This resource is unusual - instead of returning an Operation from + // Create, it returns the created object itself. We don't parse + // any of the values there, preferring to centralize that logic in + // Read(). In this resource, Read is also unusual - it requires + // us to know the server-side generated name of the object we're + // trying to fetch, and the only way to know that is to capture + // it here. The following two lines do that. + d.SetId(flattenResourceManagerLienName(res["name"]).(string)) + d.Set("name", flattenResourceManagerLienName(res["name"])) + + return resourceResourceManagerLienRead(d, meta) +} + +func resourceResourceManagerLienRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "https://cloudresourcemanager.googleapis.com/v1/liens?parent={{parent}}") + if err != nil { + return err + } + + res, err := Get(config, url) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("ResourceManagerLien %q", d.Id())) + } + + // Extract the object we're interested in from the list response. + itemsList_ := res["liens"] + var itemsList []interface{} + if itemsList_ != nil { + itemsList = itemsList_.([]interface{}) + } + listObj := make([]map[string]interface{}, len(itemsList)) + for i, item := range itemsList { + listObj[i] = item.(map[string]interface{}) + } + res = nil + for _, item := range listObj { + thisName := d.Get("name") + thatName := flattenResourceManagerLienName(item["name"]) + log.Printf("[DEBUG] Checking equality of %#v, %#v", thatName, thisName) + if !reflect.DeepEqual(thatName, thisName) { + continue + } + res = item + break + } + if res == nil { + // Object isn't there any more - remove it from the state. + log.Printf("[DEBUG] Removing ResourceManagerLien because it couldn't be matched.") + d.SetId("") + return nil + } + res, err = resourceResourceManagerLienDecoder(d, meta, res) + if err != nil { + return err + } + + if err := d.Set("name", flattenResourceManagerLienName(res["name"])); err != nil { + return fmt.Errorf("Error reading Lien: %s", err) + } + if err := d.Set("reason", flattenResourceManagerLienReason(res["reason"])); err != nil { + return fmt.Errorf("Error reading Lien: %s", err) + } + if err := d.Set("origin", flattenResourceManagerLienOrigin(res["origin"])); err != nil { + return fmt.Errorf("Error reading Lien: %s", err) + } + if err := d.Set("create_time", flattenResourceManagerLienCreateTime(res["createTime"])); err != nil { + return fmt.Errorf("Error reading Lien: %s", err) + } + if err := d.Set("parent", flattenResourceManagerLienParent(res["parent"])); err != nil { + return fmt.Errorf("Error reading Lien: %s", err) + } + if err := d.Set("restrictions", flattenResourceManagerLienRestrictions(res["restrictions"])); err != nil { + return fmt.Errorf("Error reading Lien: %s", err) + } + + return nil +} + +func resourceResourceManagerLienDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "https://cloudresourcemanager.googleapis.com/v1/liens?parent={{parent}}") + if err != nil { + return err + } + + url, err = replaceVars(d, config, "https://cloudresourcemanager.googleapis.com/v1/liens/{{name}}") + if err != nil { + return err + } + log.Printf("[DEBUG] Deleting Lien %q", d.Id()) + res, err := Delete(config, url) + if err != nil { + return fmt.Errorf("Error deleting Lien %q: %s", d.Id(), err) + } + + log.Printf("[DEBUG] Finished deleting Lien %q: %#v", d.Id(), res) + return nil +} + +func resourceResourceManagerLienImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + parseImportId([]string{"(?P[^/]+)/(?P[^/]+)"}, d, config) + + // Replace import id for the resource id + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + parent, err := replaceVars(d, config, "projects/{{parent}}") + if err != nil { + return nil, err + } + d.Set("parent", parent) + + return []*schema.ResourceData{d}, nil +} + +func flattenResourceManagerLienName(v interface{}) interface{} { + return NameFromSelfLinkStateFunc(v) +} + +func flattenResourceManagerLienReason(v interface{}) interface{} { + return v +} + +func flattenResourceManagerLienOrigin(v interface{}) interface{} { + return v +} + +func flattenResourceManagerLienCreateTime(v interface{}) interface{} { + return v +} + +func flattenResourceManagerLienParent(v interface{}) interface{} { + return v +} + +func flattenResourceManagerLienRestrictions(v interface{}) interface{} { + return v +} + +func expandResourceManagerLienReason(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandResourceManagerLienOrigin(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandResourceManagerLienParent(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandResourceManagerLienRestrictions(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func resourceResourceManagerLienDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + // The problem we're trying to solve here is that this property is a Project, + // and there are a lot of ways to specify a Project, including the ID vs + // Number, which is something that we can't address in a diffsuppress. + // Since we can't enforce a particular method of entering the project, + // we're just going to have to use whatever the user entered, whether + // it's project/projectName, project/12345, projectName, or 12345. + // The normal behavior of this method would be 'return res' - and that's + // what we'll fall back to if any of our conditions aren't met. Those + // conditions are: + // 1) if the new or old values contain '/', the prefix of that is 'projects'. + // 2) if either is non-numeric, a project with that ID exists. + // 3) the project IDs represented by both the new and old values are the same. + config := meta.(*Config) + new := res["parent"].(string) + old := d.Get("parent").(string) + if strings.HasPrefix(new, "projects/") { + new = strings.Split(new, "/")[1] + } + if strings.HasPrefix(old, "projects/") { + old = strings.Split(old, "/")[1] + } + log.Printf("[DEBUG] Trying to figure out whether to use %s or %s", old, new) + // If there's still a '/' in there, the value must not be a project ID. + if strings.Contains(old, "/") || strings.Contains(new, "/") { + return res, nil + } + // If 'old' isn't entirely numeric, let's assume it's a project ID. + // If it's a project ID + var oldProjId int64 + var newProjId int64 + if oldVal, err := strconv.ParseInt(old, 10, 64); err == nil { + log.Printf("[DEBUG] The old value was a real number: %d", oldVal) + oldProjId = oldVal + } else { + pOld, err := config.clientResourceManager.Projects.Get(old).Do() + if err != nil { + return res, nil + } + oldProjId = pOld.ProjectNumber + } + if newVal, err := strconv.ParseInt(new, 10, 64); err == nil { + log.Printf("[DEBUG] The new value was a real number: %d", newVal) + newProjId = newVal + } else { + pNew, err := config.clientResourceManager.Projects.Get(new).Do() + if err != nil { + return res, nil + } + newProjId = pNew.ProjectNumber + } + if newProjId == oldProjId { + res["parent"] = d.Get("parent") + } + return res, nil +} diff --git a/google/resource_resourcemanager_lien_test.go b/google/resource_resourcemanager_lien_test.go new file mode 100644 index 00000000..8e8ff2c9 --- /dev/null +++ b/google/resource_resourcemanager_lien_test.go @@ -0,0 +1,108 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + resourceManager "google.golang.org/api/cloudresourcemanager/v1" +) + +func TestAccResourceManagerLien_basic(t *testing.T) { + t.Parallel() + + projectName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + org := getTestOrgFromEnv(t) + var lien resourceManager.Lien + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckResourceManagerLienDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccResourceManagerLien_basic(projectName, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourceManagerLienExists( + "google_resource_manager_lien.lien", projectName, &lien), + ), + }, + resource.TestStep{ + ResourceName: "google_resource_manager_lien.lien", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(_ *terraform.State) (string, error) { + // This has to be a function to close over lien.Name, which is necessary + // because Name is a Computed attribute. + return fmt.Sprintf("%s/%s", + projectName, + strings.Split(lien.Name, "/")[1]), nil + }, + }, + }, + }) +} + +func testAccCheckResourceManagerLienExists(n, projectName string, lien *resourceManager.Lien) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientResourceManager.Liens.List().Parent(fmt.Sprintf("projects/%s", projectName)).Do() + if err != nil { + return err + } + if len(found.Liens) != 1 { + return fmt.Errorf("Lien %s not found", rs.Primary.ID) + } + + *lien = *found.Liens[0] + + return nil + } +} + +func testAccCheckResourceManagerLienDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_resource_manager_lien" { + continue + } + + _, err := config.clientResourceManager.Liens.List().Parent(fmt.Sprintf("projects/%s", rs.Primary.Attributes["parent"])).Do() + if err == nil { + return fmt.Errorf("Lien %s still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccResourceManagerLien_basic(projectName, org string) string { + return fmt.Sprintf(` +resource "google_project" "project" { + project_id = "%s" + name = "some test project" + org_id = "%s" +} + +resource "google_resource_manager_lien" "lien" { + parent = "projects/${google_project.project.project_id}" + restrictions = ["resourcemanager.projects.delete"] + origin = "something" + reason = "something else" +} +`, projectName, org) +} diff --git a/google/transport.go b/google/transport.go index ba7d80ca..32a4765f 100644 --- a/google/transport.go +++ b/google/transport.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "net/http" + "net/url" "reflect" "regexp" "strings" @@ -75,23 +76,23 @@ func isEmptyValue(v reflect.Value) bool { return false } -func Post(config *Config, url string, body map[string]interface{}) (map[string]interface{}, error) { - return sendRequest(config, "POST", url, body) +func Post(config *Config, rawurl string, body map[string]interface{}) (map[string]interface{}, error) { + return sendRequest(config, "POST", rawurl, body) } -func Get(config *Config, url string) (map[string]interface{}, error) { - return sendRequest(config, "GET", url, nil) +func Get(config *Config, rawurl string) (map[string]interface{}, error) { + return sendRequest(config, "GET", rawurl, nil) } -func Put(config *Config, url string, body map[string]interface{}) (map[string]interface{}, error) { - return sendRequest(config, "PUT", url, body) +func Put(config *Config, rawurl string, body map[string]interface{}) (map[string]interface{}, error) { + return sendRequest(config, "PUT", rawurl, body) } -func Delete(config *Config, url string) (map[string]interface{}, error) { - return sendRequest(config, "DELETE", url, nil) +func Delete(config *Config, rawurl string) (map[string]interface{}, error) { + return sendRequest(config, "DELETE", rawurl, nil) } -func sendRequest(config *Config, method, url string, body map[string]interface{}) (map[string]interface{}, error) { +func sendRequest(config *Config, method, rawurl string, body map[string]interface{}) (map[string]interface{}, error) { reqHeaders := make(http.Header) reqHeaders.Set("User-Agent", config.userAgent) reqHeaders.Set("Content-Type", "application/json") @@ -105,7 +106,15 @@ func sendRequest(config *Config, method, url string, body map[string]interface{} } } - req, err := http.NewRequest(method, url+"?alt=json", &buf) + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + q := u.Query() + q.Set("alt", "json") + u.RawQuery = q.Encode() + + req, err := http.NewRequest(method, u.String(), &buf) if err != nil { return nil, err } diff --git a/google/utils.go b/google/utils.go index bf40b722..5f35a80f 100644 --- a/google/utils.go +++ b/google/utils.go @@ -298,15 +298,13 @@ func mergeSchemas(a, b map[string]*schema.Schema) map[string]*schema.Schema { return merged } -func mergeResourceMaps(a, b map[string]*schema.Resource) map[string]*schema.Resource { +func mergeResourceMaps(ms ...map[string]*schema.Resource) map[string]*schema.Resource { merged := make(map[string]*schema.Resource) - for k, v := range a { - merged[k] = v - } - - for k, v := range b { - merged[k] = v + for _, m := range ms { + for k, v := range m { + merged[k] = v + } } return merged diff --git a/website/docs/r/resourcemanager_lien.html.markdown b/website/docs/r/resourcemanager_lien.html.markdown new file mode 100644 index 00000000..93cd0082 --- /dev/null +++ b/website/docs/r/resourcemanager_lien.html.markdown @@ -0,0 +1,103 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +layout: "google" +page_title: "Google: google_resourcemanager_lien" +sidebar_current: "docs-google-resourcemanager-lien" +description: |- + A Lien represents an encumbrance on the actions that can be performed on a resource. +--- + +# google\_resourcemanager\_lien + +A Lien represents an encumbrance on the actions that can be performed on a resource. + + +## Example Usage + +```hcl +resource "random_id" "r" { + byte_length = 8 +} + +resource "google_project" "project" { + project_id = "project-${random_id.r.hex}" + name = "A very important project!" +} + +resource "google_resourcemanager_lien" "lien" { + parent = "projects/${google_project.project.number}" + restrictions = ["resourcemanager.projects.delete"] + origin = "machine-readable-explanation" + reason = "This project is very important to me!" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `reason` - + (Required) + Concise user-visible strings indicating why an action cannot be performed + on a resource. Maximum length of 200 characters. +* `origin` - + (Required) + A stable, user-visible/meaningful string identifying the origin + of the Lien, intended to be inspected programmatically. Maximum length of + 200 characters. +* `parent` - + (Required) + A reference to the resource this Lien is attached to. + The server will validate the parent against those for which Liens are supported. + Since a variety of objects can have Liens against them, you must provide the type + prefix (e.g. "projects/my-project-name"). +* `restrictions` - + (Required) + The types of operations which should be blocked as a result of this Lien. + Each value should correspond to an IAM permission. The server will validate + the permissions against those for which Liens are supported. An empty + list is meaningless and will be rejected. + e.g. ['resourcemanager.projects.delete'] + + +- - - + + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `name` - + A system-generated unique identifier for this Lien. +* `create_time` - + Time of creation + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +Lien can be imported using any of these accepted formats: + +``` +$ terraform import google_resourcemanager_lien.default {{parent}}/{{name}} +```