Skip to content

Commit

Permalink
Upstream Bigtable Instance IAM PR
Browse files Browse the repository at this point in the history
  • Loading branch information
rileykarson committed Jul 3, 2019
1 parent 5c0b4c4 commit 9a3a5fb
Show file tree
Hide file tree
Showing 10 changed files with 616 additions and 0 deletions.
211 changes: 211 additions & 0 deletions third_party/terraform/tests/resource_bigtable_instance_iam_test.go
Original file line number Diff line number Diff line change
@@ -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"
}
}`
19 changes: 19 additions & 0 deletions third_party/terraform/utils/config.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"google.golang.org/api/accesscontextmanager/v1beta"
<% end -%>
"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"
Expand Down Expand Up @@ -193,6 +194,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{
Expand Down Expand Up @@ -463,6 +471,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))
Expand Down
50 changes: 50 additions & 0 deletions third_party/terraform/utils/field_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/(.+)"
)
Expand Down Expand Up @@ -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
}
79 changes: 79 additions & 0 deletions third_party/terraform/utils/field_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
}
}
Loading

0 comments on commit 9a3a5fb

Please sign in to comment.