diff --git a/google/config.go b/google/config.go index c8982f22217..b156239ee88 100644 --- a/google/config.go +++ b/google/config.go @@ -17,6 +17,7 @@ import ( googleoauth "golang.org/x/oauth2/google" appengine "google.golang.org/api/appengine/v1" "google.golang.org/api/bigquery/v2" + "google.golang.org/api/bigtableadmin/v2" "google.golang.org/api/cloudbilling/v1" "google.golang.org/api/cloudbuild/v1" "google.golang.org/api/cloudfunctions/v1" @@ -173,6 +174,13 @@ type Config struct { clientStorageTransfer *storagetransfer.Service bigtableClientFactory *BigtableClientFactory + BigtableAdminBasePath string + // Unlike other clients, the Bigtable Admin client doesn't use a single + // service. Instead, there are several distinct services created off + // the base service object. To imitate most other handwritten clients, + // we expose those directly instead of providing the `Service` object + // as a factory. + clientBigtableProjectsInstances *bigtableadmin.ProjectsInstancesService } var defaultClientScopes = []string{ @@ -427,6 +435,17 @@ func (c *Config) LoadAndValidate() error { TokenSource: tokenSource, } + bigtableAdminBasePath := removeBasePathVersion(c.BigtableAdminBasePath) + log.Printf("[INFO] Instantiating Google Cloud BigtableAdmin for path %s", bigtableAdminBasePath) + + clientBigtable, err := bigtableadmin.NewService(context, option.WithHTTPClient(client)) + if err != nil { + return err + } + clientBigtable.UserAgent = userAgent + clientBigtable.BasePath = bigtableAdminBasePath + c.clientBigtableProjectsInstances = bigtableadmin.NewProjectsInstancesService(clientBigtable) + sourceRepoClientBasePath := removeBasePathVersion(c.SourceRepoBasePath) log.Printf("[INFO] Instantiating Google Cloud Source Repo client for path %s", sourceRepoClientBasePath) c.clientSourceRepo, err = sourcerepo.NewService(context, option.WithHTTPClient(client)) diff --git a/google/field_helpers.go b/google/field_helpers.go index a822c4ea9bb..981a170659a 100644 --- a/google/field_helpers.go +++ b/google/field_helpers.go @@ -14,6 +14,8 @@ const ( regionalLinkTemplate = "projects/%s/regions/%s/%s/%s" regionalLinkBasePattern = "projects/(.+)/regions/(.+)/%s/(.+)" regionalPartialLinkBasePattern = "regions/(.+)/%s/(.+)" + projectLinkTemplate = "projects/%s/%s/%s" + projectBasePattern = "projects/(.+)/%s/(.+)" organizationLinkTemplate = "organizations/%s/%s/%s" organizationBasePattern = "organizations/(.+)/%s/(.+)" ) @@ -355,3 +357,51 @@ func getRegionFromSchema(regionSchemaField, zoneSchemaField string, d TerraformR return "", fmt.Errorf("Cannot determine region: set in this resource, or set provider-level 'region' or 'zone'.") } + +type ProjectFieldValue struct { + Project string + Name string + + resourceType string +} + +func (f ProjectFieldValue) RelativeLink() string { + if len(f.Name) == 0 { + return "" + } + + return fmt.Sprintf(projectLinkTemplate, f.Project, f.resourceType, f.Name) +} + +// Parses a project field with the following formats: +// - projects/{my_projects}/{resource_type}/{resource_name} +func parseProjectFieldValue(resourceType, fieldValue, projectSchemaField string, d TerraformResourceData, config *Config, isEmptyValid bool) (*ProjectFieldValue, error) { + if len(fieldValue) == 0 { + if isEmptyValid { + return &ProjectFieldValue{resourceType: resourceType}, nil + } + return nil, fmt.Errorf("The project field for resource %s cannot be empty", resourceType) + } + + r := regexp.MustCompile(fmt.Sprintf(projectBasePattern, resourceType)) + if parts := r.FindStringSubmatch(fieldValue); parts != nil { + return &ProjectFieldValue{ + Project: parts[1], + Name: parts[2], + + resourceType: resourceType, + }, nil + } + + project, err := getProjectFromSchema(projectSchemaField, d, config) + if err != nil { + return nil, err + } + + return &ProjectFieldValue{ + Project: project, + Name: GetResourceNameFromSelfLink(fieldValue), + + resourceType: resourceType, + }, nil +} diff --git a/google/field_helpers_test.go b/google/field_helpers_test.go index 49847f77760..08d5bbc0a05 100644 --- a/google/field_helpers_test.go +++ b/google/field_helpers_test.go @@ -353,3 +353,82 @@ func TestParseRegionalFieldValue(t *testing.T) { }) } } + +func TestParseProjectFieldValue(t *testing.T) { + const resourceType = "instances" + cases := map[string]struct { + FieldValue string + ExpectedRelativeLink string + ExpectedError bool + IsEmptyValid bool + ProjectSchemaField string + ProjectSchemaValue string + Config *Config + }{ + "instance is a full self link": { + FieldValue: "https://www.googleapis.com/compute/v1/projects/myproject/instances/my-instance", + ExpectedRelativeLink: "projects/myproject/instances/my-instance", + }, + "instance is a relative self link": { + FieldValue: "projects/myproject/instances/my-instance", + ExpectedRelativeLink: "projects/myproject/instances/my-instance", + }, + "instance is a partial relative self link": { + FieldValue: "projects/instances/my-instance", + Config: &Config{Project: "default-project"}, + ExpectedRelativeLink: "projects/default-project/instances/my-instance", + }, + "instance is the name only": { + FieldValue: "my-instance", + Config: &Config{Project: "default-project"}, + ExpectedRelativeLink: "projects/default-project/instances/my-instance", + }, + "instance is the name only and has a project set in schema": { + FieldValue: "my-instance", + ProjectSchemaField: "project", + ProjectSchemaValue: "schema-project", + Config: &Config{Project: "default-project"}, + ExpectedRelativeLink: "projects/schema-project/instances/my-instance", + }, + "instance is the name only and has a project set in schema but the field is not specified.": { + FieldValue: "my-instance", + ProjectSchemaValue: "schema-project", + Config: &Config{Project: "default-project"}, + ExpectedRelativeLink: "projects/default-project/instances/my-instance", + }, + "instance is empty and it is valid": { + FieldValue: "", + IsEmptyValid: true, + ExpectedRelativeLink: "", + }, + "instance is empty and it is not valid": { + FieldValue: "", + IsEmptyValid: false, + ExpectedError: true, + }, + } + + for tn, tc := range cases { + fieldsInSchema := make(map[string]interface{}) + + if len(tc.ProjectSchemaValue) > 0 && len(tc.ProjectSchemaField) > 0 { + fieldsInSchema[tc.ProjectSchemaField] = tc.ProjectSchemaValue + } + + d := &ResourceDataMock{ + FieldsInSchema: fieldsInSchema, + } + + v, err := parseProjectFieldValue(resourceType, tc.FieldValue, tc.ProjectSchemaField, d, tc.Config, tc.IsEmptyValid) + + if err != nil { + if !tc.ExpectedError { + t.Errorf("bad: %s, did not expect an error. Error: %s", tn, err) + } + } else { + if v.RelativeLink() != tc.ExpectedRelativeLink { + t.Errorf("bad: %s, expected relative link to be '%s' but got '%s'", tn, tc.ExpectedRelativeLink, v.RelativeLink()) + } + } + } +} diff --git a/google/iam_bigtable_instance.go b/google/iam_bigtable_instance.go new file mode 100644 index 00000000000..bab4f1057b2 --- /dev/null +++ b/google/iam_bigtable_instance.go @@ -0,0 +1,119 @@ +package google + +import ( + "fmt" + "google.golang.org/api/bigtableadmin/v2" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var IamBigtableInstanceSchema = map[string]*schema.Schema{ + "instance": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, +} + +type BigtableInstanceIamUpdater struct { + project string + instance string + Config *Config +} + +func NewBigtableInstanceUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) { + project, err := getProject(d, config) + if err != nil { + return nil, err + } + + d.Set("project", project) + + return &BigtableInstanceIamUpdater{ + project: project, + instance: d.Get("instance").(string), + Config: config, + }, nil +} + +func BigtableInstanceIdParseFunc(d *schema.ResourceData, config *Config) error { + fv, err := parseProjectFieldValue("instances", d.Id(), "project", d, config, false) + if err != nil { + return err + } + + d.Set("project", fv.Project) + d.Set("instance", fv.Name) + + // Explicitly set the id so imported resources have the same ID format as non-imported ones. + d.SetId(fv.RelativeLink()) + return nil +} + +func (u *BigtableInstanceIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + req := &bigtableadmin.GetIamPolicyRequest{} + p, err := u.Config.clientBigtableProjectsInstances.GetIamPolicy(u.GetResourceId(), req).Do() + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + cloudResourcePolicy, err := bigtableToResourceManagerPolicy(p) + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return cloudResourcePolicy, nil +} + +func (u *BigtableInstanceIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + bigtablePolicy, err := resourceManagerToBigtablePolicy(policy) + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + req := &bigtableadmin.SetIamPolicyRequest{Policy: bigtablePolicy} + _, err = u.Config.clientBigtableProjectsInstances.SetIamPolicy(u.GetResourceId(), req).Do() + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *BigtableInstanceIamUpdater) GetResourceId() string { + return fmt.Sprintf("projects/%s/instances/%s", u.project, u.instance) +} + +func (u *BigtableInstanceIamUpdater) GetMutexKey() string { + return fmt.Sprintf("iam-bigtable-instance-%s-%s", u.project, u.instance) +} + +func (u *BigtableInstanceIamUpdater) DescribeResource() string { + return fmt.Sprintf("Bigtable Instance %s/%s", u.project, u.instance) +} + +func resourceManagerToBigtablePolicy(p *cloudresourcemanager.Policy) (*bigtableadmin.Policy, error) { + out := &bigtableadmin.Policy{} + err := Convert(p, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a bigtable policy to a cloudresourcemanager policy: {{err}}", err) + } + return out, nil +} + +func bigtableToResourceManagerPolicy(p *bigtableadmin.Policy) (*cloudresourcemanager.Policy, error) { + out := &cloudresourcemanager.Policy{} + err := Convert(p, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a cloudresourcemanager policy to a bigtable policy: {{err}}", err) + } + return out, nil +} diff --git a/google/provider.go b/google/provider.go index a61816e9a83..c76ef8d3f58 100644 --- a/google/provider.go +++ b/google/provider.go @@ -118,6 +118,7 @@ func Provider() terraform.ResourceProvider { CloudFunctionsCustomEndpointEntryKey: CloudFunctionsCustomEndpointEntry, CloudIoTCustomEndpointEntryKey: CloudIoTCustomEndpointEntry, StorageTransferCustomEndpointEntryKey: StorageTransferCustomEndpointEntry, + BigtableAdminCustomEndpointEntryKey: BigtableAdminCustomEndpointEntry, }, DataSourcesMap: map[string]*schema.Resource{ @@ -209,6 +210,9 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_bigquery_dataset": resourceBigQueryDataset(), "google_bigquery_table": resourceBigQueryTable(), "google_bigtable_instance": resourceBigtableInstance(), + "google_bigtable_instance_iam_binding": ResourceIamBindingWithImport(IamBigtableInstanceSchema, NewBigtableInstanceUpdater, BigtableInstanceIdParseFunc), + "google_bigtable_instance_iam_member": ResourceIamMemberWithImport(IamBigtableInstanceSchema, NewBigtableInstanceUpdater, BigtableInstanceIdParseFunc), + "google_bigtable_instance_iam_policy": ResourceIamPolicyWithImport(IamBigtableInstanceSchema, NewBigtableInstanceUpdater, BigtableInstanceIdParseFunc), "google_bigtable_table": resourceBigtableTable(), "google_billing_account_iam_binding": ResourceIamBindingWithImport(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc), "google_billing_account_iam_member": ResourceIamMemberWithImport(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc), @@ -390,6 +394,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { config.CloudFunctionsBasePath = d.Get(CloudFunctionsCustomEndpointEntryKey).(string) config.CloudIoTBasePath = d.Get(CloudIoTCustomEndpointEntryKey).(string) config.StorageTransferBasePath = d.Get(StorageTransferCustomEndpointEntryKey).(string) + config.BigtableAdminBasePath = d.Get(BigtableAdminCustomEndpointEntryKey).(string) if err := config.LoadAndValidate(); err != nil { return nil, err @@ -443,6 +448,7 @@ func ConfigureBasePaths(c *Config) { c.CloudFunctionsBasePath = CloudFunctionsDefaultBasePath c.CloudIoTBasePath = CloudIoTDefaultBasePath c.StorageTransferBasePath = StorageTransferDefaultBasePath + c.BigtableAdminBasePath = BigtableAdminDefaultBasePath } func validateCredentials(v interface{}, k string) (warnings []string, errors []error) { diff --git a/google/provider_handwritten_endpoint.go b/google/provider_handwritten_endpoint.go index 2308824e36b..6dabe5e4016 100644 --- a/google/provider_handwritten_endpoint.go +++ b/google/provider_handwritten_endpoint.go @@ -227,6 +227,17 @@ var StorageTransferCustomEndpointEntry = &schema.Schema{ }, StorageTransferDefaultBasePath), } +var BigtableAdminDefaultBasePath = "https://bigtableadmin.googleapis.com/v2/" +var BigtableAdminCustomEndpointEntryKey = "bigtable_custom_endpoint" +var BigtableAdminCustomEndpointEntry = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_BIGTABLE_CUSTOM_ENDPOINT", + }, BigtableAdminDefaultBasePath), +} + func validateCustomEndpoint(v interface{}, k string) (ws []string, errors []error) { re := `.*/[^/]+/$` return validateRegexp(re)(v, k) diff --git a/google/resource_bigtable_instance_iam_test.go b/google/resource_bigtable_instance_iam_test.go new file mode 100644 index 00000000000..aef7780d25e --- /dev/null +++ b/google/resource_bigtable_instance_iam_test.go @@ -0,0 +1,211 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBigtableInstanceIamBinding(t *testing.T) { + t.Parallel() + + instance := "tf-bigtable-iam-" + acctest.RandString(10) + account := "tf-bigtable-iam-" + acctest.RandString(10) + role := "roles/bigtable.user" + + importId := fmt.Sprintf("projects/%s/instances/%s %s", + getTestProjectFromEnv(), instance, role) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test IAM Binding creation + Config: testAccBigtableInstanceIamBinding_basic(instance, account, role), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_bigtable_instance_iam_binding.binding", "role", role), + ), + }, + { + ResourceName: "google_bigtable_instance_iam_binding.binding", + ImportStateId: importId, + ImportState: true, + ImportStateVerify: true, + }, + { + // Test IAM Binding update + Config: testAccBigtableInstanceIamBinding_update(instance, account, role), + }, + { + ResourceName: "google_bigtable_instance_iam_binding.binding", + ImportStateId: importId, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccBigtableInstanceIamMember(t *testing.T) { + t.Parallel() + + instance := "tf-bigtable-iam-" + acctest.RandString(10) + account := "tf-bigtable-iam-" + acctest.RandString(10) + role := "roles/bigtable.user" + + importId := fmt.Sprintf("projects/%s/instances/%s %s serviceAccount:%s", + getTestProjectFromEnv(), + instance, + role, + serviceAccountCanonicalEmail(account)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test IAM Binding creation + Config: testAccBigtableInstanceIamMember(instance, account, role), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_bigtable_instance_iam_member.member", "role", role), + resource.TestCheckResourceAttr( + "google_bigtable_instance_iam_member.member", "member", "serviceAccount:"+serviceAccountCanonicalEmail(account)), + ), + }, + { + ResourceName: "google_bigtable_instance_iam_member.member", + ImportStateId: importId, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccBigtableInstanceIamPolicy(t *testing.T) { + t.Parallel() + + instance := "tf-bigtable-iam-" + acctest.RandString(10) + account := "tf-bigtable-iam-" + acctest.RandString(10) + role := "roles/bigtable.user" + + importId := fmt.Sprintf("projects/%s/instances/%s", + getTestProjectFromEnv(), instance) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test IAM Binding creation + Config: testAccBigtableInstanceIamPolicy(instance, account, role), + }, + { + ResourceName: "google_bigtable_instance_iam_policy.policy", + ImportStateId: importId, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccBigtableInstanceIamBinding_basic(instance, account, role string) string { + return fmt.Sprintf(testBigtableInstanceIam+` + +resource "google_service_account" "test-account1" { + account_id = "%s-1" + display_name = "Dataproc IAM Testing Account" +} + +resource "google_service_account" "test-account2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_bigtable_instance_iam_binding" "binding" { + instance = "${google_bigtable_instance.instance.name}" + role = "%s" + members = [ + "serviceAccount:${google_service_account.test-account1.email}", + ] +} +`, instance, acctest.RandString(10), account, account, role) +} + +func testAccBigtableInstanceIamBinding_update(instance, account, role string) string { + return fmt.Sprintf(testBigtableInstanceIam+` +resource "google_service_account" "test-account1" { + account_id = "%s-1" + display_name = "Dataproc IAM Testing Account" +} + +resource "google_service_account" "test-account2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_bigtable_instance_iam_binding" "binding" { + instance = "${google_bigtable_instance.instance.name}" + role = "%s" + members = [ + "serviceAccount:${google_service_account.test-account1.email}", + "serviceAccount:${google_service_account.test-account2.email}", + ] +} +`, instance, acctest.RandString(10), account, account, role) +} + +func testAccBigtableInstanceIamMember(instance, account, role string) string { + return fmt.Sprintf(testBigtableInstanceIam+` +resource "google_service_account" "test-account" { + account_id = "%s" + display_name = "Dataproc IAM Testing Account" +} + +resource "google_bigtable_instance_iam_member" "member" { + instance = "${google_bigtable_instance.instance.name}" + role = "%s" + member = "serviceAccount:${google_service_account.test-account.email}" +} +`, instance, acctest.RandString(10), account, role) +} + +func testAccBigtableInstanceIamPolicy(instance, account, role string) string { + return fmt.Sprintf(testBigtableInstanceIam+` +resource "google_service_account" "test-account" { + account_id = "%s" + display_name = "Dataproc IAM Testing Account" +} + +data "google_iam_policy" "policy" { + binding { + role = "%s" + members = ["serviceAccount:${google_service_account.test-account.email}"] + } +} + +resource "google_bigtable_instance_iam_policy" "policy" { + instance = "${google_bigtable_instance.instance.name}" + policy_data = "${data.google_iam_policy.policy.policy_data}" +} +`, instance, acctest.RandString(10), account, role) +} + +// Smallest instance possible for testing +var testBigtableInstanceIam = ` +resource "google_bigtable_instance" "instance" { + name = "%s" + instance_type = "DEVELOPMENT" + + cluster { + cluster_id = "c-%s" + zone = "us-central1-b" + storage_type = "HDD" + } +}` diff --git a/website/docs/provider_reference.html.markdown b/website/docs/provider_reference.html.markdown index c2bbb67a765..7c32be7cfad 100644 --- a/website/docs/provider_reference.html.markdown +++ b/website/docs/provider_reference.html.markdown @@ -218,6 +218,7 @@ be used for configuration are below: * `access_context_manager_custom_endpoint` (`GOOGLE_ACCESS_CONTEXT_MANAGER_CUSTOM_ENDPOINT`) - `https://accesscontextmanager.googleapis.com/v1/` * `app_engine_custom_endpoint` (`GOOGLE_APP_ENGINE_CUSTOM_ENDPOINT`) - `https://appengine.googleapis.com/v1/` * `bigquery_custom_endpoint` (`GOOGLE_BIGQUERY_CUSTOM_ENDPOINT`) - `https://www.googleapis.com/bigquery/v2/` +* `bigtable_custom_endpoint` (`GOOGLE_BIGTABLE_CUSTOM_ENDPOINT`) - `https://bigtableadmin.googleapis.com/v2/` * `cloud_billing_custom_endpoint` (`GOOGLE_CLOUD_BILLING_CUSTOM_ENDPOINT`) - `https://cloudbilling.googleapis.com/v1/` * `cloud_build_custom_endpoint` (`GOOGLE_CLOUD_BUILD_CUSTOM_ENDPOINT`) - `https://cloudbuild.googleapis.com/v1/` * `cloud_functions_custom_endpoint` (`GOOGLE_CLOUD_FUNCTIONS_CUSTOM_ENDPOINT`) - `https://cloudfunctions.googleapis.com/v1/` diff --git a/website/docs/r/bigtable_instance_iam.html.markdown b/website/docs/r/bigtable_instance_iam.html.markdown new file mode 100644 index 00000000000..52f6e5fa5c7 --- /dev/null +++ b/website/docs/r/bigtable_instance_iam.html.markdown @@ -0,0 +1,108 @@ +--- +layout: "google" +page_title: "Google: google_bigtable_instance_iam" +sidebar_current: "docs-google-bigtable-instance-iam" +description: |- + Collection of resources to manage IAM policy for a Bigtable instance. +--- + +# IAM policy for Bigtable instance + +Three different resources help you manage IAM policies on bigtable instances. Each of these resources serves a different use case: + +* `google_bigtable_instance_iam_policy`: Authoritative. Sets the IAM policy for the instance and replaces any existing policy already attached. +* `google_bigtable_instance_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the instance are preserved. +* `google_bigtable_instance_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role for the instance are preserved. + +~> **Note:** `google_bigtable_instance_iam_policy` **cannot** be used in conjunction with `google_bigtable_instance_iam_binding` and `google_bigtable_instance_iam_member` or they will fight over what your policy should be. In addition, be careful not to accidentaly unset ownership of the instance as `google_bigtable_instance_iam_policy` replaces the entire policy. + +~> **Note:** `google_bigtable_instance_iam_binding` resources **can be** used in conjunction with `google_bigtable_instance_iam_member` resources **only if** they do not grant privilege to the same role. + +## google\_pubsub\_subscription\_iam\_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/editor" + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_bigtable_instance_iam_policy" "editor" { + project = "your-project" + instance = "your-bigtable-instance" + policy_data = "${data.google_iam_policy.admin.policy_data}" +} +``` + +## google\_pubsub\_subscription\_iam\_binding + +```hcl +resource "google_bigtable_instance_iam_binding" "editor" { + instance = "your-bigtable-instance" + role = "roles/editor" + members = [ + "user:jane@example.com", + ] +} +``` + +## google\_pubsub\_subscription\_iam\_member + +```hcl +resource "google_bigtable_instance_iam_member" "editor" { + instance = "your-bigtable-instance" + role = "roles/editor" + member = "user:jane@example.com" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `instance` - (Required) The name or relative resource id of the instance to manage IAM policies for. + +For `google_bigtable_instance_iam_member` or `google_bigtable_instance_iam_binding`: + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **allUsers**: A special identifier that represents anyone who is on the internet; with or without a Google account. + * **allAuthenticatedUsers**: A special identifier that represents anyone who is authenticated with a Google account or a service account. + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + +* `role` - (Required) The role that should be applied. Only one + `google_bigtable_instance_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. + +`google_bigtable_instance_iam_policy` only: +* `policy_data` - (Required) The policy data generated by a `google_iam_policy` data source. + +- - - + +* `project` - (Optional) The project in which the instance belongs. If it + is not provided, Terraform will use the provider default. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the instances's IAM policy. + +## Import + +Instance IAM resources can be imported using the project, instance name, role and/or member. + +``` +$ terraform import google_bigtable_instance_iam_policy.editor "projects/{project}/instances/{instance}" + +$ terraform import google_bigtable_instance_iam_binding.editor "projects/{project}/instances/{instance} roles/editor" + +$ terraform import google_bigtable_instance_iam_member.editor "projects/{project}/instances/{instance} roles/editor user:jane@example.com" +``` diff --git a/website/google.erb b/website/google.erb index 5015c2585a1..9efed6a364c 100644 --- a/website/google.erb +++ b/website/google.erb @@ -209,6 +209,18 @@