diff --git a/google-beta/access_context_manager_operation.go b/google-beta/access_context_manager_operation.go new file mode 100644 index 0000000000..6e05db20a0 --- /dev/null +++ b/google-beta/access_context_manager_operation.go @@ -0,0 +1,71 @@ +package google + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "google.golang.org/api/accesscontextmanager/v1beta" +) + +type AccessContextManagerOperationWaiter struct { + Service *accesscontextmanager.OperationsService + Op *accesscontextmanager.Operation +} + +func (w *AccessContextManagerOperationWaiter) RefreshFunc() resource.StateRefreshFunc { + return func() (interface{}, string, error) { + op, err := w.Service.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 *AccessContextManagerOperationWaiter) Conf() *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{"false"}, + Target: []string{"true"}, + Refresh: w.RefreshFunc(), + } +} + +func accessContextManagerOperationWait(service *accesscontextmanager.Service, op *accesscontextmanager.Operation, activity string) error { + return accessContextManagerOperationWaitTime(service, op, activity, 4) +} + +func accessContextManagerOperationWaitTime(service *accesscontextmanager.Service, op *accesscontextmanager.Operation, activity string, timeoutMin int) error { + if op.Done { + if op.Error != nil { + return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) + } + return nil + } + + w := &AccessContextManagerOperationWaiter{ + Service: service.Operations, + 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 fmt.Errorf("Error waiting for %s: %s", activity, err) + } + + op = opRaw.(*accesscontextmanager.Operation) + if op.Error != nil { + return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) + } + + return nil +} diff --git a/google-beta/config.go b/google-beta/config.go index 3ddf7d39f9..694240f7c6 100644 --- a/google-beta/config.go +++ b/google-beta/config.go @@ -15,6 +15,7 @@ import ( "golang.org/x/oauth2" "golang.org/x/oauth2/google" "golang.org/x/oauth2/jwt" + "google.golang.org/api/accesscontextmanager/v1beta" appengine "google.golang.org/api/appengine/v1" "google.golang.org/api/bigquery/v2" "google.golang.org/api/cloudbilling/v1" @@ -60,6 +61,7 @@ type Config struct { tokenSource oauth2.TokenSource + clientAccessContextManager *accesscontextmanager.Service clientBilling *cloudbilling.APIService clientBuild *cloudbuild.Service clientComposer *composer.Service @@ -324,6 +326,13 @@ func (c *Config) loadAndValidate() error { } c.clientCloudFunctions.UserAgent = userAgent + log.Printf("[INFO] Instantiating Google Cloud AccessContextManager Client...") + c.clientAccessContextManager, err = accesscontextmanager.New(client) + if err != nil { + return err + } + c.clientAccessContextManager.UserAgent = userAgent + c.bigtableClientFactory = &BigtableClientFactory{ UserAgent: userAgent, TokenSource: tokenSource, diff --git a/google-beta/provider.go b/google-beta/provider.go index 8698979e0d..c3d3d0f1d8 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -104,6 +104,7 @@ func Provider() terraform.ResourceProvider { GeneratedBinaryAuthorizationResourcesMap, GeneratedContainerAnalysisResourcesMap, GeneratedFilestoreResourcesMap, + GeneratedAccessContextManagerResourcesMap, // end beta-only products GeneratedComputeResourcesMap, GeneratedRedisResourcesMap, diff --git a/google-beta/provider_accesscontextmanager_gen.go b/google-beta/provider_accesscontextmanager_gen.go new file mode 100644 index 0000000000..79920461bf --- /dev/null +++ b/google-beta/provider_accesscontextmanager_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 GeneratedAccessContextManagerResourcesMap = map[string]*schema.Resource{ + "google_access_context_manager_access_policy": resourceAccessContextManagerAccessPolicy(), +} diff --git a/google-beta/resource_access_context_manager_access_policy_test.go b/google-beta/resource_access_context_manager_access_policy_test.go new file mode 100644 index 0000000000..a793461917 --- /dev/null +++ b/google-beta/resource_access_context_manager_access_policy_test.go @@ -0,0 +1,71 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +// We can only have a single test as long as we are using a single organization +func TestAccAccessContextManagerAccessPolicy_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAccessContextManagerAccessPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAccessContextManagerAccessPolicy_basic(org, "my policy"), + }, + { + ResourceName: "google_access_context_manager_access_policy.test-access", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAccessContextManagerAccessPolicy_basic(org, "my new policy"), + }, + { + ResourceName: "google_access_context_manager_access_policy.test-access", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAccessContextManagerAccessPolicyDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_access_context_manager_access_policy" { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(rs, "https://accesscontextmanager.googleapis.com/v1beta/accessPolicies/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", url, nil) + if err == nil { + return fmt.Errorf("AccessPolicy still exists at %s", url) + } + } + + return nil +} + +func testAccAccessContextManagerAccessPolicy_basic(org, title string) string { + return fmt.Sprintf(` +resource "google_access_context_manager_access_policy" "test-access" { + parent = "organizations/%s" + title = "%s" +} +`, org, title) +} diff --git a/google-beta/resource_accesscontextmanager_access_policy.go b/google-beta/resource_accesscontextmanager_access_policy.go new file mode 100644 index 0000000000..2fba89131e --- /dev/null +++ b/google-beta/resource_accesscontextmanager_access_policy.go @@ -0,0 +1,315 @@ +// ---------------------------------------------------------------------------- +// +// *** 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 ( + "encoding/json" + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/schema" + accesscontextmanager "google.golang.org/api/accesscontextmanager/v1beta" +) + +func resourceAccessContextManagerAccessPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceAccessContextManagerAccessPolicyCreate, + Read: resourceAccessContextManagerAccessPolicyRead, + Update: resourceAccessContextManagerAccessPolicyUpdate, + Delete: resourceAccessContextManagerAccessPolicyDelete, + + Importer: &schema.ResourceImporter{ + State: resourceAccessContextManagerAccessPolicyImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(240 * time.Second), + Update: schema.DefaultTimeout(240 * time.Second), + Delete: schema.DefaultTimeout(240 * time.Second), + }, + + Schema: map[string]*schema.Schema{ + "parent": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "title": { + Type: schema.TypeString, + Required: true, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAccessContextManagerAccessPolicyCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + parentProp, err := expandAccessContextManagerAccessPolicyParent(d.Get("parent"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("parent"); !isEmptyValue(reflect.ValueOf(parentProp)) && (ok || !reflect.DeepEqual(v, parentProp)) { + obj["parent"] = parentProp + } + titleProp, err := expandAccessContextManagerAccessPolicyTitle(d.Get("title"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("title"); !isEmptyValue(reflect.ValueOf(titleProp)) && (ok || !reflect.DeepEqual(v, titleProp)) { + obj["title"] = titleProp + } + + url, err := replaceVars(d, config, "https://accesscontextmanager.googleapis.com/v1beta/accessPolicies") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new AccessPolicy: %#v", obj) + res, err := sendRequest(config, "POST", url, obj) + if err != nil { + return fmt.Errorf("Error creating AccessPolicy: %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) + + op := &accesscontextmanager.Operation{} + err = Convert(res, op) + if err != nil { + return err + } + + waitErr := accessContextManagerOperationWaitTime( + config.clientAccessContextManager, op, "Creating AccessPolicy", + int(d.Timeout(schema.TimeoutCreate).Minutes())) + + if waitErr != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create AccessPolicy: %s", waitErr) + } + + log.Printf("[DEBUG] Finished creating AccessPolicy %q: %#v", d.Id(), res) + + // The operation for this resource contains the generated name that we need + // in order to perform a READ. We need to access the object inside of it as + // a map[string]interface, so let's do that. + bytes, err := op.Response.MarshalJSON() + if err != nil { + return err + } + + var data map[string]interface{} + if err := json.Unmarshal(bytes, &data); err != nil { + return err + } + + name := GetResourceNameFromSelfLink(data["name"].(string)) + log.Printf("[DEBUG] Setting AccessPolicy name, id to %s", name) + d.Set("name", name) + d.SetId(name) + + return resourceAccessContextManagerAccessPolicyRead(d, meta) +} + +func resourceAccessContextManagerAccessPolicyRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "https://accesscontextmanager.googleapis.com/v1beta/accessPolicies/{{name}}") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("AccessContextManagerAccessPolicy %q", d.Id())) + } + + if err := d.Set("name", flattenAccessContextManagerAccessPolicyName(res["name"])); err != nil { + return fmt.Errorf("Error reading AccessPolicy: %s", err) + } + if err := d.Set("create_time", flattenAccessContextManagerAccessPolicyCreateTime(res["createTime"])); err != nil { + return fmt.Errorf("Error reading AccessPolicy: %s", err) + } + if err := d.Set("update_time", flattenAccessContextManagerAccessPolicyUpdateTime(res["updateTime"])); err != nil { + return fmt.Errorf("Error reading AccessPolicy: %s", err) + } + if err := d.Set("parent", flattenAccessContextManagerAccessPolicyParent(res["parent"])); err != nil { + return fmt.Errorf("Error reading AccessPolicy: %s", err) + } + if err := d.Set("title", flattenAccessContextManagerAccessPolicyTitle(res["title"])); err != nil { + return fmt.Errorf("Error reading AccessPolicy: %s", err) + } + + return nil +} + +func resourceAccessContextManagerAccessPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + parentProp, err := expandAccessContextManagerAccessPolicyParent(d.Get("parent"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("parent"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, parentProp)) { + obj["parent"] = parentProp + } + titleProp, err := expandAccessContextManagerAccessPolicyTitle(d.Get("title"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("title"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, titleProp)) { + obj["title"] = titleProp + } + + url, err := replaceVars(d, config, "https://accesscontextmanager.googleapis.com/v1beta/accessPolicies/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating AccessPolicy %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("parent") { + updateMask = append(updateMask, "parent") + } + + if d.HasChange("title") { + updateMask = append(updateMask, "title") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + res, err := sendRequest(config, "PATCH", url, obj) + + if err != nil { + return fmt.Errorf("Error updating AccessPolicy %q: %s", d.Id(), err) + } + + op := &accesscontextmanager.Operation{} + err = Convert(res, op) + if err != nil { + return err + } + + err = accessContextManagerOperationWaitTime( + config.clientAccessContextManager, op, "Updating AccessPolicy", + int(d.Timeout(schema.TimeoutUpdate).Minutes())) + + if err != nil { + return err + } + + return resourceAccessContextManagerAccessPolicyRead(d, meta) +} + +func resourceAccessContextManagerAccessPolicyDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "https://accesscontextmanager.googleapis.com/v1beta/accessPolicies/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting AccessPolicy %q", d.Id()) + res, err := sendRequest(config, "DELETE", url, obj) + if err != nil { + return handleNotFoundError(err, d, "AccessPolicy") + } + + op := &accesscontextmanager.Operation{} + err = Convert(res, op) + if err != nil { + return err + } + + err = accessContextManagerOperationWaitTime( + config.clientAccessContextManager, op, "Deleting AccessPolicy", + int(d.Timeout(schema.TimeoutDelete).Minutes())) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting AccessPolicy %q: %#v", d.Id(), res) + return nil +} + +func resourceAccessContextManagerAccessPolicyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + parseImportId([]string{"(?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) + + return []*schema.ResourceData{d}, nil +} + +func flattenAccessContextManagerAccessPolicyName(v interface{}) interface{} { + if v == nil { + return v + } + return NameFromSelfLinkStateFunc(v) +} + +func flattenAccessContextManagerAccessPolicyCreateTime(v interface{}) interface{} { + return v +} + +func flattenAccessContextManagerAccessPolicyUpdateTime(v interface{}) interface{} { + return v +} + +func flattenAccessContextManagerAccessPolicyParent(v interface{}) interface{} { + return v +} + +func flattenAccessContextManagerAccessPolicyTitle(v interface{}) interface{} { + return v +} + +func expandAccessContextManagerAccessPolicyParent(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandAccessContextManagerAccessPolicyTitle(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/website/docs/r/accesscontextmanager_access_policy.html.markdown b/website/docs/r/accesscontextmanager_access_policy.html.markdown new file mode 100644 index 0000000000..1bb69bbf60 --- /dev/null +++ b/website/docs/r/accesscontextmanager_access_policy.html.markdown @@ -0,0 +1,98 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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_access_context_manager_access_policy" +sidebar_current: "docs-google-access-context-manager-access-policy" +description: |- + AccessPolicy is a container for AccessLevels (which define the necessary + attributes to use GCP services) and ServicePerimeters (which define + regions of services able to freely pass data within a perimeter). +--- + +# google\_access\_context\_manager\_access\_policy + +AccessPolicy is a container for AccessLevels (which define the necessary +attributes to use GCP services) and ServicePerimeters (which define +regions of services able to freely pass data within a perimeter). An +access policy is globally visible within an organization, and the +restrictions it specifies apply to all projects within an organization. + + +To get more information about AccessPolicy, see: + +* [API documentation](https://cloud.google.com/access-context-manager/docs/reference/rest/v1beta/accessPolicies) +* How-to Guides + * [Access Policy Quickstart](https://cloud.google.com/access-context-manager/docs/quickstart) + +## Example Usage - Access Context Manager Access Policy Basic + + +```hcl +resource "google_access_context_manager_access_policy" "access-policy" { + parent = "organizations/123456789" + title = "my policy" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `parent` - + (Required) + The parent of this AccessPolicy in the Cloud Resource Hierarchy. + Format: organizations/{organization_id} + +* `title` - + (Required) + Human readable title. Does not affect behavior. + + +- - - + + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + + +* `name` - + Resource name of the AccessPolicy. Format: {policy_id} + +* `create_time` - + Time the AccessPolicy was created in UTC. + +* `update_time` - + Time the AccessPolicy was updated in UTC. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +AccessPolicy can be imported using any of these accepted formats: + +``` +$ terraform import google_access_context_manager_access_policy.default {{name}} +```