diff --git a/google/iam_compute_subnetwork.go b/google/iam_compute_subnetwork.go index 93cfad7a..5508b446 100644 --- a/google/iam_compute_subnetwork.go +++ b/google/iam_compute_subnetwork.go @@ -1,3 +1,164 @@ package google -// Magic Modules doesn't let us remove files - blank out beta-only common-compile files for now. +import ( + "fmt" + "strings" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/cloudresourcemanager/v1" + "google.golang.org/api/compute/v1" +) + +var IamComputeSubnetworkSchema = map[string]*schema.Schema{ + "subnetwork": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, +} + +type ComputeSubnetworkIamUpdater struct { + project string + region string + resourceId string + Config *Config +} + +func NewComputeSubnetworkIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) { + project, err := getProject(d, config) + if err != nil { + return nil, err + } + + region, err := getRegion(d, config) + if err != nil { + return nil, err + } + + return &ComputeSubnetworkIamUpdater{ + project: project, + region: region, + resourceId: d.Get("subnetwork").(string), + Config: config, + }, nil +} + +func ComputeSubnetworkIdParseFunc(d *schema.ResourceData, config *Config) error { + parts := strings.Split(d.Id(), "/") + var fv *RegionalFieldValue + if len(parts) == 3 { + // {project}/{region}/{name} syntax + fv = &RegionalFieldValue{ + Project: parts[0], + Region: parts[1], + Name: parts[2], + resourceType: "subnetworks", + } + } else if len(parts) == 2 { + // /{region}/{name} syntax + project, err := getProject(d, config) + if err != nil { + return err + } + fv = &RegionalFieldValue{ + Project: project, + Region: parts[0], + Name: parts[1], + resourceType: "subnetworks", + } + } else { + // We either have a name or a full self link, so use the field helper + var err error + fv, err = ParseSubnetworkFieldValue(d.Id(), d, config) + if err != nil { + return err + } + } + d.Set("subnetwork", fv.Name) + d.Set("project", fv.Project) + d.Set("region", fv.Region) + + // Explicitly set the id so imported resources have the same ID format as non-imported ones. + d.SetId(fv.RelativeLink()) + return nil +} + +func (u *ComputeSubnetworkIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { + p, err := u.Config.clientCompute.Subnetworks.GetIamPolicy(u.project, u.region, u.resourceId).Do() + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + cloudResourcePolicy, err := computeToResourceManagerPolicy(p) + + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return cloudResourcePolicy, nil +} + +func (u *ComputeSubnetworkIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { + computePolicy, err := resourceManagerToComputePolicy(policy) + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Invalid IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + req := &compute.RegionSetPolicyRequest{ + Policy: computePolicy, + } + _, err = u.Config.clientCompute.Subnetworks.SetIamPolicy(u.project, u.region, u.resourceId, req).Do() + + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err) + } + + return nil +} + +func (u *ComputeSubnetworkIamUpdater) GetResourceId() string { + return fmt.Sprintf("projects/%s/regions/%s/subnetworks/%s", u.project, u.region, u.resourceId) +} + +func (u *ComputeSubnetworkIamUpdater) GetMutexKey() string { + return fmt.Sprintf("iam-compute-subnetwork-%s-%s-%s", u.project, u.region, u.resourceId) +} + +func (u *ComputeSubnetworkIamUpdater) DescribeResource() string { + return fmt.Sprintf("Compute Subnetwork %s/%s/%s", u.project, u.region, u.resourceId) +} + +func resourceManagerToComputePolicy(p *cloudresourcemanager.Policy) (*compute.Policy, error) { + out := &compute.Policy{} + err := Convert(p, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a resourcemanager policy to a compute policy: {{err}}", err) + } + return out, nil +} + +func computeToResourceManagerPolicy(p *compute.Policy) (*cloudresourcemanager.Policy, error) { + out := &cloudresourcemanager.Policy{} + err := Convert(p, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a compute policy to a resourcemanager policy: {{err}}", err) + } + return out, nil +} diff --git a/google/provider.go b/google/provider.go index fa4c159b..8895d9f2 100644 --- a/google/provider.go +++ b/google/provider.go @@ -184,6 +184,9 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_compute_security_policy": resourceComputeSecurityPolicy(), "google_compute_shared_vpc_host_project": resourceComputeSharedVpcHostProject(), "google_compute_shared_vpc_service_project": resourceComputeSharedVpcServiceProject(), + "google_compute_subnetwork_iam_binding": ResourceIamBindingWithImport(IamComputeSubnetworkSchema, NewComputeSubnetworkIamUpdater, ComputeSubnetworkIdParseFunc), + "google_compute_subnetwork_iam_member": ResourceIamMemberWithImport(IamComputeSubnetworkSchema, NewComputeSubnetworkIamUpdater, ComputeSubnetworkIdParseFunc), + "google_compute_subnetwork_iam_policy": ResourceIamPolicyWithImport(IamComputeSubnetworkSchema, NewComputeSubnetworkIamUpdater, ComputeSubnetworkIdParseFunc), "google_compute_target_pool": resourceComputeTargetPool(), "google_container_cluster": resourceContainerCluster(), "google_container_node_pool": resourceContainerNodePool(), diff --git a/google/resource_compute_subnetwork_iam_test.go b/google/resource_compute_subnetwork_iam_test.go index 93cfad7a..fb25002a 100644 --- a/google/resource_compute_subnetwork_iam_test.go +++ b/google/resource_compute_subnetwork_iam_test.go @@ -1,3 +1,247 @@ package google -// Magic Modules doesn't let us remove files - blank out beta-only common-compile files for now. +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccComputeSubnetworkIamBinding(t *testing.T) { + t.Parallel() + + account := acctest.RandomWithPrefix("tf-test") + role := "roles/compute.networkUser" + region := getTestRegionFromEnv() + subnetwork := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccComputeSubnetworkIamBinding_basic(account, region, subnetwork, role), + }, + { + ResourceName: "google_compute_subnetwork_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", region, subnetwork, role), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccComputeSubnetworkIamBinding_update(account, region, subnetwork, role), + }, + { + ResourceName: "google_compute_subnetwork_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", region, subnetwork, role), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSubnetworkIamMember(t *testing.T) { + t.Parallel() + + project := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + role := "roles/compute.networkUser" + region := getTestRegionFromEnv() + subnetwork := 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: testAccComputeSubnetworkIamMember_basic(account, region, subnetwork, role), + }, + { + ResourceName: "google_compute_subnetwork_iam_member.foo", + ImportStateId: fmt.Sprintf("%s/%s %s serviceAccount:%s@%s.iam.gserviceaccount.com", region, subnetwork, role, account, project), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSubnetworkIamPolicy(t *testing.T) { + t.Parallel() + + project := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + role := "roles/compute.networkUser" + region := getTestRegionFromEnv() + subnetwork := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccComputeSubnetworkIamPolicy_basic(account, region, subnetwork, role), + }, + // Test a few import formats + { + ResourceName: "google_compute_subnetwork_iam_policy.foo", + ImportStateId: fmt.Sprintf("projects/%s/regions/%s/subnetworks/%s", project, region, subnetwork), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_compute_subnetwork_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s/%s/%s", project, region, subnetwork), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_compute_subnetwork_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s/%s", region, subnetwork), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_compute_subnetwork_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s", subnetwork), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccComputeSubnetworkIamBinding_basic(account, region, subnetworkName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_compute_network" "network" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "%s" + region = "%s" + ip_cidr_range = "10.1.0.0/16" + network = "${google_compute_network.network.name}" +} + +resource "google_compute_subnetwork_iam_binding" "foo" { + project = "${google_compute_subnetwork.subnetwork.project}" + region = "${google_compute_subnetwork.subnetwork.region}" + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + role = "%s" + members = ["serviceAccount:${google_service_account.test_account.email}"] +} +`, account, subnetworkName, subnetworkName, region, roleId) +} + +func testAccComputeSubnetworkIamBinding_update(account, region, subnetworkName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_service_account" "test_account_2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_compute_network" "network" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "%s" + region = "%s" + ip_cidr_range = "10.1.0.0/16" + network = "${google_compute_network.network.name}" +} + +resource "google_compute_subnetwork_iam_binding" "foo" { + project = "${google_compute_subnetwork.subnetwork.project}" + region = "${google_compute_subnetwork.subnetwork.region}" + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + role = "%s" + members = [ + "serviceAccount:${google_service_account.test_account.email}", + "serviceAccount:${google_service_account.test_account_2.email}" + ] +} +`, account, account, subnetworkName, subnetworkName, region, roleId) +} + +func testAccComputeSubnetworkIamMember_basic(account, region, subnetworkName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_compute_network" "network" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "%s" + region = "%s" + ip_cidr_range = "10.1.0.0/16" + network = "${google_compute_network.network.name}" +} + +resource "google_compute_subnetwork_iam_member" "foo" { + project = "${google_compute_subnetwork.subnetwork.project}" + region = "${google_compute_subnetwork.subnetwork.region}" + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + role = "%s" + member = "serviceAccount:${google_service_account.test_account.email}" +} +`, account, subnetworkName, subnetworkName, region, roleId) +} + +func testAccComputeSubnetworkIamPolicy_basic(account, region, subnetworkName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_compute_network" "network" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "%s" + region = "%s" + ip_cidr_range = "10.1.0.0/16" + network = "${google_compute_network.network.name}" +} + +data "google_iam_policy" "foo" { + binding { + role = "%s" + + members = ["serviceAccount:${google_service_account.test_account.email}"] + } +} + +resource "google_compute_subnetwork_iam_policy" "foo" { + project = "${google_compute_subnetwork.subnetwork.project}" + region = "${google_compute_subnetwork.subnetwork.region}" + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + policy_data = "${data.google_iam_policy.foo.policy_data}" +} +`, account, subnetworkName, subnetworkName, region, roleId) +}