Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for IAM on Spanner Instances. #1387

Merged
merged 6 commits into from
May 3, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions google/iam_spanner_instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package google

import (
"fmt"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
spanner "google.golang.org/api/spanner/v1"
)

var IamSpannerInstanceSchema = map[string]*schema.Schema{
"instance": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"project": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
}

type SpannerInstanceIamUpdater struct {
project string
instance string
Config *Config
}

func NewSpannerInstanceIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) {
project, err := getProject(d, config)
if err != nil {
return nil, err
}

return &SpannerInstanceIamUpdater{
project: project,
instance: d.Get("instance").(string),
Config: config,
}, nil
}

func SpannerInstanceIdParseFunc(d *schema.ResourceData, config *Config) error {
id, err := extractSpannerInstanceId(d.Id())
if err != nil {
return err
}
d.Set("instance", id.Instance)
d.Set("project", id.Project)

// Explicitly set the id so imported resources have the same ID format as non-imported ones.
d.SetId(id.terraformId())
return nil
}

func (u *SpannerInstanceIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
p, err := u.Config.clientSpanner.Projects.Instances.GetIamPolicy(spannerInstanceId{
Project: u.project,
Instance: u.instance,
}.instanceUri(), &spanner.GetIamPolicyRequest{}).Do()

if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err)
}

cloudResourcePolicy, err := spannerToResourceManagerPolicy(p)

if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err)
}

return cloudResourcePolicy, nil
}

func (u *SpannerInstanceIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
spannerPolicy, err := resourceManagerToSpannerPolicy(policy)

if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err)
}

_, err = u.Config.clientSpanner.Projects.Instances.SetIamPolicy(spannerInstanceId{
Project: u.project,
Instance: u.instance,
}.instanceUri(), &spanner.SetIamPolicyRequest{
Policy: spannerPolicy,
}).Do()

if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err)
}

return nil
}

func (u *SpannerInstanceIamUpdater) GetResourceId() string {
return spannerInstanceId{
Project: u.project,
Instance: u.instance,
}.terraformId()
}

func (u *SpannerInstanceIamUpdater) GetMutexKey() string {
return fmt.Sprintf("iam-spanner-instance-%s-%s", u.project, u.instance)
}

func (u *SpannerInstanceIamUpdater) DescribeResource() string {
return fmt.Sprintf("Spanner Instance: %s/%s", u.project, u.instance)
}

func resourceManagerToSpannerPolicy(p *cloudresourcemanager.Policy) (*spanner.Policy, error) {
out := &spanner.Policy{}
err := Convert(p, out)
if err != nil {
return nil, errwrap.Wrapf("Cannot convert a resourcemanager policy to a spanner policy: {{err}}", err)
}
return out, nil
}

func spannerToResourceManagerPolicy(p *spanner.Policy) (*cloudresourcemanager.Policy, error) {
out := &cloudresourcemanager.Policy{}
err := Convert(p, out)
if err != nil {
return nil, errwrap.Wrapf("Cannot convert a spanner policy to a resourcemanager policy: {{err}}", err)
}
return out, nil
}
3 changes: 3 additions & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ func Provider() terraform.ResourceProvider {
"google_kms_crypto_key_iam_member": ResourceIamMemberWithImport(IamKmsCryptoKeySchema, NewKmsCryptoKeyIamUpdater, CryptoIdParseFunc),
"google_sourcerepo_repository": resourceSourceRepoRepository(),
"google_spanner_instance": resourceSpannerInstance(),
"google_spanner_instance_iam_binding": ResourceIamBindingWithImport(IamSpannerInstanceSchema, NewSpannerInstanceIamUpdater, SpannerInstanceIdParseFunc),
"google_spanner_instance_iam_member": ResourceIamMemberWithImport(IamSpannerInstanceSchema, NewSpannerInstanceIamUpdater, SpannerInstanceIdParseFunc),
"google_spanner_instance_iam_policy": ResourceIamPolicyWithImport(IamSpannerInstanceSchema, NewSpannerInstanceIamUpdater, SpannerInstanceIdParseFunc),
"google_spanner_database": resourceSpannerDatabase(),
"google_sql_database": resourceSqlDatabase(),
"google_sql_database_instance": resourceSqlDatabaseInstance(),
Expand Down
215 changes: 215 additions & 0 deletions google/resource_spanner_instance_iam_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package google

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)

func TestAccSpannerInstanceIamBinding(t *testing.T) {
t.Parallel()

account := acctest.RandomWithPrefix("tf-test")
role := "roles/spanner.databaseAdmin"
project := getTestProjectFromEnv()
instance := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccSpannerInstanceIamBinding_basic(account, instance, role),
},
resource.TestStep{
ResourceName: "google_spanner_instance_iam_binding.foo",
ImportStateId: fmt.Sprintf("%s %s", spannerInstanceId{
Project: project,
Instance: instance,
}.terraformId(), role),
ImportState: true,
ImportStateVerify: true,
},
{
// Test Iam Binding update
Config: testAccSpannerInstanceIamBinding_update(account, instance, role),
},
resource.TestStep{
ResourceName: "google_spanner_instance_iam_binding.foo",
ImportStateId: fmt.Sprintf("%s %s", spannerInstanceId{
Project: project,
Instance: instance,
}.terraformId(), role),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccSpannerInstanceIamMember(t *testing.T) {
t.Parallel()

project := getTestProjectFromEnv()
account := acctest.RandomWithPrefix("tf-test")
role := "roles/spanner.databaseAdmin"
instance := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
// Test Iam Member creation (no update for member, no need to test)
Config: testAccSpannerInstanceIamMember_basic(account, instance, role),
},
resource.TestStep{
ResourceName: "google_spanner_instance_iam_member.foo",
ImportStateId: fmt.Sprintf("%s %s serviceAccount:%s@%s.iam.gserviceaccount.com", spannerInstanceId{
Instance: instance,
Project: project,
}.terraformId(), role, account, project),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccSpannerInstanceIamPolicy(t *testing.T) {
t.Parallel()

project := getTestProjectFromEnv()
account := acctest.RandomWithPrefix("tf-test")
role := "roles/spanner.databaseAdmin"
instance := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccSpannerInstanceIamPolicy_basic(account, instance, role),
},
// Test a few import formats
resource.TestStep{
ResourceName: "google_spanner_instance_iam_policy.foo",
ImportStateId: fmt.Sprintf("%s", spannerInstanceId{
Instance: instance,
Project: project,
}.terraformId()),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccSpannerInstanceIamBinding_basic(account, instance, roleId string) string {
return fmt.Sprintf(`
resource "google_service_account" "test_account" {
account_id = "%s"
display_name = "Spanner Iam Testing Account"
}

resource "google_spanner_instance" "instance" {
name = "%s"
config = "regional-us-central1"
display_name = "%s"
num_nodes = 1
}

resource "google_spanner_instance_iam_binding" "foo" {
project = "${google_spanner_instance.instance.project}"
instance = "${google_spanner_instance.instance.name}"
role = "%s"
members = ["serviceAccount:${google_service_account.test_account.email}"]
}
`, account, instance, instance, roleId)
}

func testAccSpannerInstanceIamBinding_update(account, instance, roleId string) string {
return fmt.Sprintf(`
resource "google_service_account" "test_account" {
account_id = "%s"
display_name = "Spanner Iam Testing Account"
}

resource "google_service_account" "test_account_2" {
account_id = "%s-2"
display_name = "Spanner Iam Testing Account"
}

resource "google_spanner_instance" "instance" {
name = "%s"
config = "regional-us-central1"
display_name = "%s"
num_nodes = 1
}

resource "google_spanner_instance_iam_binding" "foo" {
project = "${google_spanner_instance.instance.project}"
instance = "${google_spanner_instance.instance.name}"
role = "%s"
members = [
"serviceAccount:${google_service_account.test_account.email}",
"serviceAccount:${google_service_account.test_account_2.email}"
]
}
`, account, account, instance, instance, roleId)
}

func testAccSpannerInstanceIamMember_basic(account, instance, roleId string) string {
return fmt.Sprintf(`
resource "google_service_account" "test_account" {
account_id = "%s"
display_name = "Spanner Iam Testing Account"
}

resource "google_spanner_instance" "instance" {
name = "%s"
config = "regional-us-central1"
display_name = "%s"
num_nodes = 1
}

resource "google_spanner_instance_iam_member" "foo" {
project = "${google_spanner_instance.instance.project}"
instance = "${google_spanner_instance.instance.name}"
role = "%s"
member = "serviceAccount:${google_service_account.test_account.email}"
}
`, account, instance, instance, roleId)
}

func testAccSpannerInstanceIamPolicy_basic(account, instance, roleId string) string {
return fmt.Sprintf(`
resource "google_service_account" "test_account" {
account_id = "%s"
display_name = "Spanner Iam Testing Account"
}

resource "google_spanner_instance" "instance" {
name = "%s"
config = "regional-us-central1"
display_name = "%s"
num_nodes = 1
}

data "google_iam_policy" "foo" {
binding {
role = "%s"

members = ["serviceAccount:${google_service_account.test_account.email}"]
}
}

resource "google_spanner_instance_iam_policy" "foo" {
project = "${google_spanner_instance.instance.project}"
instance = "${google_spanner_instance.instance.name}"
policy_data = "${data.google_iam_policy.foo.policy_data}"
}
`, account, instance, instance, roleId)
}
Loading