From ec0d1f6a1874ce03d3a855daa804b5ecf020abc1 Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Tue, 16 Oct 2018 15:09:08 -0700 Subject: [PATCH 01/11] Add OiCS link generation to the Terraform docs. --- provider/terraform/custom_code.rb | 16 ++++++++++++++++ templates/terraform/resource.html.markdown.erb | 14 +++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/provider/terraform/custom_code.rb b/provider/terraform/custom_code.rb index 3a77e7ae8342..ff3729b824c2 100644 --- a/provider/terraform/custom_code.rb +++ b/provider/terraform/custom_code.rb @@ -123,6 +123,22 @@ def config_example substitute_example_paths body end + def oics_link + hash = { + cloudshell_git_repo: 'https://github.com/terraform-google-modules/docs-examples.git', + cloudshell_working_dir: @name, + cloudshell_image: 'gcr.io/graphite-cloud-shell-images/terraform:latest', + open_in_editor: 'main.tf', + cloudshell_print: './motd', + cloudshell_tutorial: './tutorial.md' + } + URI::HTTPS.build( + host: 'console.cloud.google.com', + path: '/cloudshell/open', + query: URI.encode_www_form(hash) + ) + end + def substitute_test_paths(config) config = config.gsub('path/to/private.key', 'test-fixtures/ssl_cert/test.key') config.gsub('path/to/certificate.crt', 'test-fixtures/ssl_cert/test.crt') diff --git a/templates/terraform/resource.html.markdown.erb b/templates/terraform/resource.html.markdown.erb index dcbbed33dcbb..5c92f49f555c 100644 --- a/templates/terraform/resource.html.markdown.erb +++ b/templates/terraform/resource.html.markdown.erb @@ -77,11 +77,19 @@ To get more information about <%= object.name -%>, see: <% end -%> <% unless object.example.empty? -%> -## Example Usage + <%- object.example.each do |example| -%> + <%- unless example.skip_test -%> +
+ + Open in Cloud Shell + +
+ <%- end -%> +## Example Usage - <%= example.name.camelize(:upper).uncombine %> + -<% object.example.each do |example| -%> <%= example.config_documentation -%> -<%- end %> + <%- end %> <%- end -%> ## Argument Reference From 06d12b3ebb5ef0e127c2830d34f36f8ad3f0e65f Mon Sep 17 00:00:00 2001 From: Dana Hoffman Date: Tue, 16 Oct 2018 17:04:09 -0700 Subject: [PATCH 02/11] changes to (r)igm so that resources can be converted from beta back to ga (#564) Merged PR #564. --- build/inspec | 2 +- build/terraform | 2 +- ...resource_compute_instance_group_manager.go | 162 ++++-- ...e_compute_region_instance_group_manager.go | 118 +++- ...rce_compute_instance_group_manager_test.go | 530 +++--------------- ...pute_region_instance_group_manager_test.go | 522 +++-------------- 6 files changed, 368 insertions(+), 968 deletions(-) diff --git a/build/inspec b/build/inspec index faf47ee4071d..257d3f760307 160000 --- a/build/inspec +++ b/build/inspec @@ -1 +1 @@ -Subproject commit faf47ee4071d8c4c4bc7ca62cb904ffa0fbd1271 +Subproject commit 257d3f760307559938568aeed74261df5cde4b87 diff --git a/build/terraform b/build/terraform index 1a9338f9bfbf..56f6530712f9 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 1a9338f9bfbf9636d84fe30eb750afc12221b441 +Subproject commit 56f6530712f9cbfa5db40e9238d35703931fa951 diff --git a/provider/terraform/resources/resource_compute_instance_group_manager.go b/provider/terraform/resources/resource_compute_instance_group_manager.go index b7b71179befc..584316b56d9c 100644 --- a/provider/terraform/resources/resource_compute_instance_group_manager.go +++ b/provider/terraform/resources/resource_compute_instance_group_manager.go @@ -3,6 +3,7 @@ package google import ( "fmt" "log" + "regexp" "strings" "time" @@ -14,6 +15,11 @@ import ( "google.golang.org/api/compute/v1" ) +var ( + instanceGroupManagerIdRegex = regexp.MustCompile("^" + ProjectRegex + "/[a-z0-9-]+/[a-z0-9-]+$") + instanceGroupManagerIdNameRegex = regexp.MustCompile("^[a-z0-9-]+$") +) + func resourceComputeInstanceGroupManager() *schema.Resource { return &schema.Resource{ Create: resourceComputeInstanceGroupManagerCreate, @@ -322,7 +328,7 @@ func resourceComputeInstanceGroupManagerCreate(d *schema.ResourceData, meta inte } // It probably maybe worked, so store the ID now - d.SetId(manager.Name) + d.SetId(instanceGroupManagerId{Project: project, Zone: zone, Name: manager.Name}.terraformId()) // Wait for the operation to complete err = computeSharedOperationWait(config.clientCompute, op, project, "Creating InstanceGroupManager") @@ -372,39 +378,45 @@ func flattenFixedOrPercent(fixedOrPercent *computeBeta.FixedOrPercent) []map[str func getManager(d *schema.ResourceData, meta interface{}) (*computeBeta.InstanceGroupManager, error) { config := meta.(*Config) - - project, err := getProject(d, config) + zonalID, err := parseInstanceGroupManagerId(d.Id()) if err != nil { return nil, err } - - region, err := getRegion(d, config) - if err != nil { - return nil, err + if zonalID.Project == "" { + project, err := getProject(d, config) + if err != nil { + return nil, err + } + zonalID.Project = project + } + if zonalID.Zone == "" { + zonalID.Zone, _ = getZone(d, config) } getInstanceGroupManager := func(zone string) (interface{}, error) { - return config.clientComputeBeta.InstanceGroupManagers.Get(project, zone, d.Id()).Do() + return config.clientComputeBeta.InstanceGroupManagers.Get(zonalID.Project, zone, zonalID.Name).Do() } var manager *computeBeta.InstanceGroupManager - var e error - if zone, _ := getZone(d, config); zone != "" { - manager, e = config.clientComputeBeta.InstanceGroupManagers.Get(project, zone, d.Id()).Do() - - if e != nil { - return nil, handleNotFoundError(e, d, fmt.Sprintf("Instance Group Manager %q", d.Get("name").(string))) - } - } else { + if zonalID.Zone == "" { // If the resource was imported, the only info we have is the ID. Try to find the resource // by searching in the region of the project. - var resource interface{} - resource, e = getZonalBetaResourceFromRegion(getInstanceGroupManager, region, config.clientComputeBeta, project) - if e != nil { - return nil, e + region, err := getRegion(d, config) + if err != nil { + return nil, err + } + resource, err := getZonalBetaResourceFromRegion(getInstanceGroupManager, region, config.clientComputeBeta, zonalID.Project) + if err != nil { + return nil, err + } + if resource != nil { + manager = resource.(*computeBeta.InstanceGroupManager) + } + } else { + manager, err = config.clientComputeBeta.InstanceGroupManagers.Get(zonalID.Project, zonalID.Zone, zonalID.Name).Do() + if err != nil { + return nil, handleNotFoundError(err, d, fmt.Sprintf("Instance Group Manager %q", zonalID.Name)) } - - manager = resource.(*computeBeta.InstanceGroupManager) } if manager == nil { @@ -528,14 +540,21 @@ func performZoneUpdate(config *Config, id string, updateStrategy string, rolling func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - project, err := getProject(d, config) + zonalID, err := parseInstanceGroupManagerId(d.Id()) if err != nil { return err } - - zone, err := getZone(d, config) - if err != nil { - return err + if zonalID.Project == "" { + zonalID.Project, err = getProject(d, config) + if err != nil { + return err + } + } + if zonalID.Zone == "" { + zonalID.Zone, err = getZone(d, config) + if err != nil { + return err + } } d.Partial(true) @@ -555,14 +574,14 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte } op, err := config.clientComputeBeta.InstanceGroupManagers.SetTargetPools( - project, zone, d.Id(), setTargetPools).Do() + zonalID.Project, zonalID.Zone, zonalID.Name, setTargetPools).Do() if err != nil { return fmt.Errorf("Error updating InstanceGroupManager: %s", err) } // Wait for the operation to complete - err = computeSharedOperationWait(config.clientCompute, op, project, "Updating InstanceGroupManager") + err = computeSharedOperationWait(config.clientCompute, op, zonalID.Project, "Updating InstanceGroupManager") if err != nil { return err } @@ -581,14 +600,14 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte // Make the request: op, err := config.clientComputeBeta.InstanceGroups.SetNamedPorts( - project, zone, d.Id(), setNamedPorts).Do() + zonalID.Project, zonalID.Zone, zonalID.Name, setNamedPorts).Do() if err != nil { return fmt.Errorf("Error updating InstanceGroupManager: %s", err) } // Wait for the operation to complete: - err = computeSharedOperationWait(config.clientCompute, op, project, "Updating InstanceGroupManager") + err = computeSharedOperationWait(config.clientCompute, op, zonalID.Project, "Updating InstanceGroupManager") if err != nil { return err } @@ -599,14 +618,14 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte if d.HasChange("target_size") { targetSize := int64(d.Get("target_size").(int)) op, err := config.clientComputeBeta.InstanceGroupManagers.Resize( - project, zone, d.Id(), targetSize).Do() + zonalID.Project, zonalID.Zone, zonalID.Name, targetSize).Do() if err != nil { return fmt.Errorf("Error updating InstanceGroupManager: %s", err) } // Wait for the operation to complete - err = computeSharedOperationWait(config.clientCompute, op, project, "Updating InstanceGroupManager") + err = computeSharedOperationWait(config.clientCompute, op, zonalID.Project, "Updating InstanceGroupManager") if err != nil { return err } @@ -622,14 +641,14 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte } op, err := config.clientComputeBeta.InstanceGroupManagers.SetAutoHealingPolicies( - project, zone, d.Id(), setAutoHealingPoliciesRequest).Do() + zonalID.Project, zonalID.Zone, zonalID.Name, setAutoHealingPoliciesRequest).Do() if err != nil { return fmt.Errorf("Error updating AutoHealingPolicies: %s", err) } // Wait for the operation to complete - err = computeSharedOperationWait(config.clientCompute, op, project, "Updating AutoHealingPolicies") + err = computeSharedOperationWait(config.clientCompute, op, zonalID.Project, "Updating AutoHealingPolicies") if err != nil { return err } @@ -644,21 +663,21 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte InstanceTemplate: d.Get("instance_template").(string), } - op, err := config.clientComputeBeta.InstanceGroupManagers.SetInstanceTemplate(project, zone, d.Id(), setInstanceTemplate).Do() + op, err := config.clientComputeBeta.InstanceGroupManagers.SetInstanceTemplate(zonalID.Project, zonalID.Zone, zonalID.Name, setInstanceTemplate).Do() if err != nil { return fmt.Errorf("Error updating InstanceGroupManager: %s", err) } // Wait for the operation to complete - err = computeSharedOperationWait(config.clientCompute, op, project, "Updating InstanceGroupManager") + err = computeSharedOperationWait(config.clientCompute, op, zonalID.Project, "Updating InstanceGroupManager") if err != nil { return err } updateStrategy := d.Get("update_strategy").(string) rollingUpdatePolicy := expandUpdatePolicy(d.Get("rolling_update_policy").([]interface{})) - err = performZoneUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, nil, project, zone) + err = performZoneUpdate(config, zonalID.Name, updateStrategy, rollingUpdatePolicy, nil, zonalID.Project, zonalID.Zone) d.SetPartial("instance_template") } @@ -667,7 +686,7 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte updateStrategy := d.Get("update_strategy").(string) rollingUpdatePolicy := expandUpdatePolicy(d.Get("rolling_update_policy").([]interface{})) versions := expandVersions(d.Get("version").([]interface{})) - err = performZoneUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, versions, project, zone) + err = performZoneUpdate(config, zonalID.Name, updateStrategy, rollingUpdatePolicy, versions, zonalID.Project, zonalID.Zone) if err != nil { return err } @@ -683,22 +702,31 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte func resourceComputeInstanceGroupManagerDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - project, err := getProject(d, config) + zonalID, err := parseInstanceGroupManagerId(d.Id()) if err != nil { return err } - zone, err := getZone(d, config) - if err != nil { - return err + if zonalID.Project == "" { + zonalID.Project, err = getProject(d, config) + if err != nil { + return err + } + } + + if zonalID.Zone == "" { + zonalID.Zone, err = getZone(d, config) + if err != nil { + return err + } } - op, err := config.clientComputeBeta.InstanceGroupManagers.Delete(project, zone, d.Id()).Do() + op, err := config.clientComputeBeta.InstanceGroupManagers.Delete(zonalID.Project, zonalID.Zone, zonalID.Name).Do() attempt := 0 for err != nil && attempt < 20 { attempt++ time.Sleep(2000 * time.Millisecond) - op, err = config.clientComputeBeta.InstanceGroupManagers.Delete(project, zone, d.Id()).Do() + op, err = config.clientComputeBeta.InstanceGroupManagers.Delete(zonalID.Project, zonalID.Zone, zonalID.Name).Do() } if err != nil { @@ -708,7 +736,7 @@ func resourceComputeInstanceGroupManagerDelete(d *schema.ResourceData, meta inte currentSize := int64(d.Get("target_size").(int)) // Wait for the operation to complete - err = computeSharedOperationWait(config.clientCompute, op, project, "Deleting InstanceGroupManager") + err = computeSharedOperationWait(config.clientCompute, op, zonalID.Project, "Deleting InstanceGroupManager") for err != nil && currentSize > 0 { if !strings.Contains(err.Error(), "timeout") { @@ -716,7 +744,7 @@ func resourceComputeInstanceGroupManagerDelete(d *schema.ResourceData, meta inte } instanceGroup, err := config.clientComputeBeta.InstanceGroups.Get( - project, zone, d.Id()).Do() + zonalID.Project, zonalID.Zone, zonalID.Name).Do() if err != nil { return fmt.Errorf("Error getting instance group size: %s", err) } @@ -729,7 +757,7 @@ func resourceComputeInstanceGroupManagerDelete(d *schema.ResourceData, meta inte log.Printf("[INFO] timeout occured, but instance group is shrinking (%d < %d)", instanceGroupSize, currentSize) currentSize = instanceGroupSize - err = computeSharedOperationWait(config.clientCompute, op, project, "Deleting InstanceGroupManager") + err = computeSharedOperationWait(config.clientCompute, op, zonalID.Project, "Deleting InstanceGroupManager") } d.SetId("") @@ -838,5 +866,43 @@ func flattenAutoHealingPolicies(autoHealingPolicies []*computeBeta.InstanceGroup func resourceInstanceGroupManagerStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { d.Set("wait_for_instances", false) + zonalID, err := parseInstanceGroupManagerId(d.Id()) + if err != nil { + return nil, err + } + if zonalID.Zone == "" || zonalID.Project == "" { + return nil, fmt.Errorf("Invalid instance group manager import ID. Expecting {projectId}/{zone}/{name}.") + } + d.Set("project", zonalID.Project) + d.Set("zone", zonalID.Zone) + d.Set("name", zonalID.Name) return []*schema.ResourceData{d}, nil } + +type instanceGroupManagerId struct { + Project string + Zone string + Name string +} + +func (i instanceGroupManagerId) terraformId() string { + return fmt.Sprintf("%s/%s/%s", i.Project, i.Zone, i.Name) +} + +func parseInstanceGroupManagerId(id string) (*instanceGroupManagerId, error) { + switch { + case instanceGroupManagerIdRegex.MatchString(id): + parts := strings.Split(id, "/") + return &instanceGroupManagerId{ + Project: parts[0], + Zone: parts[1], + Name: parts[2], + }, nil + case instanceGroupManagerIdNameRegex.MatchString(id): + return &instanceGroupManagerId{ + Name: id, + }, nil + default: + return nil, fmt.Errorf("Invalid instance group manager specifier. Expecting either {projectId}/{zone}/{name} or {name}, where {projectId} and {zone} will be derived from the provider.") + } +} diff --git a/provider/terraform/resources/resource_compute_region_instance_group_manager.go b/provider/terraform/resources/resource_compute_region_instance_group_manager.go index 648083b362ed..b11f7dfc29cf 100644 --- a/provider/terraform/resources/resource_compute_region_instance_group_manager.go +++ b/provider/terraform/resources/resource_compute_region_instance_group_manager.go @@ -3,6 +3,7 @@ package google import ( "fmt" "log" + "regexp" "strings" "time" @@ -14,6 +15,11 @@ import ( computeBeta "google.golang.org/api/compute/v0.beta" ) +var ( + regionInstanceGroupManagerIdRegex = regexp.MustCompile("^" + ProjectRegex + "/[a-z0-9-]+/[a-z0-9-]+$") + regionInstanceGroupManagerIdNameRegex = regexp.MustCompile("^[a-z0-9-]+$") +) + func resourceComputeRegionInstanceGroupManager() *schema.Resource { return &schema.Resource{ Create: resourceComputeRegionInstanceGroupManagerCreate, @@ -272,6 +278,11 @@ func resourceComputeRegionInstanceGroupManagerCreate(d *schema.ResourceData, met return err } + region, err := getRegion(d, config) + if err != nil { + return err + } + if _, ok := d.GetOk("rolling_update_policy"); d.Get("update_strategy") == "ROLLING_UPDATE" && !ok { return fmt.Errorf("[rolling_update_policy] must be set when 'update_strategy' is set to 'ROLLING_UPDATE'") } @@ -291,13 +302,13 @@ func resourceComputeRegionInstanceGroupManagerCreate(d *schema.ResourceData, met ForceSendFields: []string{"TargetSize"}, } - op, err := config.clientComputeBeta.RegionInstanceGroupManagers.Insert(project, d.Get("region").(string), manager).Do() + op, err := config.clientComputeBeta.RegionInstanceGroupManagers.Insert(project, region, manager).Do() if err != nil { return fmt.Errorf("Error creating RegionInstanceGroupManager: %s", err) } - d.SetId(manager.Name) + d.SetId(regionInstanceGroupManagerId{Project: project, Region: region, Name: manager.Name}.terraformId()) // Wait for the operation to complete err = computeSharedOperationWait(config.clientCompute, op, project, "Creating InstanceGroupManager") @@ -312,19 +323,28 @@ type getInstanceManagerFunc func(*schema.ResourceData, interface{}) (*computeBet func getRegionalManager(d *schema.ResourceData, meta interface{}) (*computeBeta.InstanceGroupManager, error) { config := meta.(*Config) - project, err := getProject(d, config) + regionalID, err := parseRegionInstanceGroupManagerId(d.Id()) if err != nil { return nil, err } - region, err := getRegion(d, config) - if err != nil { - return nil, err + if regionalID.Project == "" { + regionalID.Project, err = getProject(d, config) + if err != nil { + return nil, err + } + } + + if regionalID.Region == "" { + regionalID.Region, err = getRegion(d, config) + if err != nil { + return nil, err + } } - manager, err := config.clientComputeBeta.RegionInstanceGroupManagers.Get(project, region, d.Id()).Do() + manager, err := config.clientComputeBeta.RegionInstanceGroupManagers.Get(regionalID.Project, regionalID.Region, regionalID.Name).Do() if err != nil { - return nil, handleNotFoundError(err, d, fmt.Sprintf("Region Instance Manager %q", d.Get("name").(string))) + return nil, handleNotFoundError(err, d, fmt.Sprintf("Region Instance Manager %q", regionalID.Name)) } return manager, nil @@ -352,10 +372,16 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta return err } - project, err := getProject(d, config) + regionalID, err := parseRegionInstanceGroupManagerId(d.Id()) if err != nil { return err } + if regionalID.Project == "" { + regionalID.Project, err = getProject(d, config) + if err != nil { + return err + } + } d.Set("base_instance_name", manager.BaseInstanceName) d.Set("instance_template", manager.InstanceTemplate) @@ -365,7 +391,7 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta d.Set("name", manager.Name) d.Set("region", GetResourceNameFromSelfLink(manager.Region)) d.Set("description", manager.Description) - d.Set("project", project) + d.Set("project", regionalID.Project) d.Set("target_size", manager.TargetSize) d.Set("target_pools", manager.TargetPools) d.Set("named_port", flattenNamedPortsBeta(manager.NamedPorts)) @@ -463,7 +489,10 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met return err } - region := d.Get("region").(string) + region, err := getRegion(d, config) + if err != nil { + return err + } d.Partial(true) @@ -481,7 +510,7 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met } op, err := config.clientComputeBeta.RegionInstanceGroupManagers.SetTargetPools( - project, region, d.Id(), setTargetPools).Do() + project, region, d.Get("name").(string), setTargetPools).Do() if err != nil { return fmt.Errorf("Error updating RegionInstanceGroupManager: %s", err) @@ -503,7 +532,7 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met } op, err := config.clientComputeBeta.RegionInstanceGroupManagers.SetInstanceTemplate( - project, region, d.Id(), setInstanceTemplate).Do() + project, region, d.Get("name").(string), setInstanceTemplate).Do() if err != nil { return fmt.Errorf("Error updating RegionInstanceGroupManager: %s", err) @@ -526,7 +555,7 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met updateStrategy := d.Get("update_strategy").(string) rollingUpdatePolicy := expandUpdatePolicy(d.Get("rolling_update_policy").([]interface{})) versions := expandVersions(d.Get("version").([]interface{})) - err = performRegionUpdate(config, d.Id(), updateStrategy, rollingUpdatePolicy, versions, project, region) + err = performRegionUpdate(config, d.Get("name").(string), updateStrategy, rollingUpdatePolicy, versions, project, region) if err != nil { return err } @@ -543,7 +572,7 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met // Make the request: op, err := config.clientComputeBeta.RegionInstanceGroups.SetNamedPorts( - project, region, d.Id(), setNamedPorts).Do() + project, region, d.Get("name").(string), setNamedPorts).Do() if err != nil { return fmt.Errorf("Error updating RegionInstanceGroupManager: %s", err) @@ -561,7 +590,7 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met if d.HasChange("target_size") { targetSize := int64(d.Get("target_size").(int)) op, err := config.clientComputeBeta.RegionInstanceGroupManagers.Resize( - project, region, d.Id(), targetSize).Do() + project, region, d.Get("name").(string), targetSize).Do() if err != nil { return fmt.Errorf("Error resizing RegionInstanceGroupManager: %s", err) @@ -583,7 +612,7 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met } op, err := config.clientComputeBeta.RegionInstanceGroupManagers.SetAutoHealingPolicies( - project, region, d.Id(), setAutoHealingPoliciesRequest).Do() + project, region, d.Get("name").(string), setAutoHealingPoliciesRequest).Do() if err != nil { return fmt.Errorf("Error updating AutoHealingPolicies: %s", err) @@ -606,21 +635,33 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met func resourceComputeRegionInstanceGroupManagerDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - project, err := getProject(d, config) + regionalID, err := parseRegionInstanceGroupManagerId(d.Id()) if err != nil { return err } - region := d.Get("region").(string) + if regionalID.Project == "" { + regionalID.Project, err = getProject(d, config) + if err != nil { + return err + } + } - op, err := config.clientComputeBeta.RegionInstanceGroupManagers.Delete(project, region, d.Id()).Do() + if regionalID.Region == "" { + regionalID.Region, err = getRegion(d, config) + if err != nil { + return err + } + } + + op, err := config.clientComputeBeta.RegionInstanceGroupManagers.Delete(regionalID.Project, regionalID.Region, regionalID.Name).Do() if err != nil { return fmt.Errorf("Error deleting region instance group manager: %s", err) } // Wait for the operation to complete - err = computeSharedOperationWaitTime(config.clientCompute, op, project, int(d.Timeout(schema.TimeoutDelete).Minutes()), "Deleting RegionInstanceGroupManager") + err = computeSharedOperationWaitTime(config.clientCompute, op, regionalID.Project, int(d.Timeout(schema.TimeoutDelete).Minutes()), "Deleting RegionInstanceGroupManager") d.SetId("") return nil @@ -664,5 +705,40 @@ func hashZoneFromSelfLinkOrResourceName(value interface{}) int { func resourceRegionInstanceGroupManagerStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { d.Set("wait_for_instances", false) + regionalID, err := parseRegionInstanceGroupManagerId(d.Id()) + if err != nil { + return nil, err + } + d.Set("project", regionalID.Project) + d.Set("region", regionalID.Region) + d.Set("name", regionalID.Name) return []*schema.ResourceData{d}, nil } + +type regionInstanceGroupManagerId struct { + Project string + Region string + Name string +} + +func (r regionInstanceGroupManagerId) terraformId() string { + return fmt.Sprintf("%s/%s/%s", r.Project, r.Region, r.Name) +} + +func parseRegionInstanceGroupManagerId(id string) (*regionInstanceGroupManagerId, error) { + switch { + case regionInstanceGroupManagerIdRegex.MatchString(id): + parts := strings.Split(id, "/") + return ®ionInstanceGroupManagerId{ + Project: parts[0], + Region: parts[1], + Name: parts[2], + }, nil + case regionInstanceGroupManagerIdNameRegex.MatchString(id): + return ®ionInstanceGroupManagerId{ + Name: id, + }, nil + default: + return nil, fmt.Errorf("Invalid region instance group manager specifier. Expecting either {projectId}/{region}/{name} or {name}, where {projectId} and {region} will be derived from the provider.") + } +} diff --git a/provider/terraform/tests/resource_compute_instance_group_manager_test.go b/provider/terraform/tests/resource_compute_instance_group_manager_test.go index ef3d10e93cd5..5326f18622d8 100644 --- a/provider/terraform/tests/resource_compute_instance_group_manager_test.go +++ b/provider/terraform/tests/resource_compute_instance_group_manager_test.go @@ -2,16 +2,8 @@ package google import ( "fmt" - "reflect" - "strconv" - "strings" "testing" - computeBeta "google.golang.org/api/compute/v0.beta" - "google.golang.org/api/compute/v1" - - "sort" - "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" @@ -20,8 +12,6 @@ import ( func TestAccInstanceGroupManager_basic(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -32,21 +22,15 @@ func TestAccInstanceGroupManager_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_basic(template, target, igm1, igm2), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-basic", &manager), - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-no-tp", &manager), - ), }, - resource.TestStep{ + { ResourceName: "google_compute_instance_group_manager.igm-basic", ImportState: true, ImportStateVerify: true, }, - resource.TestStep{ + { ResourceName: "google_compute_instance_group_manager.igm-no-tp", ImportState: true, ImportStateVerify: true, @@ -58,8 +42,6 @@ func TestAccInstanceGroupManager_basic(t *testing.T) { func TestAccInstanceGroupManager_targetSizeZero(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - templateName := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igmName := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -68,26 +50,21 @@ func TestAccInstanceGroupManager_targetSizeZero(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_targetSizeZero(templateName, igmName), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-basic", &manager), - ), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, }, }, }) - - if manager.TargetSize != 0 { - t.Errorf("Expected target_size to be 0, got %d", manager.TargetSize) - } } func TestAccInstanceGroupManager_update(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - template1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -99,31 +76,21 @@ func TestAccInstanceGroupManager_update(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_update(template1, target1, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-update", &manager), - testAccCheckInstanceGroupManagerUpdated("google_compute_instance_group_manager.igm-update", 2, []string{target1}, template1), - testAccCheckInstanceGroupManagerNamedPorts( - "google_compute_instance_group_manager.igm-update", - map[string]int64{"customhttp": 8080}, - &manager), - ), }, - resource.TestStep{ + { + ResourceName: "google_compute_instance_group_manager.igm-update", + ImportState: true, + ImportStateVerify: true, + }, + { Config: testAccInstanceGroupManager_update2(template1, target1, target2, template2, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-update", &manager), - testAccCheckInstanceGroupManagerUpdated( - "google_compute_instance_group_manager.igm-update", 3, - []string{target1, target2}, template2), - testAccCheckInstanceGroupManagerNamedPorts( - "google_compute_instance_group_manager.igm-update", - map[string]int64{"customhttp": 8080, "customhttps": 8443}, - &manager), - ), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-update", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -132,8 +99,6 @@ func TestAccInstanceGroupManager_update(t *testing.T) { func TestAccInstanceGroupManager_updateLifecycle(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - tag1 := "tag1" tag2 := "tag2" igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -143,21 +108,21 @@ func TestAccInstanceGroupManager_updateLifecycle(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_updateLifecycle(tag1, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-update", &manager), - ), }, - resource.TestStep{ + { + ResourceName: "google_compute_instance_group_manager.igm-update", + ImportState: true, + ImportStateVerify: true, + }, + { Config: testAccInstanceGroupManager_updateLifecycle(tag2, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-update", &manager), - testAccCheckInstanceGroupManagerTemplateTags( - "google_compute_instance_group_manager.igm-update", []string{tag2}), - ), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-update", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -166,7 +131,6 @@ func TestAccInstanceGroupManager_updateLifecycle(t *testing.T) { func TestAccInstanceGroupManager_updateStrategy(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) resource.Test(t, resource.TestCase{ @@ -174,14 +138,13 @@ func TestAccInstanceGroupManager_updateStrategy(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_updateStrategy(igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-update-strategy", &manager), - testAccCheckInstanceGroupManagerUpdateStrategy( - "google_compute_instance_group_manager.igm-update-strategy", "NONE"), - ), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-update-strategy", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -190,8 +153,6 @@ func TestAccInstanceGroupManager_updateStrategy(t *testing.T) { func TestAccInstanceGroupManager_rollingUpdatePolicy(t *testing.T) { t.Parallel() - var manager computeBeta.InstanceGroupManager - igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) resource.Test(t, resource.TestCase{ @@ -201,43 +162,11 @@ func TestAccInstanceGroupManager_rollingUpdatePolicy(t *testing.T) { Steps: []resource.TestStep{ resource.TestStep{ Config: testAccInstanceGroupManager_rollingUpdatePolicy(igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerBetaExists( - "google_compute_instance_group_manager.igm-rolling-update-policy", &manager), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "update_strategy", "ROLLING_UPDATE"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.type", "PROACTIVE"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.minimal_action", "REPLACE"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_surge_percent", "50"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_unavailable_percent", "50"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.min_ready_sec", "20"), - ), }, + // No import step because rolling updates are broken and the field will be removed in 2.0.0. + // TODO(danawillow): Remove this test once we've removed the field. resource.TestStep{ Config: testAccInstanceGroupManager_rollingUpdatePolicy2(igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerBetaExists( - "google_compute_instance_group_manager.igm-rolling-update-policy", &manager), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "update_strategy", "ROLLING_UPDATE"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.type", "PROACTIVE"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.minimal_action", "REPLACE"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_surge_fixed", "2"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_unavailable_fixed", "2"), - resource.TestCheckResourceAttr( - "google_compute_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.min_ready_sec", "20"), - testAccCheckInstanceGroupManagerRollingUpdatePolicy( - &manager, "google_compute_instance_group_manager.igm-rolling-update-policy"), - ), }, }, }) @@ -246,8 +175,6 @@ func TestAccInstanceGroupManager_rollingUpdatePolicy(t *testing.T) { func TestAccInstanceGroupManager_separateRegions(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - igm1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -256,14 +183,18 @@ func TestAccInstanceGroupManager_separateRegions(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_separateRegions(igm1, igm2), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-basic", &manager), - testAccCheckInstanceGroupManagerExists( - "google_compute_instance_group_manager.igm-basic-2", &manager), - ), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_compute_instance_group_manager.igm-basic-2", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -272,8 +203,6 @@ func TestAccInstanceGroupManager_separateRegions(t *testing.T) { func TestAccInstanceGroupManager_versions(t *testing.T) { t.Parallel() - var manager computeBeta.InstanceGroupManager - primaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) canaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -283,14 +212,10 @@ func TestAccInstanceGroupManager_versions(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_versions(primaryTemplate, canaryTemplate, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerBetaExists("google_compute_instance_group_manager.igm-basic", &manager), - testAccCheckInstanceGroupManagerVersions("google_compute_instance_group_manager.igm-basic", primaryTemplate, canaryTemplate), - ), }, - resource.TestStep{ + { ResourceName: "google_compute_instance_group_manager.igm-basic", ImportState: true, ImportStateVerify: true, @@ -302,8 +227,6 @@ func TestAccInstanceGroupManager_versions(t *testing.T) { func TestAccInstanceGroupManager_autoHealingPolicies(t *testing.T) { t.Parallel() - var manager computeBeta.InstanceGroupManager - template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -314,15 +237,10 @@ func TestAccInstanceGroupManager_autoHealingPolicies(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_autoHealingPolicies(template, target, igm, hck), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceGroupManagerBetaExists( - "google_compute_instance_group_manager.igm-basic", &manager), - testAccCheckInstanceGroupManagerAutoHealingPolicies("google_compute_instance_group_manager.igm-basic", hck, 10), - ), }, - resource.TestStep{ + { ResourceName: "google_compute_instance_group_manager.igm-basic", ImportState: true, ImportStateVerify: true, @@ -338,8 +256,6 @@ func TestAccInstanceGroupManager_autoHealingPolicies(t *testing.T) { func TestAccInstanceGroupManager_selfLinkStability(t *testing.T) { t.Parallel() - var manager computeBeta.InstanceGroupManager - template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -351,10 +267,13 @@ func TestAccInstanceGroupManager_selfLinkStability(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccInstanceGroupManager_selfLinkStability(template, target, igm, hck, autoscaler), - Check: testAccCheckInstanceGroupManagerBetaExists( - "google_compute_instance_group_manager.igm-basic", &manager), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -367,327 +286,24 @@ func testAccCheckInstanceGroupManagerDestroy(s *terraform.State) error { if rs.Type != "google_compute_instance_group_manager" { continue } - _, err := config.clientCompute.InstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() - if err == nil { - return fmt.Errorf("InstanceGroupManager still exists") - } - } - - return nil -} - -func testAccCheckInstanceGroupManagerExists(n string, manager *compute.InstanceGroupManager) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - found, err := config.clientCompute.InstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() - if err != nil { - return err - } - - if found.Name != rs.Primary.ID { - return fmt.Errorf("InstanceGroupManager not found") - } - - *manager = *found - - return nil - } -} - -func testAccCheckInstanceGroupManagerBetaExists(n string, manager *computeBeta.InstanceGroupManager) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - found, err := config.clientComputeBeta.InstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() - if err != nil { - return err - } - - if found.Name != rs.Primary.ID { - return fmt.Errorf("InstanceGroupManager not found") - } - - *manager = *found - - return nil - } -} - -func testAccCheckInstanceGroupManagerUpdated(n string, size int64, targetPools []string, template string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientCompute.InstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() - if err != nil { - return err - } - - // Cannot check the target pool as the instance creation is asynchronous. However, can - // check the target_size. - if manager.TargetSize != size { - return fmt.Errorf("instance count incorrect") - } - - tpNames := make([]string, 0, len(manager.TargetPools)) - for _, targetPool := range manager.TargetPools { - tpNames = append(tpNames, GetResourceNameFromSelfLink(targetPool)) - } - - sort.Strings(tpNames) - sort.Strings(targetPools) - if !reflect.DeepEqual(tpNames, targetPools) { - return fmt.Errorf("target pools incorrect. Expected %s, got %s", targetPools, tpNames) - } - - // check that the instance template updated - instanceTemplate, err := config.clientCompute.InstanceTemplates.Get( - config.Project, template).Do() - if err != nil { - return fmt.Errorf("Error reading instance template: %s", err) - } - - if instanceTemplate.Name != template { - return fmt.Errorf("instance template not updated") - } - - return nil - } -} - -func testAccCheckInstanceGroupManagerNamedPorts(n string, np map[string]int64, instanceGroupManager *compute.InstanceGroupManager) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientCompute.InstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() - if err != nil { - return err - } - - var found bool - for _, namedPort := range manager.NamedPorts { - found = false - for name, port := range np { - if namedPort.Name == name && namedPort.Port == port { - found = true - } - } - if !found { - return fmt.Errorf("named port incorrect") - } - } - - return nil - } -} - -func testAccCheckInstanceGroupManagerVersions(n string, primaryTemplate string, canaryTemplate string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientComputeBeta.InstanceGroupManagers.Get(config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() - if err != nil { - return err - } - - if len(manager.Versions) != 2 { - return fmt.Errorf("Expected # of versions to be 2, got %d", len(manager.Versions)) - } - - primaryVersion := manager.Versions[0] - if !strings.Contains(primaryVersion.InstanceTemplate, primaryTemplate) { - return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", primaryTemplate, primaryVersion.InstanceTemplate) - } - - canaryVersion := manager.Versions[1] - if !strings.Contains(canaryVersion.InstanceTemplate, canaryTemplate) { - return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", canaryTemplate, canaryVersion.InstanceTemplate) - } - - return nil - } -} - -func testAccCheckInstanceGroupManagerAutoHealingPolicies(n, hck string, initialDelaySec int64) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientComputeBeta.InstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() - if err != nil { - return err - } - - if len(manager.AutoHealingPolicies) != 1 { - return fmt.Errorf("Expected # of auto healing policies to be 1, got %d", len(manager.AutoHealingPolicies)) - } - autoHealingPolicy := manager.AutoHealingPolicies[0] - - if !strings.Contains(autoHealingPolicy.HealthCheck, hck) { - return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", hck, autoHealingPolicy.HealthCheck) - } - - if autoHealingPolicy.InitialDelaySec != initialDelaySec { - return fmt.Errorf("Expected auto healing policy inital delay to be %d, got %d", initialDelaySec, autoHealingPolicy.InitialDelaySec) - } - return nil - } -} - -func testAccCheckInstanceGroupManagerTemplateTags(n string, tags []string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientCompute.InstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + id, err := parseInstanceGroupManagerId(rs.Primary.ID) if err != nil { return err } - - // check that the instance template updated - instanceTemplate, err := config.clientCompute.InstanceTemplates.Get( - config.Project, GetResourceNameFromSelfLink(manager.InstanceTemplate)).Do() - if err != nil { - return fmt.Errorf("Error reading instance template: %s", err) + if id.Project == "" { + id.Project = config.Project } - - if !reflect.DeepEqual(instanceTemplate.Properties.Tags.Items, tags) { - return fmt.Errorf("instance template not updated") + if id.Zone == "" { + id.Zone = rs.Primary.Attributes["zone"] } - - return nil - } -} - -func testAccCheckInstanceGroupManagerUpdateStrategy(n, strategy string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - if rs.Primary.Attributes["update_strategy"] != strategy { - return fmt.Errorf("Expected strategy to be %s, got %s", - strategy, rs.Primary.Attributes["update_strategy"]) + _, err = config.clientCompute.InstanceGroupManagers.Get( + id.Project, id.Zone, id.Name).Do() + if err == nil { + return fmt.Errorf("InstanceGroupManager still exists") } - return nil } -} - -func testAccCheckInstanceGroupManagerRollingUpdatePolicy(manager *computeBeta.InstanceGroupManager, resource string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs := s.RootModule().Resources[resource] - - updatePolicy := manager.UpdatePolicy - surgeFixed, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.max_surge_fixed"], 10, 64) - if updatePolicy.MaxSurge.Fixed != surgeFixed { - return fmt.Errorf("Expected update policy MaxSurge to be %d, got %d", surgeFixed, updatePolicy.MaxSurge.Fixed) - } - - surgePercent, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.max_surge_percent"], 10, 64) - if updatePolicy.MaxSurge.Percent != surgePercent { - return fmt.Errorf("Expected update policy MaxSurge to be %d, got %d", surgePercent, updatePolicy.MaxSurge.Percent) - } - - unavailableFixed, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.max_unavailable_fixed"], 10, 64) - if updatePolicy.MaxUnavailable.Fixed != unavailableFixed { - return fmt.Errorf("Expected update policy MaxUnavailable to be %d, got %d", unavailableFixed, updatePolicy.MaxUnavailable.Fixed) - } - - unavailablePercent, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.max_unavailable_percent"], 10, 64) - if updatePolicy.MaxUnavailable.Percent != unavailablePercent { - return fmt.Errorf("Expected update policy MaxUnavailable to be %d, got %d", unavailablePercent, updatePolicy.MaxUnavailable.Percent) - } - - policyType := rs.Primary.Attributes["rolling_update_policy.0.type"] - if updatePolicy.Type != policyType { - return fmt.Errorf("Expected update policy Type to be \"%s\", got \"%s\"", policyType, updatePolicy.Type) - } - - policyAction := rs.Primary.Attributes["rolling_update_policy.0.minimal_action"] - if updatePolicy.MinimalAction != policyAction { - return fmt.Errorf("Expected update policy MinimalAction to be \"%s\", got \"%s\"", policyAction, updatePolicy.MinimalAction) - } - - minReadySec, _ := strconv.ParseInt(rs.Primary.Attributes["rolling_update_policy.0.min_ready_sec"], 10, 64) - if updatePolicy.MinReadySec != minReadySec { - return fmt.Errorf("Expected update policy MinReadySec to be %d, got %d", minReadySec, updatePolicy.MinReadySec) - } - return nil - } + return nil } func testAccInstanceGroupManager_basic(template, target, igm1, igm2 string) string { @@ -1019,7 +635,7 @@ func testAccInstanceGroupManager_updateStrategy(igm string) string { base_instance_name = "igm-update-strategy" zone = "us-central1-c" target_size = 2 - update_strategy = "NONE" + update_strategy = "REPLACE" named_port { name = "customhttp" port = 8080 diff --git a/provider/terraform/tests/resource_compute_region_instance_group_manager_test.go b/provider/terraform/tests/resource_compute_region_instance_group_manager_test.go index 1d51a9171400..83a1eb93d6e4 100644 --- a/provider/terraform/tests/resource_compute_region_instance_group_manager_test.go +++ b/provider/terraform/tests/resource_compute_region_instance_group_manager_test.go @@ -2,15 +2,9 @@ package google import ( "fmt" - "reflect" "strings" "testing" - computeBeta "google.golang.org/api/compute/v0.beta" - "google.golang.org/api/compute/v1" - - "sort" - "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" @@ -19,8 +13,6 @@ import ( func TestAccRegionInstanceGroupManager_basic(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -31,14 +23,18 @@ func TestAccRegionInstanceGroupManager_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccRegionInstanceGroupManager_basic(template, target, igm1, igm2), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-basic", &manager), - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-no-tp", &manager), - ), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-no-tp", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -47,8 +43,6 @@ func TestAccRegionInstanceGroupManager_basic(t *testing.T) { func TestAccRegionInstanceGroupManager_targetSizeZero(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - templateName := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igmName := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -57,26 +51,21 @@ func TestAccRegionInstanceGroupManager_targetSizeZero(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccRegionInstanceGroupManager_targetSizeZero(templateName, igmName), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-basic", &manager), - ), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, }, }, }) - - if manager.TargetSize != 0 { - t.Errorf("Expected target_size to be 0, got %d", manager.TargetSize) - } } func TestAccRegionInstanceGroupManager_update(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - template1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -88,36 +77,21 @@ func TestAccRegionInstanceGroupManager_update(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccRegionInstanceGroupManager_update(template1, target1, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-update", &manager), - testAccCheckRegionInstanceGroupManagerNamedPorts( - "google_compute_region_instance_group_manager.igm-update", - map[string]int64{"customhttp": 8080}, - &manager), - ), }, - resource.TestStep{ + { + ResourceName: "google_compute_region_instance_group_manager.igm-update", + ImportState: true, + ImportStateVerify: true, + }, + { Config: testAccRegionInstanceGroupManager_update2(template1, target1, target2, template2, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-update", &manager), - testAccCheckRegionInstanceGroupManagerUpdated( - "google_compute_region_instance_group_manager.igm-update", 3, - []string{target1, target2}, template2), - testAccCheckRegionInstanceGroupManagerNamedPorts( - "google_compute_region_instance_group_manager.igm-update", - map[string]int64{"customhttp": 8080, "customhttps": 8443}, - &manager), - ), }, - resource.TestStep{ - ResourceName: "google_compute_region_instance_group_manager.igm-update", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"update_strategy"}, + { + ResourceName: "google_compute_region_instance_group_manager.igm-update", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -126,8 +100,6 @@ func TestAccRegionInstanceGroupManager_update(t *testing.T) { func TestAccRegionInstanceGroupManager_updateLifecycle(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - tag1 := "tag1" tag2 := "tag2" igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -137,21 +109,21 @@ func TestAccRegionInstanceGroupManager_updateLifecycle(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccRegionInstanceGroupManager_updateLifecycle(tag1, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-update", &manager), - ), }, - resource.TestStep{ + { + ResourceName: "google_compute_region_instance_group_manager.igm-update", + ImportState: true, + ImportStateVerify: true, + }, + { Config: testAccRegionInstanceGroupManager_updateLifecycle(tag2, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-update", &manager), - testAccCheckRegionInstanceGroupManagerTemplateTags( - "google_compute_region_instance_group_manager.igm-update", []string{tag2}), - ), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-update", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -160,7 +132,6 @@ func TestAccRegionInstanceGroupManager_updateLifecycle(t *testing.T) { func TestAccRegionInstanceGroupManager_updateStrategy(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) resource.Test(t, resource.TestCase{ @@ -170,12 +141,11 @@ func TestAccRegionInstanceGroupManager_updateStrategy(t *testing.T) { Steps: []resource.TestStep{ resource.TestStep{ Config: testAccRegionInstanceGroupManager_updateStrategy(igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-update-strategy", &manager), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-update-strategy", "update_strategy", "NONE"), - ), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-update-strategy", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -184,8 +154,6 @@ func TestAccRegionInstanceGroupManager_updateStrategy(t *testing.T) { func TestAccRegionInstanceGroupManager_rollingUpdatePolicy(t *testing.T) { t.Parallel() - var manager computeBeta.InstanceGroupManager - igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) resource.Test(t, resource.TestCase{ @@ -195,53 +163,18 @@ func TestAccRegionInstanceGroupManager_rollingUpdatePolicy(t *testing.T) { Steps: []resource.TestStep{ resource.TestStep{ Config: testAccRegionInstanceGroupManager_rollingUpdatePolicy(igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerBetaExists( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", &manager), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "update_strategy", "ROLLING_UPDATE"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.type", "PROACTIVE"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.minimal_action", "REPLACE"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_surge_fixed", "2"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_unavailable_fixed", "2"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.min_ready_sec", "20"), - ), }, + // No import step because rolling updates are broken and the field will be removed in 2.0.0. + // TODO(danawillow): Remove this test once we've removed the field. resource.TestStep{ Config: testAccRegionInstanceGroupManager_rollingUpdatePolicy2(igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerBetaExists( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", &manager), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "update_strategy", "ROLLING_UPDATE"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.type", "PROACTIVE"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.minimal_action", "REPLACE"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_surge_fixed", "2"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.max_unavailable_fixed", "0"), - resource.TestCheckResourceAttr( - "google_compute_region_instance_group_manager.igm-rolling-update-policy", "rolling_update_policy.0.min_ready_sec", "10"), - testAccCheckInstanceGroupManagerRollingUpdatePolicy( - &manager, "google_compute_region_instance_group_manager.igm-rolling-update-policy"), - ), }, }, }) } - func TestAccRegionInstanceGroupManager_separateRegions(t *testing.T) { t.Parallel() - var manager compute.InstanceGroupManager - igm1 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -250,14 +183,18 @@ func TestAccRegionInstanceGroupManager_separateRegions(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccRegionInstanceGroupManager_separateRegions(igm1, igm2), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-basic", &manager), - testAccCheckRegionInstanceGroupManagerExists( - "google_compute_region_instance_group_manager.igm-basic-2", &manager), - ), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic-2", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -266,8 +203,6 @@ func TestAccRegionInstanceGroupManager_separateRegions(t *testing.T) { func TestAccRegionInstanceGroupManager_versions(t *testing.T) { t.Parallel() - var manager computeBeta.InstanceGroupManager - primaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) canaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -277,14 +212,10 @@ func TestAccRegionInstanceGroupManager_versions(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccRegionInstanceGroupManager_versions(primaryTemplate, canaryTemplate, igm), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerBetaExists("google_compute_region_instance_group_manager.igm-basic", &manager), - testAccCheckRegionInstanceGroupManagerVersions("google_compute_region_instance_group_manager.igm-basic", primaryTemplate, canaryTemplate), - ), }, - resource.TestStep{ + { ResourceName: "google_compute_region_instance_group_manager.igm-basic", ImportState: true, ImportStateVerify: true, @@ -296,8 +227,6 @@ func TestAccRegionInstanceGroupManager_versions(t *testing.T) { func TestAccRegionInstanceGroupManager_autoHealingPolicies(t *testing.T) { t.Parallel() - var manager computeBeta.InstanceGroupManager - template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) @@ -308,13 +237,13 @@ func TestAccRegionInstanceGroupManager_autoHealingPolicies(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccRegionInstanceGroupManager_autoHealingPolicies(template, target, igm, hck), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerBetaExists( - "google_compute_region_instance_group_manager.igm-basic", &manager), - testAccCheckRegionInstanceGroupManagerAutoHealingPolicies("google_compute_region_instance_group_manager.igm-basic", hck, 10), - ), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -323,8 +252,6 @@ func TestAccRegionInstanceGroupManager_autoHealingPolicies(t *testing.T) { func TestAccRegionInstanceGroupManager_distributionPolicy(t *testing.T) { t.Parallel() - var manager computeBeta.InstanceGroupManager - template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) zones := []string{"us-central1-a", "us-central1-b"} @@ -334,13 +261,13 @@ func TestAccRegionInstanceGroupManager_distributionPolicy(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccRegionInstanceGroupManager_distributionPolicy(template, igm, zones), - Check: resource.ComposeTestCheckFunc( - testAccCheckRegionInstanceGroupManagerBetaExists( - "google_compute_region_instance_group_manager.igm-basic", &manager), - testAccCheckRegionInstanceGroupManagerDistributionPolicy("google_compute_region_instance_group_manager.igm-basic", zones), - ), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -353,309 +280,24 @@ func testAccCheckRegionInstanceGroupManagerDestroy(s *terraform.State) error { if rs.Type != "google_compute_region_instance_group_manager" { continue } - _, err := config.clientCompute.RegionInstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() - if err == nil { - return fmt.Errorf("RegionInstanceGroupManager still exists") - } - } - - return nil -} - -func testAccCheckRegionInstanceGroupManagerExists(n string, manager *compute.InstanceGroupManager) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - found, err := config.clientCompute.RegionInstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() - if err != nil { - return err - } - - if found.Name != rs.Primary.ID { - return fmt.Errorf("RegionInstanceGroupManager not found") - } - - *manager = *found - - return nil - } -} - -func testAccCheckRegionInstanceGroupManagerBetaExists(n string, manager *computeBeta.InstanceGroupManager) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - found, err := config.clientComputeBeta.RegionInstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() - if err != nil { - return err - } - - if found.Name != rs.Primary.ID { - return fmt.Errorf("RegionInstanceGroupManager not found") - } - - *manager = *found - - return nil - } -} - -func testAccCheckRegionInstanceGroupManagerUpdated(n string, size int64, targetPools []string, template string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientCompute.RegionInstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() - if err != nil { - return err - } - - // Cannot check the target pool as the instance creation is asynchronous. However, can - // check the target_size. - if manager.TargetSize != size { - return fmt.Errorf("instance count incorrect") - } - - tpNames := make([]string, 0, len(manager.TargetPools)) - for _, targetPool := range manager.TargetPools { - tpNames = append(tpNames, GetResourceNameFromSelfLink(targetPool)) - } - - sort.Strings(tpNames) - sort.Strings(targetPools) - if !reflect.DeepEqual(tpNames, targetPools) { - return fmt.Errorf("target pools incorrect. Expected %s, got %s", targetPools, tpNames) - } - - // check that the instance template updated - instanceTemplate, err := config.clientCompute.InstanceTemplates.Get( - config.Project, template).Do() - if err != nil { - return fmt.Errorf("Error reading instance template: %s", err) - } - - if instanceTemplate.Name != template { - return fmt.Errorf("instance template not updated") - } - - return nil - } -} - -func testAccCheckRegionInstanceGroupManagerNamedPorts(n string, np map[string]int64, instanceGroupManager *compute.InstanceGroupManager) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientCompute.RegionInstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() - if err != nil { - return err - } - - var found bool - for _, namedPort := range manager.NamedPorts { - found = false - for name, port := range np { - if namedPort.Name == name && namedPort.Port == port { - found = true - } - } - if !found { - return fmt.Errorf("named port incorrect") - } - } - - return nil - } -} - -func testAccCheckRegionInstanceGroupManagerVersions(n string, primaryTemplate string, canaryTemplate string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientComputeBeta.RegionInstanceGroupManagers.Get(config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() + id, err := parseRegionInstanceGroupManagerId(rs.Primary.ID) if err != nil { return err } - - if len(manager.Versions) != 2 { - return fmt.Errorf("Expected # of versions to be 2, got %d", len(manager.Versions)) - } - - primaryVersion := manager.Versions[0] - if !strings.Contains(primaryVersion.InstanceTemplate, primaryTemplate) { - return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", primaryTemplate, primaryVersion.InstanceTemplate) + if id.Project == "" { + id.Project = config.Project } - - canaryVersion := manager.Versions[1] - if !strings.Contains(canaryVersion.InstanceTemplate, canaryTemplate) { - return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", canaryTemplate, canaryVersion.InstanceTemplate) - } - - return nil - } -} - -func testAccCheckRegionInstanceGroupManagerAutoHealingPolicies(n, hck string, initialDelaySec int64) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) + if id.Region == "" { + id.Region = rs.Primary.Attributes["region"] } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientComputeBeta.RegionInstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() - if err != nil { - return err - } - - if len(manager.AutoHealingPolicies) != 1 { - return fmt.Errorf("Expected # of auto healing policies to be 1, got %d", len(manager.AutoHealingPolicies)) - } - autoHealingPolicy := manager.AutoHealingPolicies[0] - - if !strings.Contains(autoHealingPolicy.HealthCheck, hck) { - return fmt.Errorf("Expected string \"%s\" to appear in \"%s\"", hck, autoHealingPolicy.HealthCheck) - } - - if autoHealingPolicy.InitialDelaySec != initialDelaySec { - return fmt.Errorf("Expected auto healing policy inital delay to be %d, got %d", initialDelaySec, autoHealingPolicy.InitialDelaySec) - } - return nil - } -} - -func testAccCheckRegionInstanceGroupManagerDistributionPolicy(n string, zones []string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientComputeBeta.RegionInstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() - if err != nil { - return err - } - - if manager.DistributionPolicy == nil { - return fmt.Errorf("Expected distribution policy to exist") - } - - zoneConfigs := manager.DistributionPolicy.Zones - if len(zoneConfigs) != len(zones) { - return fmt.Errorf("Expected number of zones in distribution policy to match; had %d, expected %d", len(zoneConfigs), len(zones)) - } - - sort.Strings(zones) - sortedExisting := make([]string, 0) - for _, zone := range zoneConfigs { - sortedExisting = append(sortedExisting, zone.Zone) - } - sort.Strings(sortedExisting) - - for i := 0; i < len(zones); i++ { - if !strings.HasSuffix(sortedExisting[i], zones[i]) { - return fmt.Errorf("found mismatched zone configuration: expected entry #%d as '%s', got %s", i, zones[i], sortedExisting[i]) - } + _, err = config.clientCompute.RegionInstanceGroupManagers.Get( + id.Project, id.Region, id.Name).Do() + if err == nil { + return fmt.Errorf("RegionInstanceGroupManager still exists") } - - return nil } -} - -func testAccCheckRegionInstanceGroupManagerTemplateTags(n string, tags []string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - config := testAccProvider.Meta().(*Config) - - manager, err := config.clientCompute.RegionInstanceGroupManagers.Get( - config.Project, rs.Primary.Attributes["region"], rs.Primary.ID).Do() - if err != nil { - return err - } - - // check that the instance template updated - instanceTemplate, err := config.clientCompute.InstanceTemplates.Get( - config.Project, GetResourceNameFromSelfLink(manager.InstanceTemplate)).Do() - if err != nil { - return fmt.Errorf("Error reading instance template: %s", err) - } - - if !reflect.DeepEqual(instanceTemplate.Properties.Tags.Items, tags) { - return fmt.Errorf("instance template not updated") - } - - return nil - } + return nil } func testAccRegionInstanceGroupManager_basic(template, target, igm1, igm2 string) string { From fcd777704629022309bd3b55c08a25efb1e21e10 Mon Sep 17 00:00:00 2001 From: Sam Levenick Date: Wed, 17 Oct 2018 13:50:13 -0700 Subject: [PATCH 03/11] Basic structure for singular InSpec resources (#573) Merged PR #573. --- .ci/magic-modules/create-pr.sh | 2 +- build/inspec | 2 +- build/terraform | 2 +- products/compute/inspec.yaml | 81 ++++++++++++++++++++++++++ provider/inspec.rb | 9 +++ templates/inspec/singular_resource.erb | 29 ++++++++- 6 files changed, 121 insertions(+), 4 deletions(-) diff --git a/.ci/magic-modules/create-pr.sh b/.ci/magic-modules/create-pr.sh index 041f98dcb2a2..885492e1d0c9 100755 --- a/.ci/magic-modules/create-pr.sh +++ b/.ci/magic-modules/create-pr.sh @@ -82,7 +82,7 @@ if [ "$BRANCH_NAME" = "$ORIGINAL_PR_BRANCH" ]; then fi git checkout -b "$BRANCH_NAME" - if INSPEC_PR=$(hub pull-request -b "$INSPEC_REPO_USER/inspec:master" -F ./downstream_body); then + if INSPEC_PR=$(hub pull-request -b "$INSPEC_REPO_USER/inspec-gcp:master" -F ./downstream_body); then DEPENDENCIES="${DEPENDENCIES}depends: $INSPEC_PR ${NEWLINE}" else echo "InSpec - did not generate a PR." diff --git a/build/inspec b/build/inspec index 257d3f760307..f359ebbf01dc 160000 --- a/build/inspec +++ b/build/inspec @@ -1 +1 @@ -Subproject commit 257d3f760307559938568aeed74261df5cde4b87 +Subproject commit f359ebbf01dc1294dc5338ad2dc380a888a14563 diff --git a/build/terraform b/build/terraform index 56f6530712f9..d622ec466e68 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 56f6530712f9cbfa5db40e9238d35703931fa951 +Subproject commit d622ec466e688b09c601d34f5ce1c7e5796dadf3 diff --git a/products/compute/inspec.yaml b/products/compute/inspec.yaml index 6523b93ef4ab..bcd603cee34d 100644 --- a/products/compute/inspec.yaml +++ b/products/compute/inspec.yaml @@ -19,6 +19,87 @@ manifest: !ruby/object:Provider::Inspec::Manifest summary: 'InSpec resources for verifying GCP infrastructure' description: | InSpec resources for verifying GCP infrastructure +overrides: !ruby/object:Provider::ResourceOverrides + Address: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Autoscaler: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + BackendBucket: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + BackendService: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Disk: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + DiskType: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Firewall: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + ForwardingRule: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + GlobalAddress: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + GlobalForwardingRule: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + HealthCheck: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + HttpHealthCheck: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + HttpsHealthCheck: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Image: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Instance: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + InstanceGroup: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + InstanceGroupManager: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + InstanceTemplate: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + InterconnectAttachment: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + License: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + MachineType: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Network: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Region: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + RegionAutoscaler: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + RegionDisk: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + RegionDiskType: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Route: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Router: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Snapshot: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + SslCertificate: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + SslPolicy: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + Subnetwork: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + TargetHttpProxy: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + TargetHttpsProxy: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + TargetPool: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + TargetTcpProxy: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + TargetVpnGateway: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + TargetSslProxy: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + UrlMap: !ruby/object:Provider::Chef::ResourceOverride + exclude: true + VpnTunnel: !ruby/object:Provider::Chef::ResourceOverride + exclude: true files: !ruby/object:Provider::Config::Files style: functions: diff --git a/provider/inspec.rb b/provider/inspec.rb index 7f66da907f30..69439d7b94de 100644 --- a/provider/inspec.rb +++ b/provider/inspec.rb @@ -21,6 +21,7 @@ module Provider # Code generator for Example Cookbooks that manage Google Cloud Platform # resources. class Inspec < Provider::Core + include Google::RubyUtils # Settings for the provider class Config < Provider::Config attr_reader :manifest @@ -53,6 +54,14 @@ def generate_resource(data) ) end + # Returns the url that this object can be retrieved from + # based off of the self link + def url(object) + url = object.self_link_url[1] + return url.join('') if url.is_a?(Array) + url.split("\n").join('') + end + # TODO? def generate_resource_tests(data) end diff --git a/templates/inspec/singular_resource.erb b/templates/inspec/singular_resource.erb index 3628efec92c1..0858313284a5 100644 --- a/templates/inspec/singular_resource.erb +++ b/templates/inspec/singular_resource.erb @@ -14,4 +14,31 @@ -%> <%= compile 'templates/license.erb' -%> -<%= lines(autogen_notice :ruby) -%> \ No newline at end of file +<%= lines(autogen_notice :ruby) -%> + +# A provider to manage <%= @api.name -%> resources. +class <%= object.name -%> < Inspec.resource(1) + + name 'google_<%= product_ns.downcase -%>_<%= object.name.downcase -%>' + desc '<%= object.name -%>' + supports platform: 'gcp-mm' + +<% object.properties.each do |prop| -%> + <%= "attr_reader :#{prop.out_name}" -%> + +<% end -%> + def base + '<%= object.self_link_url[0].join %>' + end + + def url + '<%= url(object) %>' + end + + # TODO + def parse end + + def exists? + !@fetched.nil? + end +end \ No newline at end of file From 2923c48fe1a1687dce1ca7dbeb40632232a6df0e Mon Sep 17 00:00:00 2001 From: Chris Stephens Date: Wed, 17 Oct 2018 14:36:13 -0700 Subject: [PATCH 04/11] move puppet logging to debug instead of info (#574) Merged PR #574. --- provider/test_data/property.rb | 2 +- provider/test_matrix.rb | 4 ++-- templates/puppet/resource.erb | 2 +- templates/puppet/type.erb | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/provider/test_data/property.rb b/provider/test_data/property.rb index ad76f36768ff..c51fde76d71f 100644 --- a/provider/test_data/property.rb +++ b/provider/test_data/property.rb @@ -31,7 +31,7 @@ def initialize(provider) # rubocop:disable Metrics/PerceivedComplexity def property(prop, index, comparator, value, start_indent = 0, name_override = nil) - Google::LOGGER.info \ + Google::LOGGER.debug \ "Generating test #{prop.out_name}[#{index}] #{comparator} #{value}" if prop.class <= Api::Type::ResourceRef diff --git a/provider/test_matrix.rb b/provider/test_matrix.rb index 3d37f2cc8ba9..0cd69faad908 100644 --- a/provider/test_matrix.rb +++ b/provider/test_matrix.rb @@ -26,7 +26,7 @@ def initialize end def add(matrix, file, object) - Google::LOGGER.info \ + Google::LOGGER.debug \ "Registering test matrix for #{object.name} @ #{file}" @matrixes << matrix end @@ -104,7 +104,7 @@ def pop(ensurable, exists = :none, changes = :none, has_name = :none, # Ensures that all test contexts are defined def verify - Google::LOGGER.info "Verifying test matrix for #{@object.name} @ #{@file}" + Google::LOGGER.debug "Verifying test matrix for #{@object.name} @ #{@file}" verify_topics verify_match_expectations fail_if_not_all_popped unless @hierarchy.empty? diff --git a/templates/puppet/resource.erb b/templates/puppet/resource.erb index c831e8f8caa9..ecce6b3df1cc 100644 --- a/templates/puppet/resource.erb +++ b/templates/puppet/resource.erb @@ -42,7 +42,7 @@ -%> <%= lines(emit_requires(requires)) -%> -<% Google::LOGGER.info "Generating #{object.name}: #{object.out_name}" -%> +<% Google::LOGGER.debug "Generating #{object.name}: #{object.out_name}" -%> Puppet::Type.type(:<%= object.out_name -%>).provide(:google) do mk_resource_methods diff --git a/templates/puppet/type.erb b/templates/puppet/type.erb index 6562ee68af8f..fe21a01ed614 100644 --- a/templates/puppet/type.erb +++ b/templates/puppet/type.erb @@ -25,7 +25,7 @@ -%> <%= lines(emit_requires(requires)) -%> -<% Google::LOGGER.info "Generating #{object.name}: #{object.out_name}" -%> +<% Google::LOGGER.debug "Generating #{object.name}: #{object.out_name}" -%> Puppet::Type.newtype(:<%= object.out_name -%>) do <%= format_description(object, 2, '@doc =') %> @@ -39,7 +39,7 @@ Puppet::Type.newtype(:<%= object.out_name -%>) do unless object.parameters.nil? object.parameters.each do |param| if param.class <= Api::Type::ResourceRef - Google::LOGGER.info \ + Google::LOGGER.debug \ "Generating autorequire #{object.name}.#{param.name}: #{param.type}" -%> autorequire(:<%= param.out_type -%>) do @@ -103,7 +103,7 @@ Puppet::Type.newtype(:<%= object.out_name -%>) do <% unless object.parameters.nil? -%> <% object.parameters.each do |p| -%> -<% Google::LOGGER.info "Generating param #{object.name}.#{p.name}:#{p.type}" -%> +<% Google::LOGGER.debug "Generating param #{object.name}.#{p.name}:#{p.type}" -%> <%= namevar = identities.include?(p.name) ? 'namevar: true' : nil @@ -132,7 +132,7 @@ Puppet::Type.newtype(:<%= object.out_name -%>) do <% end -%> <% end -%> <% object.properties.each do |p| - Google::LOGGER.info "Generating #{object.name}.#{p.name}: #{p.type}" + Google::LOGGER.debug "Generating #{object.name}.#{p.name}: #{p.type}" -%> <%= From 33a4c2a429df77c91f5034976788ab346a494830 Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Wed, 17 Oct 2018 14:50:20 -0700 Subject: [PATCH 05/11] Terraform: Add project, organization, folder, service account tests to mm (#577) Merged PR #577. --- ...oogle_organization_iam_custom_role_test.go | 245 ++++++ .../resource_google_organization_iam_test.go | 206 +++++ ...esource_google_organization_policy_test.go | 391 +++++++++ ...esource_google_project_iam_binding_test.go | 257 ++++++ ...rce_google_project_iam_custom_role_test.go | 177 ++++ ...resource_google_project_iam_member_test.go | 169 ++++ ...resource_google_project_iam_policy_test.go | 780 ++++++++++++++++++ .../resource_google_project_migrate_test.go | 70 ++ ...google_project_organization_policy_test.go | 354 ++++++++ .../resource_google_project_service_test.go | 189 +++++ .../resource_google_project_services_test.go | 322 ++++++++ .../tests/resource_google_project_test.go | 576 +++++++++++++ ...esource_google_service_account_iam_test.go | 167 ++++ ...esource_google_service_account_key_test.go | 175 ++++ 14 files changed, 4078 insertions(+) create mode 100644 provider/terraform/tests/resource_google_organization_iam_custom_role_test.go create mode 100644 provider/terraform/tests/resource_google_organization_iam_test.go create mode 100644 provider/terraform/tests/resource_google_organization_policy_test.go create mode 100644 provider/terraform/tests/resource_google_project_iam_binding_test.go create mode 100644 provider/terraform/tests/resource_google_project_iam_custom_role_test.go create mode 100644 provider/terraform/tests/resource_google_project_iam_member_test.go create mode 100644 provider/terraform/tests/resource_google_project_iam_policy_test.go create mode 100644 provider/terraform/tests/resource_google_project_migrate_test.go create mode 100644 provider/terraform/tests/resource_google_project_organization_policy_test.go create mode 100644 provider/terraform/tests/resource_google_project_service_test.go create mode 100644 provider/terraform/tests/resource_google_project_services_test.go create mode 100644 provider/terraform/tests/resource_google_project_test.go create mode 100644 provider/terraform/tests/resource_google_service_account_iam_test.go create mode 100644 provider/terraform/tests/resource_google_service_account_key_test.go diff --git a/provider/terraform/tests/resource_google_organization_iam_custom_role_test.go b/provider/terraform/tests/resource_google_organization_iam_custom_role_test.go new file mode 100644 index 000000000000..6d7fef648515 --- /dev/null +++ b/provider/terraform/tests/resource_google_organization_iam_custom_role_test.go @@ -0,0 +1,245 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccOrganizationIamCustomRole_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + roleId := "tfIamCustomRole" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationIamCustomRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleOrganizationIamCustomRole_basic(org, roleId), + Check: testAccCheckGoogleOrganizationIamCustomRole( + "google_organization_iam_custom_role.foo", + "My Custom Role", + "foo", + "GA", + []string{"resourcemanager.projects.list"}), + }, + { + Config: testAccCheckGoogleOrganizationIamCustomRole_update(org, roleId), + Check: testAccCheckGoogleOrganizationIamCustomRole( + "google_organization_iam_custom_role.foo", + "My Custom Role Updated", + "bar", + "BETA", + []string{"resourcemanager.projects.list", "resourcemanager.organizations.get"}), + }, + { + ResourceName: "google_organization_iam_custom_role.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccOrganizationIamCustomRole_undelete(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + roleId := "tfIamCustomRole" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationIamCustomRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleOrganizationIamCustomRole_basic(org, roleId), + Check: testAccCheckGoogleOrganizationIamCustomRoleDeletionStatus("google_organization_iam_custom_role.foo", false), + }, + // Soft-delete + { + Config: testAccCheckGoogleOrganizationIamCustomRole_deleted(org, roleId), + Check: testAccCheckGoogleOrganizationIamCustomRoleDeletionStatus("google_organization_iam_custom_role.foo", true), + }, + // Undelete + { + Config: testAccCheckGoogleOrganizationIamCustomRole_basic(org, roleId), + Check: testAccCheckGoogleOrganizationIamCustomRoleDeletionStatus("google_organization_iam_custom_role.foo", false), + }, + }, + }) +} + +func TestAccOrganizationIamCustomRole_createAfterDestroy(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + roleId := "tfIamCustomRole" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationIamCustomRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleOrganizationIamCustomRole_basic(org, roleId), + Check: testAccCheckGoogleOrganizationIamCustomRole( + "google_organization_iam_custom_role.foo", + "My Custom Role", + "foo", + "GA", + []string{"resourcemanager.projects.list"}), + }, + // Destroy resources + { + Config: " ", + Destroy: true, + }, + // Re-create with no existing state + { + Config: testAccCheckGoogleOrganizationIamCustomRole_basic(org, roleId), + Check: testAccCheckGoogleOrganizationIamCustomRole( + "google_organization_iam_custom_role.foo", + "My Custom Role", + "foo", + "GA", + []string{"resourcemanager.projects.list"}), + }, + }, + }) +} + +func testAccCheckGoogleOrganizationIamCustomRoleDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_organization_iam_custom_role" { + continue + } + + role, err := config.clientIAM.Organizations.Roles.Get(rs.Primary.ID).Do() + + if err != nil { + return err + } + + if !role.Deleted { + return fmt.Errorf("Iam custom role still exists") + } + + } + + return nil +} + +func testAccCheckGoogleOrganizationIamCustomRole(n, title, description, stage string, permissions []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + role, err := config.clientIAM.Organizations.Roles.Get(rs.Primary.ID).Do() + + if err != nil { + return err + } + + if title != role.Title { + return fmt.Errorf("Incorrect title. Expected %q, got %q", title, role.Title) + } + + if description != role.Description { + return fmt.Errorf("Incorrect description. Expected %q, got %q", description, role.Description) + } + + if stage != role.Stage { + return fmt.Errorf("Incorrect stage. Expected %q, got %q", stage, role.Stage) + } + + sort.Strings(permissions) + sort.Strings(role.IncludedPermissions) + if !reflect.DeepEqual(permissions, role.IncludedPermissions) { + return fmt.Errorf("Incorrect permissions. Expected %q, got %q", permissions, role.IncludedPermissions) + } + + return nil + } +} + +func testAccCheckGoogleOrganizationIamCustomRoleDeletionStatus(n string, deleted bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + role, err := config.clientIAM.Organizations.Roles.Get(rs.Primary.ID).Do() + + if err != nil { + return err + } + + if deleted != role.Deleted { + return fmt.Errorf("Incorrect deletion status. Expected %t, got %t", deleted, role.Deleted) + } + + return nil + } +} + +func testAccCheckGoogleOrganizationIamCustomRole_basic(orgId, roleId string) string { + return fmt.Sprintf(` +resource "google_organization_iam_custom_role" "foo" { + role_id = "%s" + org_id = "%s" + title = "My Custom Role" + description = "foo" + permissions = ["resourcemanager.projects.list"] +} +`, roleId, orgId) +} + +func testAccCheckGoogleOrganizationIamCustomRole_deleted(orgId, roleId string) string { + return fmt.Sprintf(` +resource "google_organization_iam_custom_role" "foo" { + role_id = "%s" + org_id = "%s" + title = "My Custom Role" + description = "foo" + permissions = ["resourcemanager.projects.list"] + deleted = true +} +`, roleId, orgId) +} + +func testAccCheckGoogleOrganizationIamCustomRole_update(orgId, roleId string) string { + return fmt.Sprintf(` +resource "google_organization_iam_custom_role" "foo" { + role_id = "%s" + org_id = "%s" + title = "My Custom Role Updated" + description = "bar" + permissions = ["resourcemanager.projects.list", "resourcemanager.organizations.get"] + stage = "BETA" +} +`, roleId, orgId) +} diff --git a/provider/terraform/tests/resource_google_organization_iam_test.go b/provider/terraform/tests/resource_google_organization_iam_test.go new file mode 100644 index 000000000000..8b0df19e9d99 --- /dev/null +++ b/provider/terraform/tests/resource_google_organization_iam_test.go @@ -0,0 +1,206 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/cloudresourcemanager/v1" +) + +// Bindings and members are tested serially to avoid concurrent updates of the org's IAM policy. +// When concurrent changes happen, the behavior is to abort and ask the user to retry allowing +// them to see the new diff instead of blindly overriding the policy stored in GCP. This desired +// behavior however induces flakiness in our acceptance tests, hence the need for running them +// serially. +// Policies are *not tested*, because testing them will ruin changes made to the test org. +func TestAccOrganizationIam(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + account := acctest.RandomWithPrefix("tf-test") + roleId := "tfIamTest" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Binding creation + Config: testAccOrganizationIamBinding_basic(account, roleId, org), + Check: testAccCheckGoogleOrganizationIamBindingExists("foo", "test-role", []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + { + ResourceName: "google_organization_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s organizations/%s/roles/%s", org, org, roleId), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccOrganizationIamBinding_update(account, roleId, org), + Check: testAccCheckGoogleOrganizationIamBindingExists("foo", "test-role", []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + { + ResourceName: "google_organization_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s organizations/%s/roles/%s", org, org, roleId), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccOrganizationIamMember_basic(account, org), + Check: testAccCheckGoogleOrganizationIamMemberExists("foo", "roles/browser", + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + ), + }, + { + ResourceName: "google_organization_iam_member.foo", + ImportStateId: fmt.Sprintf("%s roles/browser serviceAccount:%s@%s.iam.gserviceaccount.com", org, account, getTestProjectFromEnv()), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleOrganizationIamBindingExists(bindingResourceName, roleResourceName string, members []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + bindingRs, ok := s.RootModule().Resources["google_organization_iam_binding."+bindingResourceName] + if !ok { + return fmt.Errorf("Not found: %s", bindingResourceName) + } + + roleRs, ok := s.RootModule().Resources["google_organization_iam_custom_role."+roleResourceName] + if !ok { + return fmt.Errorf("Not found: %s", roleResourceName) + } + + config := testAccProvider.Meta().(*Config) + p, err := config.clientResourceManager.Organizations.GetIamPolicy("organizations/"+bindingRs.Primary.Attributes["org_id"], &cloudresourcemanager.GetIamPolicyRequest{}).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == roleRs.Primary.ID { + sort.Strings(members) + sort.Strings(binding.Members) + + if reflect.DeepEqual(members, binding.Members) { + return nil + } + + return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", roleRs.Primary.ID) + } +} + +func testAccCheckGoogleOrganizationIamMemberExists(n, role, member string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["google_organization_iam_member."+n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + config := testAccProvider.Meta().(*Config) + p, err := config.clientResourceManager.Organizations.GetIamPolicy("organizations/"+rs.Primary.Attributes["org_id"], &cloudresourcemanager.GetIamPolicyRequest{}).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + for _, m := range binding.Members { + if m == member { + return nil + } + } + + return fmt.Errorf("Missing member %q, got %v", member, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +// We are using a custom role since iam_binding is authoritative on the member list and +// we want to avoid removing members from an existing role to prevent unwanted side effects. +func testAccOrganizationIamBinding_basic(account, role, org string) string { + return fmt.Sprintf(` +resource "google_service_account" "test-account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_organization_iam_custom_role" "test-role" { + role_id = "%s" + org_id = "%s" + title = "Iam Testing Role" + permissions = ["genomics.datasets.get"] +} + +resource "google_organization_iam_binding" "foo" { + org_id = "%s" + role = "${google_organization_iam_custom_role.test-role.id}" + members = ["serviceAccount:${google_service_account.test-account.email}"] +} +`, account, role, org, org) +} + +func testAccOrganizationIamBinding_update(account, role, org string) string { + return fmt.Sprintf(` +resource "google_service_account" "test-account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_organization_iam_custom_role" "test-role" { + role_id = "%s" + org_id = "%s" + title = "Iam Testing Role" + permissions = ["genomics.datasets.get"] +} + +resource "google_service_account" "test-account-2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_organization_iam_binding" "foo" { + org_id = "%s" + role = "${google_organization_iam_custom_role.test-role.id}" + members = [ + "serviceAccount:${google_service_account.test-account.email}", + "serviceAccount:${google_service_account.test-account-2.email}" + ] +} +`, account, role, org, account, org) +} + +func testAccOrganizationIamMember_basic(account, org string) string { + return fmt.Sprintf(` +resource "google_service_account" "test-account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_organization_iam_member" "foo" { + org_id = "%s" + role = "roles/browser" + member = "serviceAccount:${google_service_account.test-account.email}" +} +`, account, org) +} diff --git a/provider/terraform/tests/resource_google_organization_policy_test.go b/provider/terraform/tests/resource_google_organization_policy_test.go new file mode 100644 index 000000000000..b62a0bac9272 --- /dev/null +++ b/provider/terraform/tests/resource_google_organization_policy_test.go @@ -0,0 +1,391 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var DENIED_ORG_POLICIES = []string{ + "doubleclicksearch.googleapis.com", + "replicapoolupdater.googleapis.com", +} + +// Since each test here is acting on the same organization, run the tests serially to +// avoid race conditions and aborted operations. +func TestAccOrganizationPolicy(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "boolean": testAccOrganizationPolicy_boolean, + "list_allowAll": testAccOrganizationPolicy_list_allowAll, + "list_allowSome": testAccOrganizationPolicy_list_allowSome, + "list_denySome": testAccOrganizationPolicy_list_denySome, + "list_update": testAccOrganizationPolicy_list_update, + "restore_policy": testAccOrganizationPolicy_restore_defaultTrue, + } + + for name, tc := range testCases { + // shadow the tc variable into scope so that when + // the loop continues, if t.Run hasn't executed tc(t) + // yet, we don't have a race condition + // see https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccOrganizationPolicy_boolean(t *testing.T) { + org := getTestOrgTargetFromEnv(t) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + // Test creation of an enforced boolean policy + Config: testAccOrganizationPolicyConfig_boolean(org, true), + Check: testAccCheckGoogleOrganizationBooleanPolicy("bool", true), + }, + { + // Test update from enforced to not + Config: testAccOrganizationPolicyConfig_boolean(org, false), + Check: testAccCheckGoogleOrganizationBooleanPolicy("bool", false), + }, + { + Config: " ", + Destroy: true, + }, + { + // Test creation of a not enforced boolean policy + Config: testAccOrganizationPolicyConfig_boolean(org, false), + Check: testAccCheckGoogleOrganizationBooleanPolicy("bool", false), + }, + { + // Test update from not enforced to enforced + Config: testAccOrganizationPolicyConfig_boolean(org, true), + Check: testAccCheckGoogleOrganizationBooleanPolicy("bool", true), + }, + { + ResourceName: "google_organization_policy.bool", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + +} + +func testAccOrganizationPolicy_list_allowAll(t *testing.T) { + org := getTestOrgTargetFromEnv(t) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationPolicyConfig_list_allowAll(org), + Check: testAccCheckGoogleOrganizationListPolicyAll("list", "ALLOW"), + }, + { + ResourceName: "google_organization_policy.list", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccOrganizationPolicy_list_allowSome(t *testing.T) { + org := getTestOrgTargetFromEnv(t) + project := getTestProjectFromEnv() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationPolicyConfig_list_allowSome(org, project), + Check: testAccCheckGoogleOrganizationListPolicyAllowedValues("list", []string{"projects/" + project, "projects/debian-cloud"}), + }, + { + ResourceName: "google_organization_policy.list", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccOrganizationPolicy_list_denySome(t *testing.T) { + org := getTestOrgTargetFromEnv(t) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationPolicyConfig_list_denySome(org), + Check: testAccCheckGoogleOrganizationListPolicyDeniedValues("list", DENIED_ORG_POLICIES), + }, + { + ResourceName: "google_organization_policy.list", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccOrganizationPolicy_list_update(t *testing.T) { + org := getTestOrgTargetFromEnv(t) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationPolicyConfig_list_allowAll(org), + Check: testAccCheckGoogleOrganizationListPolicyAll("list", "ALLOW"), + }, + { + Config: testAccOrganizationPolicyConfig_list_denySome(org), + Check: testAccCheckGoogleOrganizationListPolicyDeniedValues("list", DENIED_ORG_POLICIES), + }, + { + ResourceName: "google_organization_policy.list", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccOrganizationPolicy_restore_defaultTrue(t *testing.T) { + org := getTestOrgTargetFromEnv(t) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationPolicyConfig_restore_defaultTrue(org), + Check: testAccCheckGoogleOrganizationRestoreDefaultTrue("restore", &cloudresourcemanager.RestoreDefault{}), + }, + { + ResourceName: "google_organization_policy.restore", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleOrganizationPolicyDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_organization_policy" { + continue + } + + org := "organizations/" + rs.Primary.Attributes["org_id"] + constraint := canonicalOrgPolicyConstraint(rs.Primary.Attributes["constraint"]) + policy, err := config.clientResourceManager.Organizations.GetOrgPolicy(org, &cloudresourcemanager.GetOrgPolicyRequest{ + Constraint: constraint, + }).Do() + + if err != nil { + return err + } + + if policy.ListPolicy != nil || policy.BooleanPolicy != nil { + return fmt.Errorf("Org policy with constraint '%s' hasn't been cleared", constraint) + } + } + return nil +} + +func testAccCheckGoogleOrganizationBooleanPolicy(n string, enforced bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + policy, err := getGoogleOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + if policy.BooleanPolicy.Enforced != enforced { + return fmt.Errorf("Expected boolean policy enforcement to be '%t', got '%t'", enforced, policy.BooleanPolicy.Enforced) + } + + return nil + } +} + +func testAccCheckGoogleOrganizationListPolicyAll(n, policyType string) resource.TestCheckFunc { + return func(s *terraform.State) error { + policy, err := getGoogleOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + if len(policy.ListPolicy.AllowedValues) > 0 || len(policy.ListPolicy.DeniedValues) > 0 { + return fmt.Errorf("The `values` field shouldn't be set") + } + + if policy.ListPolicy.AllValues != policyType { + return fmt.Errorf("Expected the list policy to '%s' all values, got '%s'", policyType, policy.ListPolicy.AllValues) + } + + return nil + } +} + +func testAccCheckGoogleOrganizationListPolicyAllowedValues(n string, values []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + policy, err := getGoogleOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + sort.Strings(policy.ListPolicy.AllowedValues) + sort.Strings(values) + if !reflect.DeepEqual(policy.ListPolicy.AllowedValues, values) { + return fmt.Errorf("Expected the list policy to allow '%s', instead allowed '%s'", values, policy.ListPolicy.AllowedValues) + } + + return nil + } +} + +func testAccCheckGoogleOrganizationListPolicyDeniedValues(n string, values []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + policy, err := getGoogleOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + sort.Strings(policy.ListPolicy.DeniedValues) + sort.Strings(values) + if !reflect.DeepEqual(policy.ListPolicy.DeniedValues, values) { + return fmt.Errorf("Expected the list policy to deny '%s', instead denied '%s'", values, policy.ListPolicy.DeniedValues) + } + + return nil + } +} + +func testAccCheckGoogleOrganizationRestoreDefaultTrue(n string, policyDefault *cloudresourcemanager.RestoreDefault) resource.TestCheckFunc { + return func(s *terraform.State) error { + + policy, err := getGoogleOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + if !reflect.DeepEqual(policy.RestoreDefault, policyDefault) { + return fmt.Errorf("Expected the restore default '%s', instead denied, %s", policyDefault, policy.RestoreDefault) + } + + return nil + } +} + +func getGoogleOrganizationPolicyTestResource(s *terraform.State, n string) (*cloudresourcemanager.OrgPolicy, error) { + rn := "google_organization_policy." + n + rs, ok := s.RootModule().Resources[rn] + if !ok { + return nil, fmt.Errorf("Not found: %s", rn) + } + + if rs.Primary.ID == "" { + return nil, fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + return config.clientResourceManager.Organizations.GetOrgPolicy("organizations/"+rs.Primary.Attributes["org_id"], &cloudresourcemanager.GetOrgPolicyRequest{ + Constraint: rs.Primary.Attributes["constraint"], + }).Do() +} + +func testAccOrganizationPolicyConfig_boolean(org string, enforced bool) string { + return fmt.Sprintf(` +resource "google_organization_policy" "bool" { + org_id = "%s" + constraint = "constraints/compute.disableSerialPortAccess" + + boolean_policy { + enforced = %t + } +} +`, org, enforced) +} + +func testAccOrganizationPolicyConfig_list_allowAll(org string) string { + return fmt.Sprintf(` +resource "google_organization_policy" "list" { + org_id = "%s" + constraint = "constraints/serviceuser.services" + + list_policy { + allow { + all = true + } + } +} +`, org) +} + +func testAccOrganizationPolicyConfig_list_allowSome(org, project string) string { + return fmt.Sprintf(` +resource "google_organization_policy" "list" { + org_id = "%s" + constraint = "constraints/compute.trustedImageProjects" + + list_policy { + allow { + values = [ + "projects/%s", + "projects/debian-cloud" + ] + } + } +} +`, org, project) +} + +func testAccOrganizationPolicyConfig_list_denySome(org string) string { + return fmt.Sprintf(` +resource "google_organization_policy" "list" { + org_id = "%s" + constraint = "serviceuser.services" + + list_policy { + deny { + values = [ + "doubleclicksearch.googleapis.com", + "replicapoolupdater.googleapis.com", + ] + } + } +} +`, org) +} + +func testAccOrganizationPolicyConfig_restore_defaultTrue(org string) string { + return fmt.Sprintf(` +resource "google_organization_policy" "restore" { + org_id = "%s" + constraint = "serviceuser.services" + + restore_policy { + default = true + } +} +`, org) +} diff --git a/provider/terraform/tests/resource_google_project_iam_binding_test.go b/provider/terraform/tests/resource_google_project_iam_binding_test.go new file mode 100644 index 000000000000..1bdbf1448306 --- /dev/null +++ b/provider/terraform/tests/resource_google_project_iam_binding_test.go @@ -0,0 +1,257 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func projectIamBindingImportStep(resourceName, pid, role string) resource.TestStep { + return resource.TestStep{ + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s %s", pid, role), + ImportState: true, + ImportStateVerify: true, + } +} + +// Test that an IAM binding can be applied to a project +func TestAccProjectIamBinding_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + role := "roles/compute.instanceAdmin" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM binding + { + Config: testAccProjectAssociateBindingBasic(pid, pname, org, role), + }, + projectIamBindingImportStep("google_project_iam_binding.acceptance", pid, role), + }, + }) +} + +// Test that multiple IAM bindings can be applied to a project, one at a time +func TestAccProjectIamBinding_multiple(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + role := "roles/compute.instanceAdmin" + role2 := "roles/viewer" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM binding + { + Config: testAccProjectAssociateBindingBasic(pid, pname, org, role), + }, + // Apply another IAM binding + { + Config: testAccProjectAssociateBindingMultiple(pid, pname, org, role, role2), + }, + projectIamBindingImportStep("google_project_iam_binding.acceptance", pid, role), + projectIamBindingImportStep("google_project_iam_binding.multiple", pid, role2), + }, + }) +} + +// Test that multiple IAM bindings can be applied to a project all at once +func TestAccProjectIamBinding_multipleAtOnce(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + role := "roles/compute.instanceAdmin" + role2 := "roles/viewer" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM binding + { + Config: testAccProjectAssociateBindingMultiple(pid, pname, org, role, role2), + }, + projectIamBindingImportStep("google_project_iam_binding.acceptance", pid, role), + projectIamBindingImportStep("google_project_iam_binding.multiple", pid, role2), + }, + }) +} + +// Test that an IAM binding can be updated once applied to a project +func TestAccProjectIamBinding_update(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + role := "roles/compute.instanceAdmin" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM binding + { + Config: testAccProjectAssociateBindingBasic(pid, pname, org, role), + }, + projectIamBindingImportStep("google_project_iam_binding.acceptance", pid, role), + + // Apply an updated IAM binding + { + Config: testAccProjectAssociateBindingUpdated(pid, pname, org, role), + }, + projectIamBindingImportStep("google_project_iam_binding.acceptance", pid, role), + + // Drop the original member + { + Config: testAccProjectAssociateBindingDropMemberFromBasic(pid, pname, org, role), + }, + projectIamBindingImportStep("google_project_iam_binding.acceptance", pid, role), + }, + }) +} + +// Test that an IAM binding can be removed from a project +func TestAccProjectIamBinding_remove(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + role := "roles/compute.instanceAdmin" + role2 := "roles/viewer" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply multiple IAM bindings + { + Config: testAccProjectAssociateBindingMultiple(pid, pname, org, role, role2), + }, + projectIamBindingImportStep("google_project_iam_binding.acceptance", pid, role), + projectIamBindingImportStep("google_project_iam_binding.multiple", pid, role2), + + // Remove the bindings + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + }, + }) +} + +func testAccProjectAssociateBindingBasic(pid, name, org, role string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_binding" "acceptance" { + project = "${google_project.acceptance.project_id}" + members = ["user:admin@hashicorptest.com"] + role = "%s" +} +`, pid, name, org, role) +} + +func testAccProjectAssociateBindingMultiple(pid, name, org, role, role2 string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_binding" "acceptance" { + project = "${google_project.acceptance.project_id}" + members = ["user:admin@hashicorptest.com"] + role = "%s" +} + +resource "google_project_iam_binding" "multiple" { + project = "${google_project.acceptance.project_id}" + members = ["user:paddy@hashicorp.com"] + role = "%s" +} +`, pid, name, org, role, role2) +} + +func testAccProjectAssociateBindingUpdated(pid, name, org, role string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_binding" "acceptance" { + project = "${google_project.acceptance.project_id}" + members = ["user:admin@hashicorptest.com", "user:paddy@hashicorp.com"] + role = "%s" +} +`, pid, name, org, role) +} + +func testAccProjectAssociateBindingDropMemberFromBasic(pid, name, org, role string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_binding" "acceptance" { + project = "${google_project.acceptance.project_id}" + members = ["user:paddy@hashicorp.com"] + role = "%s" +} +`, pid, name, org, role) +} diff --git a/provider/terraform/tests/resource_google_project_iam_custom_role_test.go b/provider/terraform/tests/resource_google_project_iam_custom_role_test.go new file mode 100644 index 000000000000..f0e396ca0a30 --- /dev/null +++ b/provider/terraform/tests/resource_google_project_iam_custom_role_test.go @@ -0,0 +1,177 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccProjectIamCustomRole_basic(t *testing.T) { + t.Parallel() + + roleId := "tfIamCustomRole" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectIamCustomRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleProjectIamCustomRole_basic(roleId), + Check: resource.TestCheckResourceAttr("google_project_iam_custom_role.foo", "stage", "GA"), + }, + { + ResourceName: "google_project_iam_custom_role.foo", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCheckGoogleProjectIamCustomRole_update(roleId), + }, + { + ResourceName: "google_project_iam_custom_role.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccProjectIamCustomRole_undelete(t *testing.T) { + t.Parallel() + + roleId := "tfIamCustomRole" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectIamCustomRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleProjectIamCustomRole_basic(roleId), + Check: resource.TestCheckResourceAttr("google_project_iam_custom_role.foo", "deleted", "false"), + }, + { + ResourceName: "google_project_iam_custom_role.foo", + ImportState: true, + ImportStateVerify: true, + }, + // Soft-delete + { + Config: testAccCheckGoogleProjectIamCustomRole_deleted(roleId), + Check: resource.TestCheckResourceAttr("google_project_iam_custom_role.foo", "deleted", "true"), + }, + { + ResourceName: "google_project_iam_custom_role.foo", + ImportState: true, + ImportStateVerify: true, + }, + // Undelete + { + Config: testAccCheckGoogleProjectIamCustomRole_basic(roleId), + Check: resource.TestCheckResourceAttr("google_project_iam_custom_role.foo", "deleted", "false"), + }, + { + ResourceName: "google_project_iam_custom_role.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccProjectIamCustomRole_createAfterDestroy(t *testing.T) { + t.Parallel() + + roleId := "tfIamCustomRole" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectIamCustomRoleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleProjectIamCustomRole_basic(roleId), + }, + { + ResourceName: "google_project_iam_custom_role.foo", + ImportState: true, + ImportStateVerify: true, + }, + // Destroy resources + { + Config: " ", + Destroy: true, + }, + // Re-create with no existing state + { + Config: testAccCheckGoogleProjectIamCustomRole_basic(roleId), + }, + { + ResourceName: "google_project_iam_custom_role.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleProjectIamCustomRoleDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_project_iam_custom_role" { + continue + } + + role, err := config.clientIAM.Projects.Roles.Get(rs.Primary.ID).Do() + + if err != nil { + return err + } + + if !role.Deleted { + return fmt.Errorf("Iam custom role still exists") + } + + } + + return nil +} + +func testAccCheckGoogleProjectIamCustomRole_basic(roleId string) string { + return fmt.Sprintf(` +resource "google_project_iam_custom_role" "foo" { + role_id = "%s" + title = "My Custom Role" + description = "foo" + permissions = ["iam.roles.list"] +} +`, roleId) +} + +func testAccCheckGoogleProjectIamCustomRole_deleted(roleId string) string { + return fmt.Sprintf(` +resource "google_project_iam_custom_role" "foo" { + role_id = "%s" + title = "My Custom Role" + description = "foo" + permissions = ["iam.roles.list"] + deleted = true +} +`, roleId) +} + +func testAccCheckGoogleProjectIamCustomRole_update(roleId string) string { + return fmt.Sprintf(` +resource "google_project_iam_custom_role" "foo" { + role_id = "%s" + title = "My Custom Role Updated" + description = "bar" + permissions = ["iam.roles.list", "iam.roles.create", "iam.roles.delete"] + stage = "BETA" +} +`, roleId) +} diff --git a/provider/terraform/tests/resource_google_project_iam_member_test.go b/provider/terraform/tests/resource_google_project_iam_member_test.go new file mode 100644 index 000000000000..df280216ad25 --- /dev/null +++ b/provider/terraform/tests/resource_google_project_iam_member_test.go @@ -0,0 +1,169 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func projectIamMemberImportStep(resourceName, pid, role, member string) resource.TestStep { + return resource.TestStep{ + ResourceName: resourceName, + ImportStateId: fmt.Sprintf("%s %s %s", pid, role, member), + ImportState: true, + ImportStateVerify: true, + } +} + +// Test that an IAM binding can be applied to a project +func TestAccProjectIamMember_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + resourceName := "google_project_iam_member.acceptance" + role := "roles/compute.instanceAdmin" + member := "user:admin@hashicorptest.com" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM binding + { + Config: testAccProjectAssociateMemberBasic(pid, pname, org, role, member), + }, + projectIamMemberImportStep(resourceName, pid, role, member), + }, + }) +} + +// Test that multiple IAM bindings can be applied to a project +func TestAccProjectIamMember_multiple(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + skipIfEnvNotSet(t, "GOOGLE_ORG") + + pid := "terraform-" + acctest.RandString(10) + resourceName := "google_project_iam_member.acceptance" + resourceName2 := "google_project_iam_member.multiple" + role := "roles/compute.instanceAdmin" + member := "user:admin@hashicorptest.com" + member2 := "user:paddy@hashicorp.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM binding + { + Config: testAccProjectAssociateMemberBasic(pid, pname, org, role, member), + }, + projectIamMemberImportStep(resourceName, pid, role, member), + + // Apply another IAM binding + { + Config: testAccProjectAssociateMemberMultiple(pid, pname, org, role, member, role, member2), + }, + projectIamMemberImportStep(resourceName, pid, role, member), + projectIamMemberImportStep(resourceName2, pid, role, member2), + }, + }) +} + +// Test that an IAM binding can be removed from a project +func TestAccProjectIamMember_remove(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + skipIfEnvNotSet(t, "GOOGLE_ORG") + + pid := "terraform-" + acctest.RandString(10) + resourceName := "google_project_iam_member.acceptance" + role := "roles/compute.instanceAdmin" + member := "user:admin@hashicorptest.com" + member2 := "user:paddy@hashicorp.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + + // Apply multiple IAM bindings + { + Config: testAccProjectAssociateMemberMultiple(pid, pname, org, role, member, role, member2), + }, + projectIamMemberImportStep(resourceName, pid, role, member), + projectIamMemberImportStep(resourceName, pid, role, member2), + + // Remove the bindings + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + }, + }) +} + +func testAccProjectAssociateMemberBasic(pid, name, org, role, member string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_member" "acceptance" { + project = "${google_project.acceptance.project_id}" + role = "%s" + member = "%s" +} +`, pid, name, org, role, member) +} + +func testAccProjectAssociateMemberMultiple(pid, name, org, role, member, role2, member2 string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_iam_member" "acceptance" { + project = "${google_project.acceptance.project_id}" + role = "%s" + member = "%s" +} + +resource "google_project_iam_member" "multiple" { + project = "${google_project.acceptance.project_id}" + role = "%s" + member = "%s" +} +`, pid, name, org, role, member, role2, member2) +} diff --git a/provider/terraform/tests/resource_google_project_iam_policy_test.go b/provider/terraform/tests/resource_google_project_iam_policy_test.go new file mode 100644 index 000000000000..a09460eda0ab --- /dev/null +++ b/provider/terraform/tests/resource_google_project_iam_policy_test.go @@ -0,0 +1,780 @@ +package google + +import ( + "encoding/json" + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/cloudresourcemanager/v1" +) + +func TestSubtractIamPolicy(t *testing.T) { + table := []struct { + a *cloudresourcemanager.Policy + b *cloudresourcemanager.Policy + expect cloudresourcemanager.Policy + }{ + { + a: &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "1", + "2", + }, + }, + { + Role: "b", + Members: []string{ + "1", + "2", + }, + }, + }, + }, + b: &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "3", + "4", + }, + }, + { + Role: "b", + Members: []string{ + "1", + "2", + }, + }, + }, + }, + expect: cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "1", + "2", + }, + }, + }, + }, + }, + { + a: &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "1", + "2", + }, + }, + { + Role: "b", + Members: []string{ + "1", + "2", + }, + }, + }, + }, + b: &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "1", + "2", + }, + }, + { + Role: "b", + Members: []string{ + "1", + "2", + }, + }, + }, + }, + expect: cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{}, + }, + }, + { + a: &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "1", + "2", + "3", + }, + }, + { + Role: "b", + Members: []string{ + "1", + "2", + "3", + }, + }, + }, + }, + b: &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "1", + "3", + }, + }, + { + Role: "b", + Members: []string{ + "1", + "2", + "3", + }, + }, + }, + }, + expect: cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "2", + }, + }, + }, + }, + }, + { + a: &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "1", + "2", + "3", + }, + }, + { + Role: "b", + Members: []string{ + "1", + "2", + "3", + }, + }, + }, + }, + b: &cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{ + { + Role: "a", + Members: []string{ + "1", + "2", + "3", + }, + }, + { + Role: "b", + Members: []string{ + "1", + "2", + "3", + }, + }, + }, + }, + expect: cloudresourcemanager.Policy{ + Bindings: []*cloudresourcemanager.Binding{}, + }, + }, + } + + for _, test := range table { + c := subtractIamPolicy(test.a, test.b) + sort.Sort(sortableBindings(c.Bindings)) + for i, _ := range c.Bindings { + sort.Strings(c.Bindings[i].Members) + } + + if !reflect.DeepEqual(derefBindings(c.Bindings), derefBindings(test.expect.Bindings)) { + t.Errorf("\ngot %+v\nexpected %+v", derefBindings(c.Bindings), derefBindings(test.expect.Bindings)) + } + } +} + +// Test that an IAM policy can be applied to a project +func TestAccProjectIamPolicy_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + resource.TestStep{ + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + // Apply an IAM policy from a data source. The application + // merges policies, so we validate the expected state. + resource.TestStep{ + Config: testAccProjectAssociatePolicyBasic(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectIamPolicyIsMerged("google_project_iam_policy.acceptance", "data.google_iam_policy.admin", pid), + ), + }, + resource.TestStep{ + ResourceName: "google_project_iam_policy.acceptance", + ImportState: true, + // Skipping the normal "ImportStateVerify" - Unfortunately, it's not + // really possible to make the imported policy match exactly, since + // the policy depends on the service account being used to create the + // project. + }, + // Finally, remove the custom IAM policy from config and apply, then + // confirm that the project is in its original state. + resource.TestStep{ + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(pid), + ), + }, + }, + }) +} + +// Test that an IAM policy can be applied to a project when no project is set in the resource +func TestAccProjectIamPolicy_defaultProject(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project + resource.TestStep{ + Config: testAccProjectDefaultAssociatePolicyBasic(), + Check: resource.ComposeTestCheckFunc( + testAccProjectExistingPolicy(getTestProjectFromEnv()), + ), + }, + // Apply an IAM policy from a data source. The application + // merges policies, so we validate the expected state. + resource.TestStep{ + Config: testAccProjectDefaultAssociatePolicyBasic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectIamPolicyIsMerged("google_project_iam_policy.acceptance", "data.google_iam_policy.admin", getTestProjectFromEnv()), + ), + }, + }, + }) +} + +// Test that a non-collapsed IAM policy doesn't perpetually diff +func TestAccProjectIamPolicy_expanded(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccProjectAssociatePolicyExpanded(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectIamPolicyExists("google_project_iam_policy.acceptance", "data.google_iam_policy.expanded", pid), + ), + }, + }, + }) +} + +func getStatePrimaryResource(s *terraform.State, res, expectedID string) (*terraform.InstanceState, error) { + // Get the project resource + resource, ok := s.RootModule().Resources[res] + if !ok { + return nil, fmt.Errorf("Not found: %s", res) + } + if resource.Primary.Attributes["id"] != expectedID && expectedID != "" { + return nil, fmt.Errorf("Expected project %q to match ID %q in state", resource.Primary.ID, expectedID) + } + return resource.Primary, nil +} + +func getGoogleProjectIamPolicyFromResource(resource *terraform.InstanceState) (cloudresourcemanager.Policy, error) { + var p cloudresourcemanager.Policy + ps, ok := resource.Attributes["policy_data"] + if !ok { + return p, fmt.Errorf("Resource %q did not have a 'policy_data' attribute. Attributes were %#v", resource.ID, resource.Attributes) + } + if err := json.Unmarshal([]byte(ps), &p); err != nil { + return p, fmt.Errorf("Could not unmarshal %s:\n: %v", ps, err) + } + return p, nil +} + +func getGoogleProjectIamPolicyFromState(s *terraform.State, res, expectedID string) (cloudresourcemanager.Policy, error) { + project, err := getStatePrimaryResource(s, res, expectedID) + if err != nil { + return cloudresourcemanager.Policy{}, err + } + return getGoogleProjectIamPolicyFromResource(project) +} + +func compareBindings(a, b []*cloudresourcemanager.Binding) bool { + a = mergeBindings(a) + b = mergeBindings(b) + sort.Sort(sortableBindings(a)) + sort.Sort(sortableBindings(b)) + return reflect.DeepEqual(derefBindings(a), derefBindings(b)) +} + +func testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid string) resource.TestCheckFunc { + return func(s *terraform.State) error { + projectPolicy, err := getGoogleProjectIamPolicyFromState(s, projectRes, pid) + if err != nil { + return fmt.Errorf("Error retrieving IAM policy for project from state: %s", err) + } + policyPolicy, err := getGoogleProjectIamPolicyFromState(s, policyRes, "") + if err != nil { + return fmt.Errorf("Error retrieving IAM policy for data_policy from state: %s", err) + } + + // The bindings in both policies should be identical + if !compareBindings(projectPolicy.Bindings, policyPolicy.Bindings) { + return fmt.Errorf("Project and data source policies do not match: project policy is %+v, data resource policy is %+v", derefBindings(projectPolicy.Bindings), derefBindings(policyPolicy.Bindings)) + } + return nil + } +} + +func testAccCheckGoogleProjectIamPolicyIsMerged(projectRes, policyRes, pid string) resource.TestCheckFunc { + return func(s *terraform.State) error { + err := testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid)(s) + if err != nil { + return err + } + + projectPolicy, err := getGoogleProjectIamPolicyFromState(s, projectRes, pid) + if err != nil { + return fmt.Errorf("Error retrieving IAM policy for project from state: %s", err) + } + + // Merge the project policy in Terraform state with the policy the project had before the config was applied + var expected []*cloudresourcemanager.Binding + expected = append(expected, originalPolicy.Bindings...) + expected = append(expected, projectPolicy.Bindings...) + expected = mergeBindings(expected) + + // Retrieve the actual policy from the project + c := testAccProvider.Meta().(*Config) + actual, err := getProjectIamPolicy(pid, c) + if err != nil { + return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", pid, err) + } + // The bindings should match, indicating the policy was successfully applied and merged + if !compareBindings(actual.Bindings, expected) { + return fmt.Errorf("Actual and expected project policies do not match: actual policy is %+v, expected policy is %+v", derefBindings(actual.Bindings), derefBindings(expected)) + } + + return nil + } +} + +func TestIamRolesToMembersBinding(t *testing.T) { + table := []struct { + expect []*cloudresourcemanager.Binding + input map[string]map[string]bool + }{ + { + expect: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{ + "member-1", + "member-2", + }, + }, + }, + input: map[string]map[string]bool{ + "role-1": map[string]bool{ + "member-1": true, + "member-2": true, + }, + }, + }, + { + expect: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{ + "member-1", + "member-2", + }, + }, + }, + input: map[string]map[string]bool{ + "role-1": map[string]bool{ + "member-1": true, + "member-2": true, + }, + }, + }, + { + expect: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{}, + }, + }, + input: map[string]map[string]bool{ + "role-1": map[string]bool{}, + }, + }, + } + + for _, test := range table { + got := rolesToMembersBinding(test.input) + + sort.Sort(sortableBindings(got)) + for i, _ := range got { + sort.Strings(got[i].Members) + } + + if !reflect.DeepEqual(derefBindings(got), derefBindings(test.expect)) { + t.Errorf("got %+v, expected %+v", derefBindings(got), derefBindings(test.expect)) + } + } +} +func TestIamRolesToMembersMap(t *testing.T) { + table := []struct { + input []*cloudresourcemanager.Binding + expect map[string]map[string]bool + }{ + { + input: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{ + "member-1", + "member-2", + }, + }, + }, + expect: map[string]map[string]bool{ + "role-1": map[string]bool{ + "member-1": true, + "member-2": true, + }, + }, + }, + { + input: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{ + "member-1", + "member-2", + "member-1", + "member-2", + }, + }, + }, + expect: map[string]map[string]bool{ + "role-1": map[string]bool{ + "member-1": true, + "member-2": true, + }, + }, + }, + { + input: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + }, + }, + expect: map[string]map[string]bool{ + "role-1": map[string]bool{}, + }, + }, + } + + for _, test := range table { + got := rolesToMembersMap(test.input) + if !reflect.DeepEqual(got, test.expect) { + t.Errorf("got %+v, expected %+v", got, test.expect) + } + } +} + +func TestIamMergeBindings(t *testing.T) { + table := []struct { + input []*cloudresourcemanager.Binding + expect []cloudresourcemanager.Binding + }{ + { + input: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{ + "member-1", + "member-2", + }, + }, + { + Role: "role-1", + Members: []string{ + "member-3", + }, + }, + }, + expect: []cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{ + "member-1", + "member-2", + "member-3", + }, + }, + }, + }, + { + input: []*cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{ + "member-3", + "member-4", + }, + }, + { + Role: "role-1", + Members: []string{ + "member-2", + "member-1", + }, + }, + { + Role: "role-2", + Members: []string{ + "member-1", + }, + }, + { + Role: "role-1", + Members: []string{ + "member-5", + }, + }, + { + Role: "role-3", + Members: []string{ + "member-1", + }, + }, + { + Role: "role-2", + Members: []string{ + "member-2", + }, + }, + {Role: "empty-role", Members: []string{}}, + }, + expect: []cloudresourcemanager.Binding{ + { + Role: "role-1", + Members: []string{ + "member-1", + "member-2", + "member-3", + "member-4", + "member-5", + }, + }, + { + Role: "role-2", + Members: []string{ + "member-1", + "member-2", + }, + }, + { + Role: "role-3", + Members: []string{ + "member-1", + }, + }, + }, + }, + } + + for _, test := range table { + got := mergeBindings(test.input) + sort.Sort(sortableBindings(got)) + for i, _ := range got { + sort.Strings(got[i].Members) + } + + if !reflect.DeepEqual(derefBindings(got), test.expect) { + t.Errorf("\ngot %+v\nexpected %+v", derefBindings(got), test.expect) + } + } +} + +func derefBindings(b []*cloudresourcemanager.Binding) []cloudresourcemanager.Binding { + db := make([]cloudresourcemanager.Binding, len(b)) + + for i, v := range b { + db[i] = *v + sort.Strings(db[i].Members) + } + return db +} + +// Confirm that a project has an IAM policy with at least 1 binding +func testAccProjectExistingPolicy(pid string) resource.TestCheckFunc { + return func(s *terraform.State) error { + c := testAccProvider.Meta().(*Config) + var err error + originalPolicy, err = getProjectIamPolicy(pid, c) + if err != nil { + return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", pid, err) + } + if len(originalPolicy.Bindings) == 0 { + return fmt.Errorf("Refuse to run test against project with zero IAM Bindings. This is likely an error in the test code that is not properly identifying the IAM policy of a project.") + } + return nil + } +} + +func testAccProjectDefaultAssociatePolicyBasic() string { + return fmt.Sprintf(` +resource "google_project_iam_policy" "acceptance" { + policy_data = "${data.google_iam_policy.admin.policy_data}" +} +data "google_iam_policy" "admin" { + binding { + role = "roles/storage.objectViewer" + members = [ + "user:evanbrown@google.com", + ] + } + binding { + role = "roles/compute.instanceAdmin" + members = [ + "user:evanbrown@google.com", + "user:evandbrown@gmail.com", + ] + } +} +`) +} + +func testAccProjectAssociatePolicyBasic(pid, name, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} +resource "google_project_iam_policy" "acceptance" { + project = "${google_project.acceptance.id}" + policy_data = "${data.google_iam_policy.admin.policy_data}" +} +data "google_iam_policy" "admin" { + binding { + role = "roles/storage.objectViewer" + members = [ + "user:evanbrown@google.com", + ] + } + binding { + role = "roles/compute.instanceAdmin" + members = [ + "user:evanbrown@google.com", + "user:evandbrown@gmail.com", + ] + } +} +`, pid, name, org) +} + +func testAccProject_createWithoutOrg(pid, name string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" +}`, pid, name) +} + +func testAccProject_create(pid, name, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +}`, pid, name, org) +} + +func testAccProject_createBilling(pid, name, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +}`, pid, name, org, billing) +} + +func testAccProjectAssociatePolicyExpanded(pid, name, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} +resource "google_project_iam_policy" "acceptance" { + project = "${google_project.acceptance.id}" + policy_data = "${data.google_iam_policy.expanded.policy_data}" + authoritative = false +} +data "google_iam_policy" "expanded" { + binding { + role = "roles/viewer" + members = [ + "user:paddy@carvers.co", + ] + } + + binding { + role = "roles/viewer" + members = [ + "user:paddy@hashicorp.com", + ] + } +}`, pid, name, org) +} diff --git a/provider/terraform/tests/resource_google_project_migrate_test.go b/provider/terraform/tests/resource_google_project_migrate_test.go new file mode 100644 index 000000000000..8aeff36404f0 --- /dev/null +++ b/provider/terraform/tests/resource_google_project_migrate_test.go @@ -0,0 +1,70 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestGoogleProjectMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + Attributes map[string]string + Expected map[string]string + Meta interface{} + }{ + "deprecate policy_data and support creation/deletion": { + StateVersion: 0, + Attributes: map[string]string{}, + Expected: map[string]string{ + "project_id": "test-project", + "skip_delete": "true", + }, + Meta: &Config{}, + }, + } + + for tn, tc := range cases { + is := &terraform.InstanceState{ + ID: "test-project", + Attributes: tc.Attributes, + } + is, err := resourceGoogleProjectMigrateState( + tc.StateVersion, is, tc.Meta) + + if err != nil { + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + for k, v := range tc.Expected { + if is.Attributes[k] != v { + t.Fatalf( + "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", + tn, k, v, k, is.Attributes[k], is.Attributes) + } + } + } +} + +func TestGoogleProjectMigrateState_empty(t *testing.T) { + var is *terraform.InstanceState + var meta *Config + + // should handle nil + is, err := resourceGoogleProjectMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } + if is != nil { + t.Fatalf("expected nil instancestate, got: %#v", is) + } + + // should handle non-nil but empty + is = &terraform.InstanceState{} + is, err = resourceGoogleProjectMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } +} diff --git a/provider/terraform/tests/resource_google_project_organization_policy_test.go b/provider/terraform/tests/resource_google_project_organization_policy_test.go new file mode 100644 index 000000000000..12067285c244 --- /dev/null +++ b/provider/terraform/tests/resource_google_project_organization_policy_test.go @@ -0,0 +1,354 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/cloudresourcemanager/v1" +) + +/* +Tests for `google_project_organization_policy` + +These are *not* run in parallel, as they all use the same project +and I end up with 409 Conflict errors from the API when they are +run in parallel. +*/ + +func TestAccProjectOrganizationPolicy_boolean(t *testing.T) { + projectId := getTestProjectFromEnv() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + // Test creation of an enforced boolean policy + Config: testAccProjectOrganizationPolicy_boolean(projectId, true), + Check: testAccCheckGoogleProjectOrganizationBooleanPolicy("bool", true), + }, + { + // Test update from enforced to not + Config: testAccProjectOrganizationPolicy_boolean(projectId, false), + Check: testAccCheckGoogleProjectOrganizationBooleanPolicy("bool", false), + }, + { + Config: " ", + Destroy: true, + }, + { + // Test creation of a not enforced boolean policy + Config: testAccProjectOrganizationPolicy_boolean(projectId, false), + Check: testAccCheckGoogleProjectOrganizationBooleanPolicy("bool", false), + }, + { + // Test update from not enforced to enforced + Config: testAccProjectOrganizationPolicy_boolean(projectId, true), + Check: testAccCheckGoogleProjectOrganizationBooleanPolicy("bool", true), + }, + }, + }) +} + +func TestAccProjectOrganizationPolicy_list_allowAll(t *testing.T) { + projectId := getTestProjectFromEnv() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProjectOrganizationPolicy_list_allowAll(projectId), + Check: testAccCheckGoogleProjectOrganizationListPolicyAll("list", "ALLOW"), + }, + }, + }) +} + +func TestAccProjectOrganizationPolicy_list_allowSome(t *testing.T) { + project := getTestProjectFromEnv() + canonicalProject := canonicalProjectId(project) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProjectOrganizationPolicy_list_allowSome(project), + Check: testAccCheckGoogleProjectOrganizationListPolicyAllowedValues("list", []string{canonicalProject}), + }, + }, + }) +} + +func TestAccProjectOrganizationPolicy_list_denySome(t *testing.T) { + projectId := getTestProjectFromEnv() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProjectOrganizationPolicy_list_denySome(projectId), + Check: testAccCheckGoogleProjectOrganizationListPolicyDeniedValues("list", DENIED_ORG_POLICIES), + }, + }, + }) +} + +func TestAccProjectOrganizationPolicy_list_update(t *testing.T) { + projectId := getTestProjectFromEnv() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProjectOrganizationPolicy_list_allowAll(projectId), + Check: testAccCheckGoogleProjectOrganizationListPolicyAll("list", "ALLOW"), + }, + { + Config: testAccProjectOrganizationPolicy_list_denySome(projectId), + Check: testAccCheckGoogleProjectOrganizationListPolicyDeniedValues("list", DENIED_ORG_POLICIES), + }, + }, + }) +} + +func TestAccProjectOrganizationPolicy_restore_defaultTrue(t *testing.T) { + projectId := getTestProjectFromEnv() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectOrganizationPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProjectOrganizationPolicy_restore_defaultTrue(projectId), + Check: getGoogleProjectOrganizationRestoreDefaultTrue("restore", &cloudresourcemanager.RestoreDefault{}), + }, + }, + }) +} + +func testAccCheckGoogleProjectOrganizationPolicyDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_project_organization_policy" { + continue + } + + projectId := canonicalProjectId(rs.Primary.Attributes["project"]) + constraint := canonicalOrgPolicyConstraint(rs.Primary.Attributes["constraint"]) + policy, err := config.clientResourceManager.Projects.GetOrgPolicy(projectId, &cloudresourcemanager.GetOrgPolicyRequest{ + Constraint: constraint, + }).Do() + + if err != nil { + return err + } + + if policy.ListPolicy != nil || policy.BooleanPolicy != nil { + return fmt.Errorf("Org policy with constraint '%s' hasn't been cleared", constraint) + } + } + return nil +} + +func testAccCheckGoogleProjectOrganizationBooleanPolicy(n string, enforced bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + policy, err := getGoogleProjectOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + if policy.BooleanPolicy.Enforced != enforced { + return fmt.Errorf("Expected boolean policy enforcement to be '%t', got '%t'", enforced, policy.BooleanPolicy.Enforced) + } + + return nil + } +} + +func testAccCheckGoogleProjectOrganizationListPolicyAll(n, policyType string) resource.TestCheckFunc { + return func(s *terraform.State) error { + policy, err := getGoogleProjectOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + if policy.ListPolicy == nil { + return nil + } + + if len(policy.ListPolicy.AllowedValues) > 0 || len(policy.ListPolicy.DeniedValues) > 0 { + return fmt.Errorf("The `values` field shouldn't be set") + } + + if policy.ListPolicy.AllValues != policyType { + return fmt.Errorf("The list policy should %s all values", policyType) + } + + return nil + } +} + +func testAccCheckGoogleProjectOrganizationListPolicyAllowedValues(n string, values []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + policy, err := getGoogleProjectOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + sort.Strings(policy.ListPolicy.AllowedValues) + sort.Strings(values) + if !reflect.DeepEqual(policy.ListPolicy.AllowedValues, values) { + return fmt.Errorf("Expected the list policy to allow '%s', instead allowed '%s'", values, policy.ListPolicy.AllowedValues) + } + + return nil + } +} + +func testAccCheckGoogleProjectOrganizationListPolicyDeniedValues(n string, values []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + policy, err := getGoogleProjectOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + sort.Strings(policy.ListPolicy.DeniedValues) + sort.Strings(values) + if !reflect.DeepEqual(policy.ListPolicy.DeniedValues, values) { + return fmt.Errorf("Expected the list policy to deny '%s', instead denied '%s'", values, policy.ListPolicy.DeniedValues) + } + + return nil + } +} + +func getGoogleProjectOrganizationRestoreDefaultTrue(n string, policyDefault *cloudresourcemanager.RestoreDefault) resource.TestCheckFunc { + return func(s *terraform.State) error { + + policy, err := getGoogleProjectOrganizationPolicyTestResource(s, n) + if err != nil { + return err + } + + if !reflect.DeepEqual(policy.RestoreDefault, policyDefault) { + return fmt.Errorf("Expected the restore default '%s', instead denied, %s", policyDefault, policy.RestoreDefault) + } + + return nil + } +} + +func getGoogleProjectOrganizationPolicyTestResource(s *terraform.State, n string) (*cloudresourcemanager.OrgPolicy, error) { + rn := "google_project_organization_policy." + n + rs, ok := s.RootModule().Resources[rn] + if !ok { + return nil, fmt.Errorf("Not found: %s", rn) + } + + if rs.Primary.ID == "" { + return nil, fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + projectId := canonicalProjectId(rs.Primary.Attributes["project"]) + + return config.clientResourceManager.Projects.GetOrgPolicy(projectId, &cloudresourcemanager.GetOrgPolicyRequest{ + Constraint: rs.Primary.Attributes["constraint"], + }).Do() +} + +func testAccProjectOrganizationPolicy_boolean(pid string, enforced bool) string { + return fmt.Sprintf(` +resource "google_project_organization_policy" "bool" { + project = "%s" + constraint = "constraints/compute.disableSerialPortAccess" + + boolean_policy { + enforced = %t + } +} +`, pid, enforced) +} + +func testAccProjectOrganizationPolicy_list_allowAll(pid string) string { + return fmt.Sprintf(` +resource "google_project_organization_policy" "list" { + project = "%s" + constraint = "constraints/serviceuser.services" + + list_policy { + allow { + all = true + } + } +} +`, pid) +} + +func testAccProjectOrganizationPolicy_list_allowSome(pid string) string { + return fmt.Sprintf(` + +resource "google_project_organization_policy" "list" { + project = "%s" + constraint = "constraints/compute.trustedImageProjects" + + list_policy { + allow { + values = ["projects/%s"] + } + } +} +`, pid, pid) +} + +func testAccProjectOrganizationPolicy_list_denySome(pid string) string { + return fmt.Sprintf(` + +resource "google_project_organization_policy" "list" { + project = "%s" + constraint = "constraints/serviceuser.services" + + list_policy { + deny { + values = [ + "doubleclicksearch.googleapis.com", + "replicapoolupdater.googleapis.com", + ] + } + } +} +`, pid) +} + +func testAccProjectOrganizationPolicy_restore_defaultTrue(pid string) string { + return fmt.Sprintf(` +resource "google_project_organization_policy" "restore" { + project = "%s" + constraint = "constraints/serviceuser.services" + + restore_policy { + default = true + } +} +`, pid) +} + +func canonicalProjectId(project string) string { + if strings.HasPrefix(project, "projects/") { + return project + } + return fmt.Sprintf("projects/%s", project) +} diff --git a/provider/terraform/tests/resource_google_project_service_test.go b/provider/terraform/tests/resource_google_project_service_test.go new file mode 100644 index 000000000000..8f4b0e08bf42 --- /dev/null +++ b/provider/terraform/tests/resource_google_project_service_test.go @@ -0,0 +1,189 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +// Test that services can be enabled and disabled on a project +func TestAccProjectService_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + services := []string{"iam.googleapis.com", "cloudresourcemanager.googleapis.com"} + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccProjectService_basic(services, pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectService(services, pid, true), + ), + }, + resource.TestStep{ + ResourceName: "google_project_service.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"disable_on_destroy"}, + }, + resource.TestStep{ + ResourceName: "google_project_service.test2", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"disable_on_destroy"}, + }, + // Use a separate TestStep rather than a CheckDestroy because we need the project to still exist. + resource.TestStep{ + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectService(services, pid, false), + ), + }, + // Create services with disabling turned off. + resource.TestStep{ + Config: testAccProjectService_noDisable(services, pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectService(services, pid, true), + ), + }, + // Check that services are still enabled even after the resources are deleted. + resource.TestStep{ + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectService(services, pid, true), + ), + }, + }, + }) +} + +func TestAccProjectService_handleNotFound(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + service := "iam.googleapis.com" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccProjectService_handleNotFound(service, pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectService([]string{service}, pid, true), + ), + }, + // Delete the project, implicitly deletes service, expect the plan to want to create the service again + resource.TestStep{ + Config: testAccProjectService_handleNotFoundNoProject(service, pid), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckProjectService(services []string, pid string, expectEnabled bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + apiServices, err := getApiServices(pid, config, map[string]struct{}{}) + if err != nil { + return fmt.Errorf("Error listing services for project %q: %v", pid, err) + } + + for _, expected := range services { + exists := false + for _, actual := range apiServices { + if expected == actual { + exists = true + } + } + if expectEnabled && !exists { + return fmt.Errorf("Expected service %s is not enabled server-side (found %v)", expected, apiServices) + } + if !expectEnabled && exists { + return fmt.Errorf("Expected disabled service %s is enabled server-side", expected) + } + } + + return nil + } +} + +func testAccProjectService_basic(services []string, pid, name, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_service" "test" { + project = "${google_project.acceptance.project_id}" + service = "%s" +} + +resource "google_project_service" "test2" { + project = "${google_project.acceptance.project_id}" + service = "%s" +} +`, pid, name, org, services[0], services[1]) +} + +func testAccProjectService_noDisable(services []string, pid, name, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +resource "google_project_service" "test" { + project = "${google_project.acceptance.project_id}" + service = "%s" + disable_on_destroy = false +} + +resource "google_project_service" "test2" { + project = "${google_project.acceptance.project_id}" + service = "%s" + disable_on_destroy = false +} +`, pid, name, org, services[0], services[1]) +} + +func testAccProjectService_handleNotFound(service, pid, name, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} + +// by passing through locals, we break the dependency chain +// see terraform-provider-google#1292 +locals { + project_id = "${google_project.acceptance.project_id}" +} + +resource "google_project_service" "test" { + project = "${local.project_id}" + service = "%s" +} +`, pid, name, org, service) +} + +func testAccProjectService_handleNotFoundNoProject(service, pid string) string { + return fmt.Sprintf(` +resource "google_project_service" "test" { + project = "%s" + service = "%s" +} +`, pid, service) +} diff --git a/provider/terraform/tests/resource_google_project_services_test.go b/provider/terraform/tests/resource_google_project_services_test.go new file mode 100644 index 000000000000..80983106d03d --- /dev/null +++ b/provider/terraform/tests/resource_google_project_services_test.go @@ -0,0 +1,322 @@ +package google + +import ( + "bytes" + "fmt" + "log" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +// Test that services can be enabled and disabled on a project +func TestAccProjectServices_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + services1 := []string{"iam.googleapis.com", "cloudresourcemanager.googleapis.com"} + services2 := []string{"cloudresourcemanager.googleapis.com"} + oobService := "iam.googleapis.com" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project with some services + resource.TestStep{ + Config: testAccProjectAssociateServicesBasic(services1, pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testProjectServicesMatch(services1, pid), + ), + }, + // Update services to remove one + resource.TestStep{ + Config: testAccProjectAssociateServicesBasic(services2, pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testProjectServicesMatch(services2, pid), + ), + }, + // Add a service out-of-band and ensure it is removed + resource.TestStep{ + PreConfig: func() { + config := testAccProvider.Meta().(*Config) + enableService(oobService, pid, config) + }, + Config: testAccProjectAssociateServicesBasic(services2, pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testProjectServicesMatch(services2, pid), + ), + }, + resource.TestStep{ + ResourceName: "google_project_services.acceptance", + ImportState: true, + ImportStateId: pid, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"disable_on_destroy"}, + }, + }, + }) +} + +// Test that services are authoritative when a project has existing +// sevices not represented in config +func TestAccProjectServices_authoritative(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + services := []string{"cloudresourcemanager.googleapis.com"} + oobService := "iam.googleapis.com" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project with no services + resource.TestStep{ + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectExists("google_project.acceptance", pid), + ), + }, + // Add a service out-of-band, then apply a config that creates a service. + // It should remove the out-of-band service. + resource.TestStep{ + PreConfig: func() { + config := testAccProvider.Meta().(*Config) + enableService(oobService, pid, config) + }, + Config: testAccProjectAssociateServicesBasic(services, pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testProjectServicesMatch(services, pid), + ), + }, + }, + }) +} + +// Test that services are authoritative when a project has existing +// sevices, some which are represented in the config and others +// that are not +func TestAccProjectServices_authoritative2(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + oobServices := []string{"iam.googleapis.com", "cloudresourcemanager.googleapis.com"} + services := []string{"iam.googleapis.com"} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create a new project with no services + resource.TestStep{ + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectExists("google_project.acceptance", pid), + ), + }, + // Add a service out-of-band, then apply a config that creates a service. + // It should remove the out-of-band service. + resource.TestStep{ + PreConfig: func() { + config := testAccProvider.Meta().(*Config) + for _, s := range oobServices { + enableService(s, pid, config) + } + }, + Config: testAccProjectAssociateServicesBasic(services, pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testProjectServicesMatch(services, pid), + ), + }, + }, + }) +} + +// Test that services that can't be enabled on their own (such as dataproc-control.googleapis.com) +// don't end up causing diffs when they are enabled as a side-effect of a different service's +// enablement. +func TestAccProjectServices_ignoreUnenablableServices(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + services := []string{ + "dataproc.googleapis.com", + // The following services are enabled as a side-effect of dataproc's enablement + "storage-component.googleapis.com", + "deploymentmanager.googleapis.com", + "replicapool.googleapis.com", + "replicapoolupdater.googleapis.com", + "resourceviews.googleapis.com", + "compute.googleapis.com", + "container.googleapis.com", + "containerregistry.googleapis.com", + "storage-api.googleapis.com", + "pubsub.googleapis.com", + "oslogin.googleapis.com", + "bigquery-json.googleapis.com", + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccProjectAssociateServicesBasic_withBilling(services, pid, pname, org, billingId), + Check: resource.ComposeTestCheckFunc( + testProjectServicesMatch(services, pid), + ), + }, + }, + }) +} + +func TestAccProjectServices_pagination(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + + // we need at least 50 services (doesn't matter what they are) to exercise the + // pagination handling code. + services := []string{ + "actions.googleapis.com", + "appengine.googleapis.com", + "appengineflex.googleapis.com", + "bigquery-json.googleapis.com", + "bigquerydatatransfer.googleapis.com", + "bigtableadmin.googleapis.com", + "bigtabletableadmin.googleapis.com", + "cloudbuild.googleapis.com", + "clouderrorreporting.googleapis.com", + "cloudfunctions.googleapis.com", + "cloudiot.googleapis.com", + "cloudkms.googleapis.com", + "cloudmonitoring.googleapis.com", + "cloudresourcemanager.googleapis.com", + "cloudtrace.googleapis.com", + "compute.googleapis.com", + "container.googleapis.com", + "containerregistry.googleapis.com", + "dataflow.googleapis.com", + "dataproc.googleapis.com", + "datastore.googleapis.com", + "deploymentmanager.googleapis.com", + "dialogflow.googleapis.com", + "dns.googleapis.com", + "endpoints.googleapis.com", + "firebaserules.googleapis.com", + "firestore.googleapis.com", + "genomics.googleapis.com", + "iam.googleapis.com", + "language.googleapis.com", + "logging.googleapis.com", + "ml.googleapis.com", + "monitoring.googleapis.com", + "oslogin.googleapis.com", + "pubsub.googleapis.com", + "replicapool.googleapis.com", + "replicapoolupdater.googleapis.com", + "resourceviews.googleapis.com", + "runtimeconfig.googleapis.com", + "servicecontrol.googleapis.com", + "servicemanagement.googleapis.com", + "sourcerepo.googleapis.com", + "spanner.googleapis.com", + "speech.googleapis.com", + "sql-component.googleapis.com", + "storage-api.googleapis.com", + "storage-component.googleapis.com", + "storagetransfer.googleapis.com", + "testing.googleapis.com", + "toolresults.googleapis.com", + "translate.googleapis.com", + "videointelligence.googleapis.com", + "vision.googleapis.com", + "zync.googleapis.com", + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccProjectAssociateServicesBasic_withBilling(services, pid, pname, org, billingId), + Check: resource.ComposeTestCheckFunc( + testProjectServicesMatch(services, pid), + ), + }, + }, + }) +} + +func testAccProjectAssociateServicesBasic(services []string, pid, name, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +} +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + services = [%s] + disable_on_destroy = true +} +`, pid, name, org, testStringsToString(services)) +} + +func testAccProjectAssociateServicesBasic_withBilling(services []string, pid, name, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + services = [%s] + disable_on_destroy = false +} +`, pid, name, org, billing, testStringsToString(services)) +} + +func testProjectServicesMatch(services []string, pid string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + apiServices, err := getApiServices(pid, config, ignoreProjectServices) + if err != nil { + return fmt.Errorf("Error listing services for project %q: %v", pid, err) + } + + sort.Strings(services) + sort.Strings(apiServices) + if !reflect.DeepEqual(services, apiServices) { + return fmt.Errorf("Services in config (%v) do not exactly match services returned by API (%v)", services, apiServices) + } + + return nil + } +} + +func testStringsToString(s []string) string { + var b bytes.Buffer + for i, v := range s { + b.WriteString(fmt.Sprintf("\"%s\"", v)) + if i < len(s)-1 { + b.WriteString(",") + } + } + r := b.String() + log.Printf("[DEBUG]: Converted list of strings to %s", r) + return b.String() +} diff --git a/provider/terraform/tests/resource_google_project_test.go b/provider/terraform/tests/resource_google_project_test.go new file mode 100644 index 000000000000..dad6333d8d42 --- /dev/null +++ b/provider/terraform/tests/resource_google_project_test.go @@ -0,0 +1,576 @@ +package google + +import ( + "fmt" + "os" + "reflect" + "strconv" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/cloudresourcemanager/v1" +) + +var ( + pname = "Terraform Acceptance Tests" + originalPolicy *cloudresourcemanager.Policy +) + +// Test that a Project resource can be created without an organization +func TestAccProject_createWithoutOrg(t *testing.T) { + t.Parallel() + + creds := multiEnvSearch(credsEnvVars) + if strings.Contains(creds, "iam.gserviceaccount.com") { + t.Skip("Service accounts cannot create projects without a parent. Requires user credentials.") + } + + pid := "terraform-" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // This step creates a new project + resource.TestStep{ + Config: testAccProject_createWithoutOrg(pid, pname), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectExists("google_project.acceptance", pid), + ), + }, + }, + }) +} + +// Test that a Project resource can be created and an IAM policy +// associated +func TestAccProject_create(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // This step creates a new project + resource.TestStep{ + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectExists("google_project.acceptance", pid), + ), + }, + }, + }) +} + +// Test that a Project resource can be created with an associated +// billing account +func TestAccProject_billing(t *testing.T) { + t.Parallel() + org := getTestOrgFromEnv(t) + skipIfEnvNotSet(t, "GOOGLE_BILLING_ACCOUNT_2") + billingId2 := os.Getenv("GOOGLE_BILLING_ACCOUNT_2") + billingId := getTestBillingAccountFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // This step creates a new project with a billing account + resource.TestStep{ + Config: testAccProject_createBilling(pid, pname, org, billingId), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, billingId), + ), + }, + // Make sure import supports billing account + resource.TestStep{ + ResourceName: "google_project.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + // Update to a different billing account + resource.TestStep{ + Config: testAccProject_createBilling(pid, pname, org, billingId2), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, billingId2), + ), + }, + // Unlink the billing account + resource.TestStep{ + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, ""), + ), + }, + }, + }) +} + +// Test that a Project resource can be created with labels +func TestAccProject_labels(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccProject_labels(pid, pname, org, map[string]string{"test": "that"}), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectHasLabels("google_project.acceptance", pid, map[string]string{"test": "that"}), + ), + }, + // Make sure import supports labels + { + ResourceName: "google_project.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + // update project with labels + { + Config: testAccProject_labels(pid, pname, org, map[string]string{"label": "label-value"}), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectExists("google_project.acceptance", pid), + testAccCheckGoogleProjectHasLabels("google_project.acceptance", pid, map[string]string{"label": "label-value"}), + ), + }, + // update project delete labels + { + Config: testAccProject_create(pid, pname, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectExists("google_project.acceptance", pid), + testAccCheckGoogleProjectHasNoLabels("google_project.acceptance", pid), + ), + }, + }, + }) +} + +func TestAccProject_deleteDefaultNetwork(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + billingId := getTestBillingAccountFromEnv(t) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccProject_deleteDefaultNetwork(pid, pname, org, billingId), + }, + }, + }) +} + +func TestAccProject_parentFolder(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := "terraform-" + acctest.RandString(10) + folderDisplayName := "tf-test-" + acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccProject_parentFolder(pid, pname, folderDisplayName, org), + }, + }, + }) +} + +func TestAccProject_appEngineBasic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := acctest.RandomWithPrefix("tf-test") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccProject_appEngineBasic(pid, org), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.name"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.url_dispatch_rule.#"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.code_bucket"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_hostname"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_bucket"), + ), + }, + resource.TestStep{ + ResourceName: "google_project.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccProject_appEngineBasicWithBilling(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := acctest.RandomWithPrefix("tf-test") + billingId := getTestBillingAccountFromEnv(t) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccProject_appEngineBasicWithBilling(pid, org, billingId), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.name"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.url_dispatch_rule.#"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.code_bucket"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_hostname"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_bucket"), + ), + }, + resource.TestStep{ + ResourceName: "google_project.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccProject_appEngineUpdate(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := acctest.RandomWithPrefix("tf-test") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccProject_appEngineNoApp(pid, org), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleProjectExists("google_project.acceptance", pid), + ), + }, + { + Config: testAccProject_appEngineBasic(pid, org), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.name"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.url_dispatch_rule.#"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.code_bucket"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_hostname"), + resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_bucket"), + ), + }, + resource.TestStep{ + ResourceName: "google_project.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccProject_appEngineUpdate(pid, org), + }, + resource.TestStep{ + ResourceName: "google_project.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccProject_appEngineFeatureSettings(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + pid := acctest.RandomWithPrefix("tf-test") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccProject_appEngineFeatureSettings(pid, org), + }, + resource.TestStep{ + ResourceName: "google_project.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccProject_appEngineFeatureSettingsUpdate(pid, org), + }, + resource.TestStep{ + ResourceName: "google_project.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleProjectExists(r, pid string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[r] + if !ok { + return fmt.Errorf("Not found: %s", r) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + if rs.Primary.ID != pid { + return fmt.Errorf("Expected project %q to match ID %q in state", pid, rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckGoogleProjectHasBillingAccount(r, pid, billingId string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[r] + if !ok { + return fmt.Errorf("Not found: %s", r) + } + + // State should match expected + if rs.Primary.Attributes["billing_account"] != billingId { + return fmt.Errorf("Billing ID in state (%s) does not match expected value (%s)", rs.Primary.Attributes["billing_account"], billingId) + } + + // Actual value in API should match state and expected + // Read the billing account + config := testAccProvider.Meta().(*Config) + ba, err := config.clientBilling.Projects.GetBillingInfo(prefixedProject(pid)).Do() + if err != nil { + return fmt.Errorf("Error reading billing account for project %q: %v", prefixedProject(pid), err) + } + if billingId != strings.TrimPrefix(ba.BillingAccountName, "billingAccounts/") { + return fmt.Errorf("Billing ID returned by API (%s) did not match expected value (%s)", ba.BillingAccountName, billingId) + } + return nil + } +} + +func testAccCheckGoogleProjectHasLabels(r, pid string, expected map[string]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[r] + if !ok { + return fmt.Errorf("Not found: %s", r) + } + + // State should have the same number of labels + if rs.Primary.Attributes["labels.%"] != strconv.Itoa(len(expected)) { + return fmt.Errorf("Expected %d labels, got %s", len(expected), rs.Primary.Attributes["labels.%"]) + } + + // Actual value in API should match state and expected + config := testAccProvider.Meta().(*Config) + + found, err := config.clientResourceManager.Projects.Get(pid).Do() + if err != nil { + return err + } + + actual := found.Labels + if !reflect.DeepEqual(actual, expected) { + // Determine only the different attributes + for k, v := range expected { + if av, ok := actual[k]; ok && v == av { + delete(expected, k) + delete(actual, k) + } + } + + spewConf := spew.NewDefaultConfig() + spewConf.SortKeys = true + return fmt.Errorf( + "Labels not equivalent. Difference is shown below. Top is actual, bottom is expected."+ + "\n\n%s\n\n%s", + spewConf.Sdump(actual), spewConf.Sdump(expected), + ) + } + return nil + } +} + +func testAccCheckGoogleProjectHasNoLabels(r, pid string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[r] + if !ok { + return fmt.Errorf("Not found: %s", r) + } + + // State should have zero labels + if rs.Primary.Attributes["labels.%"] != "0" { + return fmt.Errorf("Expected 0 labels, got %s", rs.Primary.Attributes["labels.%"]) + } + + // Actual value in API should match state and expected + config := testAccProvider.Meta().(*Config) + + found, err := config.clientResourceManager.Projects.Get(pid).Do() + if err != nil { + return err + } + + spewConf := spew.NewDefaultConfig() + spewConf.SortKeys = true + if found.Labels != nil { + return fmt.Errorf("Labels should be empty. Actual \n%s", spewConf.Sdump(found.Labels)) + } + return nil + } +} + +func testAccProject_labels(pid, name, org string, labels map[string]string) string { + r := fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + labels {`, pid, name, org) + + l := "" + for key, value := range labels { + l += fmt.Sprintf("%q = %q\n", key, value) + } + + l += fmt.Sprintf("}\n}") + return r + l +} + +func testAccProject_deleteDefaultNetwork(pid, name, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" # requires billing to enable compute API + auto_create_network = false +}`, pid, name, org, billing) +} + +func testAccProject_parentFolder(pid, projectName, folderName, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + # ensures we can set both org_id and folder_id as long as only one is not empty. + org_id = "" + folder_id = "${google_folder.folder1.id}" +} + +resource "google_folder" "folder1" { + display_name = "%s" + parent = "organizations/%s" +} + +`, pid, projectName, folderName, org) +} + +func testAccProject_appEngineNoApp(pid, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" +}`, pid, pid, org) +} + +func testAccProject_appEngineBasic(pid, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + + app_engine { + auth_domain = "hashicorptest.com" + location_id = "us-central" + serving_status = "SERVING" + } +}`, pid, pid, org) +} + +func testAccProject_appEngineBasicWithBilling(pid, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + + billing_account = "%s" + + app_engine { + auth_domain = "hashicorptest.com" + location_id = "us-central" + serving_status = "SERVING" + } +}`, pid, pid, org, billing) +} + +func testAccProject_appEngineUpdate(pid, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + + app_engine { + auth_domain = "tf-test.club" + location_id = "us-central" + serving_status = "USER_DISABLED" + } +}`, pid, pid, org) +} + +func testAccProject_appEngineFeatureSettings(pid, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + + app_engine { + location_id = "us-central" + + feature_settings { + "split_health_checks" = true + } + } +}`, pid, pid, org) +} + +func testAccProject_appEngineFeatureSettingsUpdate(pid, org string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + + app_engine { + location_id = "us-central" + + feature_settings { + "split_health_checks" = false + } + } +}`, pid, pid, org) +} + +func skipIfEnvNotSet(t *testing.T, envs ...string) { + for _, k := range envs { + if os.Getenv(k) == "" { + t.Skipf("Environment variable %s is not set", k) + } + } +} diff --git a/provider/terraform/tests/resource_google_service_account_iam_test.go b/provider/terraform/tests/resource_google_service_account_iam_test.go new file mode 100644 index 000000000000..1e183d870059 --- /dev/null +++ b/provider/terraform/tests/resource_google_service_account_iam_test.go @@ -0,0 +1,167 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccServiceAccountIamBinding(t *testing.T) { + t.Parallel() + + account := acctest.RandomWithPrefix("tf-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccServiceAccountIamBinding_basic(account), + Check: testAccCheckGoogleServiceAccountIam(account, "roles/viewer", []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + { + ResourceName: "google_service_account_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s %s", getServiceAccountCanonicalId(account), "roles/viewer"), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccServiceAccountIamMember(t *testing.T) { + t.Parallel() + + account := acctest.RandomWithPrefix("tf-test") + identity := fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccServiceAccountIamMember_basic(account), + Check: testAccCheckGoogleServiceAccountIam(account, "roles/editor", []string{identity}), + }, + { + ResourceName: "google_service_account_iam_member.foo", + ImportStateId: fmt.Sprintf("%s %s %s", getServiceAccountCanonicalId(account), "roles/editor", identity), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccServiceAccountIamPolicy(t *testing.T) { + t.Parallel() + + account := acctest.RandomWithPrefix("tf-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccServiceAccountIamPolicy_basic(account), + Check: testAccCheckGoogleServiceAccountIam(account, "roles/owner", []string{ + fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + { + ResourceName: "google_service_account_iam_policy.foo", + ImportStateId: getServiceAccountCanonicalId(account), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckGoogleServiceAccountIam(account, role string, members []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + p, err := config.clientIAM.Projects.ServiceAccounts.GetIamPolicy(getServiceAccountCanonicalId(account)).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + sort.Strings(members) + sort.Strings(binding.Members) + + if reflect.DeepEqual(members, binding.Members) { + return nil + } + + return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +func getServiceAccountCanonicalId(account string) string { + return fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", getTestProjectFromEnv(), account, getTestProjectFromEnv()) +} + +func testAccServiceAccountIamBinding_basic(account string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_service_account_iam_binding" "foo" { + service_account_id = "${google_service_account.test_account.id}" + role = "roles/viewer" + members = ["serviceAccount:${google_service_account.test_account.email}"] +} +`, account) +} + +func testAccServiceAccountIamMember_basic(account string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_service_account_iam_member" "foo" { + service_account_id = "${google_service_account.test_account.id}" + role = "roles/editor" + member = "serviceAccount:${google_service_account.test_account.email}" +} +`, account) +} + +func testAccServiceAccountIamPolicy_basic(account string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +data "google_iam_policy" "foo" { + binding { + role = "roles/owner" + + members = ["serviceAccount:${google_service_account.test_account.email}"] + } +} + +resource "google_service_account_iam_policy" "foo" { + service_account_id = "${google_service_account.test_account.id}" + policy_data = "${data.google_iam_policy.foo.policy_data}" +} +`, account) +} diff --git a/provider/terraform/tests/resource_google_service_account_key_test.go b/provider/terraform/tests/resource_google_service_account_key_test.go new file mode 100644 index 000000000000..66d1c77b3f93 --- /dev/null +++ b/provider/terraform/tests/resource_google_service_account_key_test.go @@ -0,0 +1,175 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +// Test that a service account key can be created and destroyed +func TestAccServiceAccountKey_basic(t *testing.T) { + t.Parallel() + + resourceName := "google_service_account_key.acceptance" + accountID := "a" + acctest.RandString(10) + displayName := "Terraform Test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccServiceAccountKey(accountID, displayName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleServiceAccountKeyExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "public_key"), + resource.TestCheckResourceAttrSet(resourceName, "valid_after"), + resource.TestCheckResourceAttrSet(resourceName, "valid_before"), + resource.TestCheckResourceAttrSet(resourceName, "private_key"), + ), + }, + }, + }) +} + +func TestAccServiceAccountKey_fromEmail(t *testing.T) { + t.Parallel() + + resourceName := "google_service_account_key.acceptance" + accountID := "a" + acctest.RandString(10) + displayName := "Terraform Test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccServiceAccountKey_fromEmail(accountID, displayName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleServiceAccountKeyExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "public_key"), + resource.TestCheckResourceAttrSet(resourceName, "valid_after"), + resource.TestCheckResourceAttrSet(resourceName, "valid_before"), + resource.TestCheckResourceAttrSet(resourceName, "private_key"), + ), + }, + }, + }) +} + +func TestAccServiceAccountKey_pgp(t *testing.T) { + t.Parallel() + resourceName := "google_service_account_key.acceptance" + accountID := "a" + acctest.RandString(10) + displayName := "Terraform Test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccServiceAccountKey_pgp(accountID, displayName, testKeyPairPubKey1), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleServiceAccountKeyExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "public_key"), + resource.TestCheckResourceAttrSet(resourceName, "private_key_encrypted"), + resource.TestCheckResourceAttrSet(resourceName, "private_key_fingerprint"), + ), + }, + }, + }) +} + +func testAccCheckGoogleServiceAccountKeyExists(r string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + rs, ok := s.RootModule().Resources[r] + if !ok { + return fmt.Errorf("Not found: %s", r) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + config := testAccProvider.Meta().(*Config) + + _, err := config.clientIAM.Projects.ServiceAccounts.Keys.Get(rs.Primary.ID).Do() + if err != nil { + return err + } + + return nil + } +} + +func testAccServiceAccountKey(account, name string) string { + return fmt.Sprintf(` +resource "google_service_account" "acceptance" { + account_id = "%s" + display_name = "%s" +} + +resource "google_service_account_key" "acceptance" { + service_account_id = "${google_service_account.acceptance.name}" + public_key_type = "TYPE_X509_PEM_FILE" +} +`, account, name) +} + +func testAccServiceAccountKey_fromEmail(account, name string) string { + return fmt.Sprintf(` +resource "google_service_account" "acceptance" { + account_id = "%s" + display_name = "%s" +} + +resource "google_service_account_key" "acceptance" { + service_account_id = "${google_service_account.acceptance.email}" + public_key_type = "TYPE_X509_PEM_FILE" +} +`, account, name) +} + +func testAccServiceAccountKey_pgp(account, name string, key string) string { + return fmt.Sprintf(` +resource "google_service_account" "acceptance" { + account_id = "%s" + display_name = "%s" +} + +resource "google_service_account_key" "acceptance" { + service_account_id = "${google_service_account.acceptance.name}" + public_key_type = "TYPE_X509_PEM_FILE" + pgp_key = < Date: Wed, 17 Oct 2018 15:16:14 -0700 Subject: [PATCH 06/11] Terraform: Add import tests for CloudIoTRegistry (#576) Merged PR #576. --- build/terraform | 2 +- .../resources/resource_cloudiot_registry.go | 23 +-- .../tests/resource_cloudiot_registry_test.go | 181 ++++++++++++++++++ 3 files changed, 192 insertions(+), 14 deletions(-) create mode 100644 provider/terraform/tests/resource_cloudiot_registry_test.go diff --git a/build/terraform b/build/terraform index d622ec466e68..bc31bef9f9ee 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit d622ec466e688b09c601d34f5ce1c7e5796dadf3 +Subproject commit bc31bef9f9ee8c3d2ba595e25db0c2741e6b26d3 diff --git a/provider/terraform/resources/resource_cloudiot_registry.go b/provider/terraform/resources/resource_cloudiot_registry.go index a7aa0d066bf4..ae850fcb5204 100644 --- a/provider/terraform/resources/resource_cloudiot_registry.go +++ b/provider/terraform/resources/resource_cloudiot_registry.go @@ -76,6 +76,7 @@ func resourceCloudIoTRegistry() *schema.Resource { }, "mqtt_config": &schema.Schema{ Type: schema.TypeMap, + Computed: true, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -90,6 +91,7 @@ func resourceCloudIoTRegistry() *schema.Resource { }, "http_config": &schema.Schema{ Type: schema.TypeMap, + Computed: true, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -233,6 +235,11 @@ func resourceCloudIoTRegistryCreate(d *schema.ResourceData, meta interface{}) er d.SetId("") return err } + + // If we infer project and region, they are never actually set so we set them here + d.Set("project", project) + d.Set("region", region) + return resourceCloudIoTRegistryRead(d, meta) } @@ -317,19 +324,9 @@ func resourceCloudIoTRegistryRead(d *schema.ResourceData, meta interface{}) erro } else { d.Set("state_notification_config", nil) } - // If no config exist for mqtt or http config default values are omitted. - mqttState := res.MqttConfig.MqttEnabledState - _, hasMqttConfig := d.GetOk("mqtt_config") - if mqttState != mqttEnabled || hasMqttConfig { - d.Set("mqtt_config", - map[string]string{"mqtt_enabled_state": mqttState}) - } - httpState := res.HttpConfig.HttpEnabledState - _, hasHttpConfig := d.GetOk("http_config") - if httpState != httpEnabled || hasHttpConfig { - d.Set("http_config", - map[string]string{"http_enabled_state": httpState}) - } + + d.Set("mqtt_config", map[string]string{"mqtt_enabled_state": res.MqttConfig.MqttEnabledState}) + d.Set("http_config", map[string]string{"http_enabled_state": res.HttpConfig.HttpEnabledState}) credentials := make([]map[string]interface{}, len(res.Credentials)) for i, item := range res.Credentials { diff --git a/provider/terraform/tests/resource_cloudiot_registry_test.go b/provider/terraform/tests/resource_cloudiot_registry_test.go new file mode 100644 index 000000000000..03d81c56cef2 --- /dev/null +++ b/provider/terraform/tests/resource_cloudiot_registry_test.go @@ -0,0 +1,181 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccCloudIoTRegistry_basic(t *testing.T) { + t.Parallel() + + registryName := fmt.Sprintf("psregistry-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudIoTRegistryDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCloudIoTRegistry_basic(registryName), + Check: resource.ComposeTestCheckFunc( + testAccCloudIoTRegistryExists( + "google_cloudiot_registry.foobar"), + ), + }, + { + ResourceName: "google_cloudiot_registry.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCloudIoTRegistry_extended(t *testing.T) { + t.Parallel() + + registryName := fmt.Sprintf("psregistry-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudIoTRegistryDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCloudIoTRegistry_extended(registryName), + Check: resource.ComposeTestCheckFunc( + testAccCloudIoTRegistryExists( + "google_cloudiot_registry.foobar"), + ), + }, + { + ResourceName: "google_cloudiot_registry.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCloudIoTRegistry_update(t *testing.T) { + t.Parallel() + + registryName := fmt.Sprintf("psregistry-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudIoTRegistryDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCloudIoTRegistry_basic(registryName), + Check: resource.ComposeTestCheckFunc( + testAccCloudIoTRegistryExists( + "google_cloudiot_registry.foobar"), + ), + }, + resource.TestStep{ + Config: testAccCloudIoTRegistry_extended(registryName), + }, + resource.TestStep{ + Config: testAccCloudIoTRegistry_basic(registryName), + }, + { + ResourceName: "google_cloudiot_registry.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckCloudIoTRegistryDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_cloudiot_registry" { + continue + } + config := testAccProvider.Meta().(*Config) + registry, _ := config.clientCloudIoT.Projects.Locations.Registries.Get(rs.Primary.ID).Do() + if registry != nil { + return fmt.Errorf("Registry still present") + } + } + return nil +} + +func testAccCloudIoTRegistryExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + config := testAccProvider.Meta().(*Config) + _, err := config.clientCloudIoT.Projects.Locations.Registries.Get(rs.Primary.ID).Do() + if err != nil { + return fmt.Errorf("Registry does not exist") + } + return nil + } +} + +func testAccCloudIoTRegistry_basic(registryName string) string { + return fmt.Sprintf(` +resource "google_cloudiot_registry" "foobar" { + name = "%s" +}`, registryName) +} + +func testAccCloudIoTRegistry_extended(registryName string) string { + return fmt.Sprintf(` +resource "google_project_iam_binding" "cloud-iot-iam-binding" { + members = ["serviceAccount:cloud-iot@system.gserviceaccount.com"] + role = "roles/pubsub.publisher" +} + +resource "google_pubsub_topic" "default-devicestatus" { + name = "psregistry-test-devicestatus-%s" +} + +resource "google_pubsub_topic" "default-telemetry" { + name = "psregistry-test-telemetry-%s" +} + +resource "google_cloudiot_registry" "foobar" { + depends_on = ["google_project_iam_binding.cloud-iot-iam-binding"] + + name = "%s" + + event_notification_config = { + pubsub_topic_name = "${google_pubsub_topic.default-devicestatus.id}" + } + + state_notification_config = { + pubsub_topic_name = "${google_pubsub_topic.default-telemetry.id}" + } + + http_config = { + http_enabled_state = "HTTP_DISABLED" + } + + mqtt_config = { + mqtt_enabled_state = "MQTT_DISABLED" + } + + credentials = [ + { + "public_key_certificate" = { + format = "X509_CERTIFICATE_PEM" + certificate = "${file("test-fixtures/rsa_cert.pem")}" + } + }, + ] +} +`, acctest.RandString(10), acctest.RandString(10), registryName) +} From 84e25bd6c9315b285e5eef917aa9f0bb63ee665c Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Wed, 17 Oct 2018 15:47:17 -0700 Subject: [PATCH 07/11] Add Storage ObjectAccessControl to Terraform (#562) Merged PR #562. --- build/ansible | 2 +- build/chef/storage | 2 +- build/puppet/storage | 2 +- build/terraform | 2 +- products/storage/api.yaml | 4 + products/storage/object_access_control.yaml | 27 ++-- products/storage/terraform.yaml | 49 +++++++ ...ogle_storage_object_access_control_test.go | 125 ++++++++++++++++++ provider/terraform/utils/provider.go | 1 + provider/terraform/utils/transport.go | 6 + .../resourceref_as_string.go.erb | 17 +++ ...object_access_control_public_object.tf.erb | 16 +++ 12 files changed, 234 insertions(+), 19 deletions(-) create mode 100644 products/storage/terraform.yaml create mode 100644 provider/terraform/tests/resource_google_storage_object_access_control_test.go create mode 100644 templates/terraform/custom_expand/resourceref_as_string.go.erb create mode 100644 templates/terraform/examples/storage_object_access_control_public_object.tf.erb diff --git a/build/ansible b/build/ansible index f83c565380f4..e50c3ed8ccf0 160000 --- a/build/ansible +++ b/build/ansible @@ -1 +1 @@ -Subproject commit f83c565380f420564e797d8028fe493223b80f39 +Subproject commit e50c3ed8ccf09b11ce1f141f7225536b60299664 diff --git a/build/chef/storage b/build/chef/storage index b06ec3d89a51..c6da01556c01 160000 --- a/build/chef/storage +++ b/build/chef/storage @@ -1 +1 @@ -Subproject commit b06ec3d89a5184ee6a8a34ed50bb737f52cc3e2c +Subproject commit c6da01556c012691b803471e3e40ceb20cfbc208 diff --git a/build/puppet/storage b/build/puppet/storage index 3dcf41e31955..ee67e7ad7c4f 160000 --- a/build/puppet/storage +++ b/build/puppet/storage @@ -1 +1 @@ -Subproject commit 3dcf41e319558a971f0f3a7c1b87b670ed0bd3fa +Subproject commit ee67e7ad7c4fcec815a733da75bc04a68bb2b228 diff --git a/build/terraform b/build/terraform index bc31bef9f9ee..8f7102b72d06 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit bc31bef9f9ee8c3d2ba595e25db0c2741e6b26d3 +Subproject commit 8f7102b72d061951034a6e8bf404754f4d6b86d5 diff --git a/products/storage/api.yaml b/products/storage/api.yaml index 1716d9e89202..f36d2db35a8a 100644 --- a/products/storage/api.yaml +++ b/products/storage/api.yaml @@ -322,6 +322,10 @@ objects: kind: 'storage#objectAccessControl' base_url: b/{{bucket}}/o/{{object}}/acl self_link: b/{{bucket}}/o/{{object}}/acl/{{entity}} + references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Official Documentation': 'https://cloud.google.com/storage/docs/access-control/create-manage-lists' + api: 'https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls' description: | The ObjectAccessControls resources represent the Access Control Lists (ACLs) for objects within Google Cloud Storage. ACLs let you specify diff --git a/products/storage/object_access_control.yaml b/products/storage/object_access_control.yaml index 55f354baac89..ff695258b807 100644 --- a/products/storage/object_access_control.yaml +++ b/products/storage/object_access_control.yaml @@ -27,25 +27,20 @@ output: true - !ruby/object:Api::Type::String name: 'entity' + required: true description: | The entity holding the permission, in one of the following forms: - user-userId - user-email - group-groupId - group-email - domain-domain - project-team-projectId - allUsers - allAuthenticatedUsers - Examples: - The user liz@example.com would be user-liz@example.com. - The group example@googlegroups.com would be - group-example@googlegroups.com. - To refer to all members of the Google Apps for Business domain - example.com, the entity would be domain-example.com. - required: true + * user-{{userId}} + * user-{{email}} (such as "user-liz@example.com") + * group-{{groupId}} + * group-{{email}} (such as "group-example@googlegroups.com") + * domain-{{domain}} (such as "domain-example.com") + * project-team-{{projectId}} + * allUsers + * allAuthenticatedUsers - !ruby/object:Api::Type::String name: 'entityId' + output: true description: 'The ID for the entity' # | 'etag' is not applicable for state convergence. - !ruby/object:Api::Type::Integer @@ -63,6 +58,7 @@ - !ruby/object:Api::Type::NestedObject name: 'projectTeam' description: 'The project team associated with the entity' + output: true properties: - !ruby/object:Api::Type::String name: 'projectNumber' @@ -77,6 +73,7 @@ - !ruby/object:Api::Type::Enum name: 'role' description: 'The access permission for the entity.' + required: true values: - :OWNER - :READER diff --git a/products/storage/terraform.yaml b/products/storage/terraform.yaml new file mode 100644 index 000000000000..1eee3f47f5d6 --- /dev/null +++ b/products/storage/terraform.yaml @@ -0,0 +1,49 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Terraform::Config +overrides: !ruby/object:Provider::ResourceOverrides + Bucket: !ruby/object:Provider::Terraform::ResourceOverride + exclude: true + BucketAccessControl: !ruby/object:Provider::Terraform::ResourceOverride + exclude: true + ObjectAccessControl: !ruby/object:Provider::Terraform::ResourceOverride + example: + - !ruby/object:Provider::Terraform::Examples + name: "storage_object_access_control_public_object" + primary_resource_id: "public_rule" + skip_test: true + vars: + bucket_name: "static-content-bucket" + object_name: "public-object" + id_format: "{{bucket}}/{{object}}/{{entity}}" + import_format: ["{{bucket}}/{{object}}/{{entity}}"] + properties: + id: !ruby/object:Provider::Terraform::PropertyOverride + exclude: true + bucket: !ruby/object:Provider::Terraform::PropertyOverride + custom_expand: 'templates/terraform/custom_expand/resourceref_as_string.go.erb' + object: !ruby/object:Provider::Terraform::PropertyOverride + description: The name of the object to apply the access control to. + DefaultObjectACL: !ruby/object:Provider::Terraform::ResourceOverride + exclude: true + +# This is for copying files over +files: !ruby/object:Provider::Config::Files + # All of these files will be copied verbatim. + copy: +<%= lines(indent(compile('provider/terraform/common~copy.yaml'), 4)) -%> + # These files have templating (ERB) code that will be run. + # This is usually to add licensing info, autogeneration notices, etc. + compile: +<%= lines(indent(compile('provider/terraform/common~compile.yaml'), 4)) -%> diff --git a/provider/terraform/tests/resource_google_storage_object_access_control_test.go b/provider/terraform/tests/resource_google_storage_object_access_control_test.go new file mode 100644 index 000000000000..cb2985f8118e --- /dev/null +++ b/provider/terraform/tests/resource_google_storage_object_access_control_test.go @@ -0,0 +1,125 @@ +package google + +import ( + "fmt" + "io/ioutil" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccStorageObjectAccessControl_basic(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + objectName := testAclObjectName() + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectAccessControlDestroy, + Steps: []resource.TestStep{ + { + Config: testGoogleStorageObjectAccessControlBasic(bucketName, objectName, "READER", "allUsers"), + }, + { + ResourceName: "google_storage_object_access_control.default", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageObjectAccessControl_update(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + objectName := testAclObjectName() + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectAccessControlDestroy, + Steps: []resource.TestStep{ + { + Config: testGoogleStorageObjectAccessControlBasic(bucketName, objectName, "READER", "allUsers"), + }, + { + ResourceName: "google_storage_object_access_control.default", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testGoogleStorageObjectAccessControlBasic(bucketName, objectName, "OWNER", "allUsers"), + }, + { + ResourceName: "google_storage_object_access_control.default", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccStorageObjectAccessControlDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_storage_bucket_acl" { + continue + } + + bucket := rs.Primary.Attributes["bucket"] + object := rs.Primary.Attributes["object"] + entity := rs.Primary.Attributes["entity"] + + rePairs, err := config.clientStorage.ObjectAccessControls.List(bucket, object).Do() + if err != nil { + return fmt.Errorf("Can't list role entity acl for object %s in bucket %s", object, bucket) + } + + for _, v := range rePairs.Items { + if v.Entity == entity { + return fmt.Errorf("found entity %s as role entity acl entry for object %s in bucket %s", entity, object, bucket) + } + } + + } + + return nil +} + +func testGoogleStorageObjectAccessControlBasic(bucketName, objectName, role, entity string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_access_control" "default" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role = "%s" + entity = "%s" +} +`, bucketName, objectName, tfObjectAcl.Name(), role, entity) +} diff --git a/provider/terraform/utils/provider.go b/provider/terraform/utils/provider.go index 69c148aca755..85d752d06c1e 100644 --- a/provider/terraform/utils/provider.go +++ b/provider/terraform/utils/provider.go @@ -106,6 +106,7 @@ func Provider() terraform.ResourceProvider { GeneratedFilestoreResourcesMap, GeneratedRedisResourcesMap, GeneratedResourceManagerResourcesMap, + GeneratedStorageResourcesMap, GeneratedMonitoringResourcesMap, map[string]*schema.Resource{ "google_app_engine_application": resourceAppEngineApplication(), diff --git a/provider/terraform/utils/transport.go b/provider/terraform/utils/transport.go index 7b860c7b4ac3..c29b8dad97ea 100644 --- a/provider/terraform/utils/transport.go +++ b/provider/terraform/utils/transport.go @@ -62,6 +62,12 @@ func sendRequest(config *Config, method, rawurl string, body map[string]interfac return nil, err } + // 204 responses will have no body, so we're going to error with "EOF" if we + // try to parse it. Instead, we can just return nil. + if res.StatusCode == 204 { + return nil, nil + } + result := make(map[string]interface{}) if err := json.NewDecoder(res.Body).Decode(&result); err != nil { return nil, err diff --git a/templates/terraform/custom_expand/resourceref_as_string.go.erb b/templates/terraform/custom_expand/resourceref_as_string.go.erb new file mode 100644 index 000000000000..34e8b9099530 --- /dev/null +++ b/templates/terraform/custom_expand/resourceref_as_string.go.erb @@ -0,0 +1,17 @@ +<%# The license inside this block applies to this file. + # Copyright 2018 Google Inc. + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-%> +func expand<%= prefix -%><%= titlelize_property(property) -%>(v interface{}, d *schema.ResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/templates/terraform/examples/storage_object_access_control_public_object.tf.erb b/templates/terraform/examples/storage_object_access_control_public_object.tf.erb new file mode 100644 index 000000000000..84179bd9c937 --- /dev/null +++ b/templates/terraform/examples/storage_object_access_control_public_object.tf.erb @@ -0,0 +1,16 @@ +resource "google_storage_object_access_control" "<%= ctx[:primary_resource_id] %>" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role = "READER" + entity = "allUsers" +} + +resource "google_storage_bucket" "bucket" { + name = "<%= ctx[:vars]['bucket_name'] %>" +} + + resource "google_storage_bucket_object" "object" { + name = "<%= ctx[:vars]['object_name'] %>" + bucket = "${google_storage_bucket.bucket.name}" + source = "../static/img/header-logo.jpg" +} From 62afbbb35bc040448da68585c1f7426276135b0a Mon Sep 17 00:00:00 2001 From: emily Date: Wed, 17 Oct 2018 16:23:09 -0700 Subject: [PATCH 08/11] fix expand for machine type in composer environment (#571) Merged PR #571. --- .../resource_composer_environment.go | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/provider/terraform/resources/resource_composer_environment.go b/provider/terraform/resources/resource_composer_environment.go index dc220fbe5bc5..c9736ffbfa43 100644 --- a/provider/terraform/resources/resource_composer_environment.go +++ b/provider/terraform/resources/resource_composer_environment.go @@ -677,10 +677,34 @@ func expandComposerEnvironmentZone(v interface{}, d *schema.ResourceData, config return getRelativePath(zone) } -func expandComposerEnvironmentMachineType(v interface{}, d *schema.ResourceData, config *Config, nodeCfgZone interface{}) (string, error) { +func expandComposerEnvironmentMachineType(v interface{}, d *schema.ResourceData, config *Config, nodeCfgZone string) (string, error) { + machineType := v.(string) + requiredZone := GetResourceNameFromSelfLink(nodeCfgZone) + fv, err := ParseMachineTypesFieldValue(v.(string), d, config) if err != nil { - return "", nil + if requiredZone == "" { + return "", err + } + + // Try to construct machine type with zone/project given in config. + project, err := getProject(d, config) + if err != nil { + return "", err + } + + fv = &ZonalFieldValue{ + Project: project, + Zone: requiredZone, + Name: GetResourceNameFromSelfLink(machineType), + resourceType: "machineTypes", + } + } + + // Make sure zone in node_config.machineType matches node_config.zone if + // given. + if requiredZone != "" && fv.Zone != requiredZone { + return "", fmt.Errorf("node_config machine_type %q must be in node_config zone %q", machineType, requiredZone) } return fv.RelativeLink(), nil } From 19ace702c33d18cc597869411d1c1ab54551642f Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Wed, 17 Oct 2018 17:17:21 -0700 Subject: [PATCH 09/11] Terraform: Add storage, missing project resource test files to MM. (#582) Merged PR #582. --- .../tests/resource_storage_bucket_acl_test.go | 290 ++++++ .../tests/resource_storage_bucket_iam_test.go | 265 +++++ .../resource_storage_bucket_object_test.go | 377 +++++++ .../tests/resource_storage_bucket_test.go | 979 ++++++++++++++++++ ...esource_storage_default_object_acl_test.go | 218 ++++ .../resource_storage_notification_test.go | 252 +++++ .../tests/resource_storage_object_acl_test.go | 382 +++++++ .../resource_usage_export_bucket_test.go | 59 ++ 8 files changed, 2822 insertions(+) create mode 100644 provider/terraform/tests/resource_storage_bucket_acl_test.go create mode 100644 provider/terraform/tests/resource_storage_bucket_iam_test.go create mode 100644 provider/terraform/tests/resource_storage_bucket_object_test.go create mode 100644 provider/terraform/tests/resource_storage_bucket_test.go create mode 100644 provider/terraform/tests/resource_storage_default_object_acl_test.go create mode 100644 provider/terraform/tests/resource_storage_notification_test.go create mode 100644 provider/terraform/tests/resource_storage_object_acl_test.go create mode 100644 provider/terraform/tests/resource_usage_export_bucket_test.go diff --git a/provider/terraform/tests/resource_storage_bucket_acl_test.go b/provider/terraform/tests/resource_storage_bucket_acl_test.go new file mode 100644 index 000000000000..53885ffbad8f --- /dev/null +++ b/provider/terraform/tests/resource_storage_bucket_acl_test.go @@ -0,0 +1,290 @@ +package google + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +var ( + roleEntityBasic1 = "OWNER:user-paddy@hashicorp.com" + roleEntityBasic2 = "READER:user-paddy@carvers.co" + roleEntityBasic3_owner = "OWNER:user-paddy@paddy.io" + roleEntityBasic3_reader = "READER:user-foran.paddy@gmail.com" + + roleEntityOwners = "OWNER:project-owners-" + os.Getenv("GOOGLE_PROJECT_NUMBER") + roleEntityEditors = "OWNER:project-editors-" + os.Getenv("GOOGLE_PROJECT_NUMBER") + roleEntityViewers = "READER:project-viewers-" + os.Getenv("GOOGLE_PROJECT_NUMBER") +) + +func testBucketName() string { + return fmt.Sprintf("%s-%d", "tf-test-acl-bucket", acctest.RandInt()) +} + +func TestAccStorageBucketAcl_basic(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + skipIfEnvNotSet(t, "GOOGLE_PROJECT_NUMBER") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic1(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic2), + ), + }, + }, + }) +} + +func TestAccStorageBucketAcl_upgrade(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + skipIfEnvNotSet(t, "GOOGLE_PROJECT_NUMBER") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic1(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic2), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic2(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasicDelete(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAclDelete(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageBucketAclDelete(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAclDelete(bucketName, roleEntityBasic3_owner), + ), + }, + }, + }) +} + +func TestAccStorageBucketAcl_downgrade(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + skipIfEnvNotSet(t, "GOOGLE_PROJECT_NUMBER") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic2(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasic3(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAcl(bucketName, roleEntityBasic3_reader), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageBucketsAclBasicDelete(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageBucketAclDelete(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageBucketAclDelete(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageBucketAclDelete(bucketName, roleEntityBasic3_owner), + ), + }, + }, + }) +} + +func TestAccStorageBucketAcl_predefined(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclPredefined(bucketName), + }, + }, + }) +} + +// Test that we allow the API to reorder our role entities without perma-diffing. +func TestAccStorageBucketAcl_unordered(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + skipIfEnvNotSet(t, "GOOGLE_PROJECT_NUMBER") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsAclUnordered(bucketName), + }, + }, + }) +} + +func testAccCheckGoogleStorageBucketAclDelete(bucket, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + _, err := config.clientStorage.BucketAccessControls.Get(bucket, roleEntity.Entity).Do() + + if err != nil { + return nil + } + + return fmt.Errorf("Error, entity %s still exists", roleEntity.Entity) + } +} + +func testAccCheckGoogleStorageBucketAcl(bucket, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + res, err := config.clientStorage.BucketAccessControls.Get(bucket, roleEntity.Entity).Do() + + if err != nil { + return fmt.Errorf("Error retrieving contents of acl for bucket %s: %s", bucket, err) + } + + if res.Role != roleEntity.Role { + return fmt.Errorf("Error, Role mismatch %s != %s", res.Role, roleEntity.Role) + } + + return nil + } +} + +func testAccStorageBucketAclDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_storage_bucket_acl" { + continue + } + + bucket := rs.Primary.Attributes["bucket"] + + _, err := config.clientStorage.BucketAccessControls.List(bucket).Do() + + if err == nil { + return fmt.Errorf("Acl for bucket %s still exists", bucket) + } + } + + return nil +} + +func testGoogleStorageBucketsAclBasic1(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s", "%s", "%s", "%s"] +} +`, bucketName, roleEntityOwners, roleEntityEditors, roleEntityViewers, roleEntityBasic1, roleEntityBasic2) +} + +func testGoogleStorageBucketsAclBasic2(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s", "%s", "%s", "%s"] +} +`, bucketName, roleEntityOwners, roleEntityEditors, roleEntityViewers, roleEntityBasic2, roleEntityBasic3_owner) +} + +func testGoogleStorageBucketsAclBasicDelete(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = [] +} +`, bucketName) +} + +func testGoogleStorageBucketsAclBasic3(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s", "%s", "%s", "%s"] +} +`, bucketName, roleEntityOwners, roleEntityEditors, roleEntityViewers, roleEntityBasic2, roleEntityBasic3_reader) +} + +func testGoogleStorageBucketsAclUnordered(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s", "%s", "%s", "%s"] +} +`, bucketName, roleEntityBasic1, roleEntityViewers, roleEntityOwners, roleEntityBasic2, roleEntityEditors) +} + +func testGoogleStorageBucketsAclPredefined(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + predefined_acl = "projectPrivate" + default_acl = "projectPrivate" +} +`, bucketName) +} diff --git a/provider/terraform/tests/resource_storage_bucket_iam_test.go b/provider/terraform/tests/resource_storage_bucket_iam_test.go new file mode 100644 index 000000000000..12fa2b957173 --- /dev/null +++ b/provider/terraform/tests/resource_storage_bucket_iam_test.go @@ -0,0 +1,265 @@ +package google + +import ( + "fmt" + "reflect" + "sort" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccStorageBucketIamBinding(t *testing.T) { + t.Parallel() + + bucket := acctest.RandomWithPrefix("tf-test") + account := acctest.RandomWithPrefix("tf-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test IAM Binding creation + Config: testAccStorageBucketIamBinding_basic(bucket, account), + Check: testAccCheckGoogleStorageBucketIam(bucket, "roles/storage.objectViewer", []string{ + fmt.Sprintf("serviceAccount:%s-1@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + { + // Test IAM Binding update + Config: testAccStorageBucketIamBinding_update(bucket, account), + Check: testAccCheckGoogleStorageBucketIam(bucket, "roles/storage.objectViewer", []string{ + fmt.Sprintf("serviceAccount:%s-1@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + }, + }) +} + +func TestAccStorageBucketIamPolicy(t *testing.T) { + t.Parallel() + + bucket := acctest.RandomWithPrefix("tf-test") + account := acctest.RandomWithPrefix("tf-test") + serviceAcct := getTestServiceAccountFromEnv(t) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test IAM Policy creation + Config: testAccStorageBucketIamPolicy_basic(bucket, account, serviceAcct), + Check: testAccCheckGoogleStorageBucketIam(bucket, "roles/storage.objectViewer", []string{ + fmt.Sprintf("serviceAccount:%s-1@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + { + // Test IAM Policy update + Config: testAccStorageBucketIamPolicy_update(bucket, account, serviceAcct), + Check: testAccCheckGoogleStorageBucketIam(bucket, "roles/storage.objectViewer", []string{ + fmt.Sprintf("serviceAccount:%s-1@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + fmt.Sprintf("serviceAccount:%s-2@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + }, + }) +} + +func TestAccStorageBucketIamMember(t *testing.T) { + t.Parallel() + + bucket := acctest.RandomWithPrefix("tf-test") + account := acctest.RandomWithPrefix("tf-test") + + 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: testAccStorageBucketIamMember_basic(bucket, account), + Check: testAccCheckGoogleStorageBucketIam(bucket, "roles/storage.admin", []string{ + fmt.Sprintf("serviceAccount:%s-1@%s.iam.gserviceaccount.com", account, getTestProjectFromEnv()), + }), + }, + }, + }) +} + +func testAccCheckGoogleStorageBucketIam(bucket, role string, members []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + p, err := config.clientStorage.Buckets.GetIamPolicy(bucket).Do() + if err != nil { + return err + } + + for _, binding := range p.Bindings { + if binding.Role == role { + sort.Strings(members) + sort.Strings(binding.Members) + + if reflect.DeepEqual(members, binding.Members) { + return nil + } + + return fmt.Errorf("Binding found but expected members is %v, got %v", members, binding.Members) + } + } + + return fmt.Errorf("No binding for role %q", role) + } +} + +func testAccStorageBucketIamPolicy_update(bucket, account, serviceAcct string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_service_account" "test-account-1" { + account_id = "%s-1" + display_name = "Iam Testing Account" +} + +resource "google_service_account" "test-account-2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + + +data "google_iam_policy" "foo-policy" { + binding { + role = "roles/storage.objectViewer" + + members = [ + "serviceAccount:${google_service_account.test-account-1.email}", + "serviceAccount:${google_service_account.test-account-2.email}", + ] + } + + binding { + role = "roles/storage.admin" + members = [ + "serviceAccount:%s", + ] + } +} + +resource "google_storage_bucket_iam_policy" "bucket-binding" { + bucket = "${google_storage_bucket.bucket.name}" + policy_data = "${data.google_iam_policy.foo-policy.policy_data}" +} + +`, bucket, account, account, serviceAcct) +} + +func testAccStorageBucketIamPolicy_basic(bucket, account, serviceAcct string) string { + return fmt.Sprintf(` + +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_service_account" "test-account-1" { + account_id = "%s-1" + display_name = "Iam Testing Account" +} + + +data "google_iam_policy" "foo-policy" { + binding { + role = "roles/storage.objectViewer" + members = [ + "serviceAccount:${google_service_account.test-account-1.email}", + ] + } + + binding { + role = "roles/storage.admin" + members = [ + "serviceAccount:%s", + ] + } +} + +resource "google_storage_bucket_iam_policy" "bucket-binding" { + bucket = "${google_storage_bucket.bucket.name}" + policy_data = "${data.google_iam_policy.foo-policy.policy_data}" +} + + +`, bucket, account, serviceAcct) +} + +func testAccStorageBucketIamBinding_basic(bucket, account string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_service_account" "test-account-1" { + account_id = "%s-1" + display_name = "Iam Testing Account" +} + +resource "google_storage_bucket_iam_binding" "foo" { + bucket = "${google_storage_bucket.bucket.name}" + role = "roles/storage.objectViewer" + members = [ + "serviceAccount:${google_service_account.test-account-1.email}", + ] +} +`, bucket, account) +} + +func testAccStorageBucketIamBinding_update(bucket, account string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_service_account" "test-account-1" { + account_id = "%s-1" + display_name = "Iam Testing Account" +} + +resource "google_service_account" "test-account-2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_storage_bucket_iam_binding" "foo" { + bucket = "${google_storage_bucket.bucket.name}" + role = "roles/storage.objectViewer" + members = [ + "serviceAccount:${google_service_account.test-account-1.email}", + "serviceAccount:${google_service_account.test-account-2.email}", + ] +} +`, bucket, account, account) +} + +func testAccStorageBucketIamMember_basic(bucket, account string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_service_account" "test-account-1" { + account_id = "%s-1" + display_name = "Iam Testing Account" +} + +resource "google_storage_bucket_iam_member" "foo" { + bucket = "${google_storage_bucket.bucket.name}" + role = "roles/storage.admin" + member = "serviceAccount:${google_service_account.test-account-1.email}" +} +`, bucket, account) +} diff --git a/provider/terraform/tests/resource_storage_bucket_object_test.go b/provider/terraform/tests/resource_storage_bucket_object_test.go new file mode 100644 index 000000000000..b7df23aa8012 --- /dev/null +++ b/provider/terraform/tests/resource_storage_bucket_object_test.go @@ -0,0 +1,377 @@ +package google + +import ( + "crypto/md5" + "encoding/base64" + "fmt" + "io/ioutil" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "os" + + "google.golang.org/api/storage/v1" +) + +const ( + objectName = "tf-gce-test" + content = "now this is content!" +) + +func TestAccStorageObject_basic(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + data := []byte("data data data") + h := md5.New() + h.Write(data) + data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil)) + + testFile := getNewTmpTestFile(t, "tf-test") + ioutil.WriteFile(testFile.Name(), data, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsObjectBasic(bucketName, testFile.Name()), + Check: testAccCheckGoogleStorageObject(bucketName, objectName, data_md5), + }, + }, + }) +} + +func TestAccStorageObject_recreate(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + + writeFile := func(name string, data []byte) string { + h := md5.New() + h.Write(data) + data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil)) + + ioutil.WriteFile(name, data, 0644) + return data_md5 + } + testFile := getNewTmpTestFile(t, "tf-test") + data_md5 := writeFile(testFile.Name(), []byte("data data data")) + updatedName := testFile.Name() + ".update" + updated_data_md5 := writeFile(updatedName, []byte("datum")) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsObjectBasic(bucketName, testFile.Name()), + Check: testAccCheckGoogleStorageObject(bucketName, objectName, data_md5), + }, + resource.TestStep{ + PreConfig: func() { + err := os.Rename(updatedName, testFile.Name()) + if err != nil { + t.Errorf("Failed to rename %s to %s", updatedName, testFile.Name()) + } + }, + Config: testGoogleStorageBucketsObjectBasic(bucketName, testFile.Name()), + Check: testAccCheckGoogleStorageObject(bucketName, objectName, updated_data_md5), + }, + }, + }) +} + +func TestAccStorageObject_content(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + data := []byte(content) + h := md5.New() + h.Write(data) + data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil)) + + testFile := getNewTmpTestFile(t, "tf-test") + ioutil.WriteFile(testFile.Name(), data, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsObjectContent(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObject(bucketName, objectName, data_md5), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "content_type", "text/plain; charset=utf-8"), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "storage_class", "STANDARD"), + ), + }, + }, + }) +} + +func TestAccStorageObject_withContentCharacteristics(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + data := []byte(content) + h := md5.New() + h.Write(data) + data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil)) + testFile := getNewTmpTestFile(t, "tf-test") + ioutil.WriteFile(testFile.Name(), data, 0644) + + disposition, encoding, language, content_type := "inline", "compress", "en", "binary/octet-stream" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsObject_optionalContentFields( + bucketName, disposition, encoding, language, content_type), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObject(bucketName, objectName, data_md5), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "content_disposition", disposition), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "content_encoding", encoding), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "content_language", language), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "content_type", content_type), + ), + }, + }, + }) +} + +func TestAccStorageObject_dynamicContent(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsObjectDynamicContent(testBucketName()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "content_type", "text/plain; charset=utf-8"), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "storage_class", "STANDARD"), + ), + }, + }, + }) +} + +func TestAccStorageObject_cacheControl(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + data := []byte(content) + h := md5.New() + h.Write(data) + data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil)) + testFile := getNewTmpTestFile(t, "tf-test") + ioutil.WriteFile(testFile.Name(), data, 0644) + + cacheControl := "private" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsObject_cacheControl(bucketName, testFile.Name(), cacheControl), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObject(bucketName, objectName, data_md5), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "cache_control", cacheControl), + ), + }, + }, + }) +} + +func TestAccStorageObject_storageClass(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + data := []byte(content) + h := md5.New() + h.Write(data) + data_md5 := base64.StdEncoding.EncodeToString(h.Sum(nil)) + testFile := getNewTmpTestFile(t, "tf-test") + ioutil.WriteFile(testFile.Name(), data, 0644) + + storageClass := "MULTI_REGIONAL" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsObject_storageClass(bucketName, storageClass), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObject(bucketName, objectName, data_md5), + resource.TestCheckResourceAttr( + "google_storage_bucket_object.object", "storage_class", storageClass), + ), + }, + }, + }) +} + +func testAccCheckGoogleStorageObject(bucket, object, md5 string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + objectsService := storage.NewObjectsService(config.clientStorage) + + getCall := objectsService.Get(bucket, object) + res, err := getCall.Do() + + if err != nil { + return fmt.Errorf("Error retrieving contents of object %s: %s", object, err) + } + + if md5 != res.Md5Hash { + return fmt.Errorf("Error contents of %s garbled, md5 hashes don't match (%s, %s)", object, md5, res.Md5Hash) + } + + return nil + } +} + +func testAccStorageObjectDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_storage_bucket_object" { + continue + } + + bucket := rs.Primary.Attributes["bucket"] + name := rs.Primary.Attributes["name"] + + objectsService := storage.NewObjectsService(config.clientStorage) + + getCall := objectsService.Get(bucket, name) + _, err := getCall.Do() + + if err == nil { + return fmt.Errorf("Object %s still exists", name) + } + } + + return nil +} + +func testGoogleStorageBucketsObjectContent(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + content = "%s" +} +`, bucketName, objectName, content) +} + +func testGoogleStorageBucketsObjectDynamicContent(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + content = "${google_storage_bucket.bucket.project}" +} +`, bucketName, objectName) +} + +func testGoogleStorageBucketsObjectBasic(bucketName, sourceFilename string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} +`, bucketName, objectName, sourceFilename) +} + +func testGoogleStorageBucketsObject_optionalContentFields( + bucketName, disposition, encoding, language, content_type string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + content = "%s" + content_disposition = "%s" + content_encoding = "%s" + content_language = "%s" + content_type = "%s" +} +`, bucketName, objectName, content, disposition, encoding, language, content_type) +} + +func testGoogleStorageBucketsObject_cacheControl(bucketName, sourceFilename, cacheControl string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" + cache_control = "%s" +} +`, bucketName, objectName, sourceFilename, cacheControl) +} + +func testGoogleStorageBucketsObject_storageClass(bucketName string, storageClass string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + content = "%s" + storage_class = "%s" +} +`, bucketName, objectName, content, storageClass) +} + +// Creates a new tmp test file. Fails the current test if we cannot create +// new tmp file in the filesystem. +func getNewTmpTestFile(t *testing.T, prefix string) *os.File { + testFile, err := ioutil.TempFile("", prefix) + if err != nil { + t.Fatalf("Cannot create temp file: %s", err) + } + return testFile +} diff --git a/provider/terraform/tests/resource_storage_bucket_test.go b/provider/terraform/tests/resource_storage_bucket_test.go new file mode 100644 index 000000000000..5d78ebde436f --- /dev/null +++ b/provider/terraform/tests/resource_storage_bucket_test.go @@ -0,0 +1,979 @@ +package google + +import ( + "bytes" + "fmt" + "log" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "google.golang.org/api/googleapi" + "google.golang.org/api/storage/v1" +) + +func TestAccStorageBucket_basic(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_basic(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "location", "US"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "force_destroy", "false"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + ), + }, + resource.TestStep{ + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_lowercaseLocation(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_lowercaseLocation(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + ), + }, + }, + }) +} + +func TestAccStorageBucket_customAttributes(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_customAttributes(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "location", "EU"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "force_destroy", "true"), + ), + }, + }, + }) +} + +func TestAccStorageBucket_lifecycleRules(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acc-bucket-%d", acctest.RandInt()) + + hash_step0_lc0_action := resourceGCSBucketLifecycleRuleActionHash(map[string]interface{}{"type": "SetStorageClass", "storage_class": "NEARLINE"}) + hash_step0_lc0_condition := resourceGCSBucketLifecycleRuleConditionHash(map[string]interface{}{"age": 2, "created_before": "", "is_live": false, "num_newer_versions": 0}) + + hash_step0_lc1_action := resourceGCSBucketLifecycleRuleActionHash(map[string]interface{}{"type": "Delete", "storage_class": ""}) + hash_step0_lc1_condition := resourceGCSBucketLifecycleRuleConditionHash(map[string]interface{}{"age": 10, "created_before": "", "is_live": false, "num_newer_versions": 0}) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_lifecycleRules(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.#", "2"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.0.action.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.0.action.%d.type", hash_step0_lc0_action), "SetStorageClass"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.0.action.%d.storage_class", hash_step0_lc0_action), "NEARLINE"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.0.condition.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.0.condition.%d.age", hash_step0_lc0_condition), "2"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.1.action.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.1.action.%d.type", hash_step0_lc1_action), "Delete"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.1.condition.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.1.condition.%d.age", hash_step0_lc1_condition), "10"), + ), + }, + resource.TestStep{ + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_storageClass(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acc-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_storageClass(bucketName, "MULTI_REGIONAL", ""), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "storage_class", "MULTI_REGIONAL"), + ), + }, + { + Config: testAccStorageBucket_storageClass(bucketName, "NEARLINE", ""), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "storage_class", "NEARLINE"), + ), + }, + { + Config: testAccStorageBucket_storageClass(bucketName, "REGIONAL", "US-CENTRAL1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "storage_class", "REGIONAL"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "location", "US-CENTRAL1"), + ), + }, + { + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_update(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + hash_step2_lc0_action := resourceGCSBucketLifecycleRuleActionHash(map[string]interface{}{"type": "Delete", "storage_class": ""}) + hash_step2_lc0_condition := resourceGCSBucketLifecycleRuleConditionHash(map[string]interface{}{"age": 10, "created_before": "", "is_live": false, "num_newer_versions": 0}) + + hash_step3_lc0_action := resourceGCSBucketLifecycleRuleActionHash(map[string]interface{}{"type": "SetStorageClass", "storage_class": "NEARLINE"}) + hash_step3_lc0_condition := resourceGCSBucketLifecycleRuleConditionHash(map[string]interface{}{"age": 2, "created_before": "", "is_live": false, "num_newer_versions": 0}) + + hash_step3_lc1_action := resourceGCSBucketLifecycleRuleActionHash(map[string]interface{}{"type": "Delete", "storage_class": ""}) + hash_step3_lc1_condition := resourceGCSBucketLifecycleRuleConditionHash(map[string]interface{}{"age": 10, "created_before": "", "is_live": false, "num_newer_versions": 2}) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_basic(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "location", "US"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "force_destroy", "false"), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_customAttributes(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "location", "EU"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "force_destroy", "true"), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_customAttributes_withLifecycle1(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "location", "EU"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "force_destroy", "true"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.0.action.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.0.action.%d.type", hash_step2_lc0_action), "Delete"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.0.condition.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.0.condition.%d.age", hash_step2_lc0_condition), "10"), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_customAttributes_withLifecycle2(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "location", "EU"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "force_destroy", "true"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.#", "2"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.0.action.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.0.action.%d.type", hash_step3_lc0_action), "SetStorageClass"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.0.action.%d.storage_class", hash_step3_lc0_action), "NEARLINE"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.0.condition.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.0.condition.%d.age", hash_step3_lc0_condition), "2"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.1.action.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.1.action.%d.type", hash_step3_lc1_action), "Delete"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.1.condition.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.1.condition.%d.age", hash_step3_lc1_condition), "10"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", fmt.Sprintf("lifecycle_rule.1.condition.%d.num_newer_versions", hash_step3_lc1_condition), "2"), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_customAttributes(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "location", "EU"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "force_destroy", "true"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "lifecycle_rule.#", "0"), + ), + }, + }, + }) +} + +func TestAccStorageBucket_forceDestroy(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_customAttributes(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_customAttributes(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketPutItem(bucketName), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_customAttributes(acctest.RandomWithPrefix("tf-test-acl-bucket")), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketMissing(bucketName), + ), + }, + }, + }) +} + +func TestAccStorageBucket_forceDestroyWithVersioning(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acc-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_forceDestroyWithVersioning(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_forceDestroyWithVersioning(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketPutItem(bucketName), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_forceDestroyWithVersioning(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketPutItem(bucketName), + ), + }, + }, + }) +} + +func TestAccStorageBucket_versioning(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_versioning(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "versioning.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "versioning.0.enabled", "true"), + ), + }, + resource.TestStep{ + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageBucket_logging(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_logging(bucketName, "log-bucket"), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "logging.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "logging.0.log_bucket", "log-bucket"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "logging.0.log_object_prefix", bucketName), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_loggingWithPrefix(bucketName, "another-log-bucket", "object-prefix"), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "logging.#", "1"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "logging.0.log_bucket", "another-log-bucket"), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "logging.0.log_object_prefix", "object-prefix"), + ), + }, + resource.TestStep{ + Config: testAccStorageBucket_basic(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "logging.#", "0"), + ), + }, + }, + }) +} + +func TestAccStorageBucket_cors(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageBucketsCors(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + ), + }, + }, + }) + + if len(bucket.Cors) != 2 { + t.Errorf("Expected # of cors elements to be 2, got %d", len(bucket.Cors)) + } + + firstArr := bucket.Cors[0] + if firstArr.MaxAgeSeconds != 10 { + t.Errorf("Expected first block's MaxAgeSeconds to be 10, got %d", firstArr.MaxAgeSeconds) + } + + for i, v := range []string{"abc", "def"} { + if firstArr.Origin[i] != v { + t.Errorf("Expected value in first block origin to be to be %v, got %v", v, firstArr.Origin[i]) + } + } + + for i, v := range []string{"a1a"} { + if firstArr.Method[i] != v { + t.Errorf("Expected value in first block method to be to be %v, got %v", v, firstArr.Method[i]) + } + } + + for i, v := range []string{"123", "456", "789"} { + if firstArr.ResponseHeader[i] != v { + t.Errorf("Expected value in first block response headerto be to be %v, got %v", v, firstArr.ResponseHeader[i]) + } + } + + secondArr := bucket.Cors[1] + if secondArr.MaxAgeSeconds != 5 { + t.Errorf("Expected second block's MaxAgeSeconds to be 5, got %d", secondArr.MaxAgeSeconds) + } + + for i, v := range []string{"ghi", "jkl"} { + if secondArr.Origin[i] != v { + t.Errorf("Expected value in second block origin to be to be %v, got %v", v, secondArr.Origin[i]) + } + } + + for i, v := range []string{"z9z"} { + if secondArr.Method[i] != v { + t.Errorf("Expected value in second block method to be to be %v, got %v", v, secondArr.Method[i]) + } + } + + for i, v := range []string{"000"} { + if secondArr.ResponseHeader[i] != v { + t.Errorf("Expected value in second block response headerto be to be %v, got %v", v, secondArr.ResponseHeader[i]) + } + } +} + +func TestAccStorageBucket_encryption(t *testing.T) { + t.Parallel() + + projectId := "terraform-" + acctest.RandString(10) + projectOrg := getTestOrgFromEnv(t) + projectBillingAccount := getTestBillingAccountFromEnv(t) + keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + bucketName := fmt.Sprintf("tf-test-crypto-bucket-%d", acctest.RandInt()) + var bucket storage.Bucket + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccStorageBucket_encryption(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + ), + }, + }, + }) +} + +func TestAccStorageBucket_labels(t *testing.T) { + t.Parallel() + + var bucket storage.Bucket + bucketName := fmt.Sprintf("tf-test-acl-bucket-%d", acctest.RandInt()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageBucketDestroy, + Steps: []resource.TestStep{ + // Going from two labels + resource.TestStep{ + Config: testAccStorageBucket_updateLabels(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + testAccCheckStorageBucketHasLabel(&bucket, "my-label", "my-updated-label-value"), + testAccCheckStorageBucketHasLabel(&bucket, "a-new-label", "a-new-label-value"), + ), + }, + resource.TestStep{ + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + }, + // Down to only one label (test single label deletion) + resource.TestStep{ + Config: testAccStorageBucket_labels(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + testAccCheckStorageBucketHasLabel(&bucket, "my-label", "my-label-value"), + ), + }, + resource.TestStep{ + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + }, + // And make sure deleting all labels work + resource.TestStep{ + Config: testAccStorageBucket_basic(bucketName), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageBucketExists( + "google_storage_bucket.bucket", bucketName, &bucket), + testAccCheckStorageBucketHasNoLabels(&bucket), + ), + }, + resource.TestStep{ + ResourceName: "google_storage_bucket.bucket", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckStorageBucketExists(n string, bucketName string, bucket *storage.Bucket) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Project_ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientStorage.Buckets.Get(rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Id != rs.Primary.ID { + return fmt.Errorf("Bucket not found") + } + + if found.Name != bucketName { + return fmt.Errorf("expected name %s, got %s", bucketName, found.Name) + } + + *bucket = *found + return nil + } +} + +func testAccCheckStorageBucketHasLabel(bucket *storage.Bucket, key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + val, ok := bucket.Labels[key] + if !ok { + return fmt.Errorf("Label with key %s not found", key) + } + + if val != value { + return fmt.Errorf("Label value did not match for key %s: expected %s but found %s", key, value, val) + } + return nil + } +} + +func testAccCheckStorageBucketHasNoLabels(bucket *storage.Bucket) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(bucket.Labels) > 0 { + return fmt.Errorf("Expected 0 labels, found %v", bucket.Labels) + } + return nil + } +} + +func testAccCheckStorageBucketPutItem(bucketName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + data := bytes.NewBufferString("test") + dataReader := bytes.NewReader(data.Bytes()) + object := &storage.Object{Name: "bucketDestroyTestFile"} + + // This needs to use Media(io.Reader) call, otherwise it does not go to /upload API and fails + if res, err := config.clientStorage.Objects.Insert(bucketName, object).Media(dataReader).Do(); err == nil { + log.Printf("[INFO] Created object %v at location %v\n\n", res.Name, res.SelfLink) + } else { + return fmt.Errorf("Objects.Insert failed: %v", err) + } + + return nil + } +} + +func testAccCheckStorageBucketMissing(bucketName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + _, err := config.clientStorage.Buckets.Get(bucketName).Do() + if err == nil { + return fmt.Errorf("Found %s", bucketName) + } + + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + return nil + } + + return err + } +} + +func testAccStorageBucketDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_storage_bucket" { + continue + } + + _, err := config.clientStorage.Buckets.Get(rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Bucket still exists") + } + } + + return nil +} + +func testAccStorageBucket_basic(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} +`, bucketName) +} + +func testAccStorageBucket_lowercaseLocation(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + location = "eu" +} +`, bucketName) +} + +func testAccStorageBucket_customAttributes(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + location = "EU" + force_destroy = "true" +} +`, bucketName) +} + +func testAccStorageBucket_customAttributes_withLifecycle1(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + location = "EU" + force_destroy = "true" + lifecycle_rule { + action { + type = "Delete" + } + condition { + age = 10 + } + } +} +`, bucketName) +} + +func testAccStorageBucket_customAttributes_withLifecycle2(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + location = "EU" + force_destroy = "true" + lifecycle_rule { + action { + type = "SetStorageClass" + storage_class = "NEARLINE" + } + condition { + age = 2 + } + } + lifecycle_rule { + action { + type = "Delete" + } + condition { + age = 10 + num_newer_versions = 2 + } + } +} +`, bucketName) +} + +func testAccStorageBucket_storageClass(bucketName, storageClass, location string) string { + var locationBlock string + if location != "" { + locationBlock = fmt.Sprintf(` + location = "%s"`, location) + } + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + storage_class = "%s"%s +} +`, bucketName, storageClass, locationBlock) +} + +func testGoogleStorageBucketsCors(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + cors { + origin = ["abc", "def"] + method = ["a1a"] + response_header = ["123", "456", "789"] + max_age_seconds = 10 + } + + cors { + origin = ["ghi", "jkl"] + method = ["z9z"] + response_header = ["000"] + max_age_seconds = 5 + } +} +`, bucketName) +} + +func testAccStorageBucket_forceDestroyWithVersioning(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + force_destroy = "true" + versioning = { + enabled = "true" + } +} +`, bucketName) +} + +func testAccStorageBucket_versioning(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + versioning = { + enabled = "true" + } +} +`, bucketName) +} + +func testAccStorageBucket_logging(bucketName string, logBucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + logging = { + log_bucket = "%s" + } +} +`, bucketName, logBucketName) +} + +func testAccStorageBucket_loggingWithPrefix(bucketName string, logBucketName string, prefix string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + logging = { + log_bucket = "%s" + log_object_prefix = "%s" + } +} +`, bucketName, logBucketName, prefix) +} + +func testAccStorageBucket_lifecycleRules(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + lifecycle_rule { + action { + type = "SetStorageClass" + storage_class = "NEARLINE" + } + condition { + age = 2 + } + } + lifecycle_rule { + action { + type = "Delete" + } + condition { + age = 10 + } + } +} +`, bucketName) +} + +func testAccStorageBucket_labels(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + labels { + my-label = "my-label-value" + } +} +`, bucketName) +} + +func testAccStorageBucket_encryption(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, bucketName string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + name = "%s" + project_id = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + + services = [ + "cloudkms.googleapis.com", + ] +} + +resource "google_kms_key_ring" "key_ring" { + project = "${google_project_services.acceptance.project}" + name = "%s" + location = "us" +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "%s" + key_ring = "${google_kms_key_ring.key_ring.id}" + rotation_period = "1000000s" +} + +resource "google_storage_bucket" "bucket" { + name = "%s" + encryption { + default_kms_key_name = "${google_kms_crypto_key.crypto_key.self_link}" + } +} + `, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, bucketName) +} + +func testAccStorageBucket_updateLabels(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" + labels { + my-label = "my-updated-label-value" + a-new-label = "a-new-label-value" + } +} +`, bucketName) +} diff --git a/provider/terraform/tests/resource_storage_default_object_acl_test.go b/provider/terraform/tests/resource_storage_default_object_acl_test.go new file mode 100644 index 000000000000..bf4de1b9ff40 --- /dev/null +++ b/provider/terraform/tests/resource_storage_default_object_acl_test.go @@ -0,0 +1,218 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccStorageDefaultObjectAcl_basic(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageDefaultObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic1, roleEntityBasic2), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + ), + }, + }, + }) +} + +func TestAccStorageDefaultObjectAcl_upgrade(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageDefaultObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic1, roleEntityBasic2), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic2, roleEntityBasic3_owner), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasicDelete(bucketName, roleEntityBasic1), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageDefaultObjectAclDelete(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAclDelete(bucketName, roleEntityBasic3_reader), + ), + }, + }, + }) +} + +func TestAccStorageDefaultObjectAcl_downgrade(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageDefaultObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic2, roleEntityBasic3_owner), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntityBasic2, roleEntityBasic3_reader), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic3_reader), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageDefaultObjectsAclBasicDelete(bucketName, roleEntityBasic1), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageDefaultObjectAcl(bucketName, roleEntityBasic1), + testAccCheckGoogleStorageDefaultObjectAclDelete(bucketName, roleEntityBasic2), + testAccCheckGoogleStorageDefaultObjectAclDelete(bucketName, roleEntityBasic3_reader), + ), + }, + }, + }) +} + +// Test that we allow the API to reorder our role entities without perma-diffing. +func TestAccStorageDefaultObjectAcl_unordered(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageDefaultObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageDefaultObjectAclUnordered(bucketName), + }, + }, + }) +} + +func testAccCheckGoogleStorageDefaultObjectAcl(bucket, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + res, err := config.clientStorage.DefaultObjectAccessControls.Get(bucket, + roleEntity.Entity).Do() + + if err != nil { + return fmt.Errorf("Error retrieving contents of storage default Acl for bucket %s: %s", bucket, err) + } + + if res.Role != roleEntity.Role { + return fmt.Errorf("Error, Role mismatch %s != %s", res.Role, roleEntity.Role) + } + + return nil + } +} + +func testAccStorageDefaultObjectAclDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + + if rs.Type != "google_storage_default_object_acl" { + continue + } + + bucket := rs.Primary.Attributes["bucket"] + + _, err := config.clientStorage.DefaultObjectAccessControls.List(bucket).Do() + if err == nil { + return fmt.Errorf("Default Storage Object Acl for bucket %s still exists", bucket) + } + } + return nil +} + +func testAccCheckGoogleStorageDefaultObjectAclDelete(bucket, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + _, err := config.clientStorage.DefaultObjectAccessControls.Get(bucket, roleEntity.Entity).Do() + + if err != nil { + return nil + } + + return fmt.Errorf("Error, Object Default Acl Entity still exists %s for bucket %s", + roleEntity.Entity, bucket) + } +} + +func testGoogleStorageDefaultObjectsAclBasicDelete(bucketName, roleEntity string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_default_object_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s"] +} +`, bucketName, roleEntity) +} + +func testGoogleStorageDefaultObjectsAclBasic(bucketName, roleEntity1, roleEntity2 string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_default_object_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, bucketName, roleEntity1, roleEntity2) +} + +func testGoogleStorageDefaultObjectAclUnordered(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_default_object_acl" "acl" { + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s", "%s", "%s", "%s"] +} +`, bucketName, roleEntityBasic1, roleEntityViewers, roleEntityOwners, roleEntityBasic2, roleEntityEditors) +} diff --git a/provider/terraform/tests/resource_storage_notification_test.go b/provider/terraform/tests/resource_storage_notification_test.go new file mode 100644 index 000000000000..6a6b8f114e8f --- /dev/null +++ b/provider/terraform/tests/resource_storage_notification_test.go @@ -0,0 +1,252 @@ +package google + +import ( + "fmt" + "os" + "reflect" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/storage/v1" +) + +var ( + payload = "JSON_API_V1" +) + +func TestAccStorageNotification_basic(t *testing.T) { + t.Parallel() + + skipIfEnvNotSet(t, "GOOGLE_PROJECT") + + var notification storage.Notification + bucketName := testBucketName() + topicName := fmt.Sprintf("tf-pstopic-test-%d", acctest.RandInt()) + topic := fmt.Sprintf("//pubsub.googleapis.com/projects/%s/topics/%s", os.Getenv("GOOGLE_PROJECT"), topicName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageNotificationDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageNotificationBasic(bucketName, topicName, topic), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageNotificationExists( + "google_storage_notification.notification", ¬ification), + resource.TestCheckResourceAttr( + "google_storage_notification.notification", "bucket", bucketName), + resource.TestCheckResourceAttr( + "google_storage_notification.notification", "topic", topic), + resource.TestCheckResourceAttr( + "google_storage_notification.notification", "payload_format", payload), + resource.TestCheckResourceAttr( + "google_storage_notification.notification_with_prefix", "object_name_prefix", "foobar"), + ), + }, + resource.TestStep{ + ResourceName: "google_storage_notification.notification", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_storage_notification.notification_with_prefix", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccStorageNotification_withEventsAndAttributes(t *testing.T) { + t.Parallel() + + skipIfEnvNotSet(t, "GOOGLE_PROJECT") + + var notification storage.Notification + bucketName := testBucketName() + topicName := fmt.Sprintf("tf-pstopic-test-%d", acctest.RandInt()) + topic := fmt.Sprintf("//pubsub.googleapis.com/projects/%s/topics/%s", os.Getenv("GOOGLE_PROJECT"), topicName) + eventType1 := "OBJECT_FINALIZE" + eventType2 := "OBJECT_ARCHIVE" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccStorageNotificationDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageNotificationOptionalEventsAttributes(bucketName, topicName, topic, eventType1, eventType2), + Check: resource.ComposeTestCheckFunc( + testAccCheckStorageNotificationExists( + "google_storage_notification.notification", ¬ification), + resource.TestCheckResourceAttr( + "google_storage_notification.notification", "bucket", bucketName), + resource.TestCheckResourceAttr( + "google_storage_notification.notification", "topic", topic), + resource.TestCheckResourceAttr( + "google_storage_notification.notification", "payload_format", payload), + testAccCheckStorageNotificationCheckEventType( + ¬ification, []string{eventType1, eventType2}), + testAccCheckStorageNotificationCheckAttributes( + ¬ification, "new-attribute", "new-attribute-value"), + ), + }, + resource.TestStep{ + ResourceName: "google_storage_notification.notification", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccStorageNotificationDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_storage_notification" { + continue + } + + bucket, notificationID := resourceStorageNotificationParseID(rs.Primary.ID) + + _, err := config.clientStorage.Notifications.Get(bucket, notificationID).Do() + if err == nil { + return fmt.Errorf("Notification configuration still exists") + } + } + + return nil +} + +func testAccCheckStorageNotificationExists(resource string, notification *storage.Notification) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resource] + if !ok { + return fmt.Errorf("Not found: %s", resource) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + bucket, notificationID := resourceStorageNotificationParseID(rs.Primary.ID) + + found, err := config.clientStorage.Notifications.Get(bucket, notificationID).Do() + if err != nil { + return err + } + + if found.Id != notificationID { + return fmt.Errorf("Storage notification configuration not found") + } + + *notification = *found + + return nil + } +} + +func testAccCheckStorageNotificationCheckEventType(notification *storage.Notification, eventTypes []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if !reflect.DeepEqual(notification.EventTypes, eventTypes) { + return fmt.Errorf("Target event types are incorrect. Expected %s, got %s", eventTypes, notification.EventTypes) + } + return nil + } +} + +func testAccCheckStorageNotificationCheckAttributes(notification *storage.Notification, key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + val, ok := notification.CustomAttributes[key] + if !ok { + return fmt.Errorf("Custom attribute with key %s not found", key) + } + + if val != value { + return fmt.Errorf("Custom attribute value did not match for key %s: expected %s but found %s", key, value, val) + } + return nil + } +} + +func testGoogleStorageNotificationBasic(bucketName, topicName, topic string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_pubsub_topic" "topic" { + name = "%s" +} + +// We have to provide GCS default storage account with the permission +// to publish to a Cloud Pub/Sub topic from this project +// Otherwise notification configuration won't work +data "google_storage_project_service_account" "gcs_account" {} + +resource "google_pubsub_topic_iam_binding" "binding" { + topic = "${google_pubsub_topic.topic.name}" + role = "roles/pubsub.publisher" + + members = ["serviceAccount:${data.google_storage_project_service_account.gcs_account.email_address}"] +} + +resource "google_storage_notification" "notification" { + bucket = "${google_storage_bucket.bucket.name}" + payload_format = "JSON_API_V1" + topic = "${google_pubsub_topic.topic.id}" + depends_on = ["google_pubsub_topic_iam_binding.binding"] +} + +resource "google_storage_notification" "notification_with_prefix" { + bucket = "${google_storage_bucket.bucket.name}" + payload_format = "JSON_API_V1" + topic = "${google_pubsub_topic.topic.id}" + object_name_prefix = "foobar" + depends_on = ["google_pubsub_topic_iam_binding.binding"] +} + +`, bucketName, topicName) +} + +func testGoogleStorageNotificationOptionalEventsAttributes(bucketName, topicName, topic, eventType1, eventType2 string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_pubsub_topic" "topic" { + name = "%s" +} + +// We have to provide GCS default storage account with the permission +// to publish to a Cloud Pub/Sub topic from this project +// Otherwise notification configuration won't work +data "google_storage_project_service_account" "gcs_account" {} + +resource "google_pubsub_topic_iam_binding" "binding" { + topic = "${google_pubsub_topic.topic.name}" + role = "roles/pubsub.publisher" + + members = ["serviceAccount:${data.google_storage_project_service_account.gcs_account.email_address}"] +} + +resource "google_storage_notification" "notification" { + bucket = "${google_storage_bucket.bucket.name}" + payload_format = "JSON_API_V1" + topic = "${google_pubsub_topic.topic.id}" + event_types = ["%s","%s"] + custom_attributes { + new-attribute = "new-attribute-value" + } + depends_on = ["google_pubsub_topic_iam_binding.binding"] +} + +`, bucketName, topicName, eventType1, eventType2) +} diff --git a/provider/terraform/tests/resource_storage_object_acl_test.go b/provider/terraform/tests/resource_storage_object_acl_test.go new file mode 100644 index 000000000000..3b10b6b0c349 --- /dev/null +++ b/provider/terraform/tests/resource_storage_object_acl_test.go @@ -0,0 +1,382 @@ +package google + +import ( + "fmt" + "io/ioutil" + "math/rand" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +var tfObjectAcl, errObjectAcl = ioutil.TempFile("", "tf-gce-test") + +func testAclObjectName() string { + return fmt.Sprintf("%s-%d", "tf-test-acl-object", + rand.New(rand.NewSource(time.Now().UnixNano())).Int()) +} + +func TestAccStorageObjectAcl_basic(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + objectName := testAclObjectName() + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic1(bucketName, objectName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic1), + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic2), + ), + }, + }, + }) +} + +func TestAccStorageObjectAcl_upgrade(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + objectName := testAclObjectName() + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic1(bucketName, objectName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic1), + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic2), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic2(bucketName, objectName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasicDelete(bucketName, objectName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAclDelete(bucketName, + objectName, roleEntityBasic1), + testAccCheckGoogleStorageObjectAclDelete(bucketName, + objectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAclDelete(bucketName, + objectName, roleEntityBasic3_reader), + ), + }, + }, + }) +} + +func TestAccStorageObjectAcl_downgrade(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + objectName := testAclObjectName() + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic2(bucketName, objectName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic3_owner), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasic3(bucketName, objectName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAcl(bucketName, + objectName, roleEntityBasic3_reader), + ), + }, + + resource.TestStep{ + Config: testGoogleStorageObjectsAclBasicDelete(bucketName, objectName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleStorageObjectAclDelete(bucketName, + objectName, roleEntityBasic1), + testAccCheckGoogleStorageObjectAclDelete(bucketName, + objectName, roleEntityBasic2), + testAccCheckGoogleStorageObjectAclDelete(bucketName, + objectName, roleEntityBasic3_reader), + ), + }, + }, + }) +} + +func TestAccStorageObjectAcl_predefined(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + objectName := testAclObjectName() + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectsAclPredefined(bucketName, objectName), + }, + }, + }) +} + +// Test that we allow the API to reorder our role entities without perma-diffing. +func TestAccStorageObjectAcl_unordered(t *testing.T) { + t.Parallel() + + bucketName := testBucketName() + objectName := testAclObjectName() + objectData := []byte("data data data") + ioutil.WriteFile(tfObjectAcl.Name(), objectData, 0644) + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if errObjectAcl != nil { + panic(errObjectAcl) + } + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccStorageObjectAclDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testGoogleStorageObjectAclUnordered(bucketName, objectName), + }, + }, + }) +} + +func testAccCheckGoogleStorageObjectAcl(bucket, object, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + res, err := config.clientStorage.ObjectAccessControls.Get(bucket, + object, roleEntity.Entity).Do() + + if err != nil { + return fmt.Errorf("Error retrieving contents of acl for bucket %s: %s", bucket, err) + } + + if res.Role != roleEntity.Role { + return fmt.Errorf("Error, Role mismatch %s != %s", res.Role, roleEntity.Role) + } + + return nil + } +} + +func testAccCheckGoogleStorageObjectAclDelete(bucket, object, roleEntityS string) resource.TestCheckFunc { + return func(s *terraform.State) error { + roleEntity, _ := getRoleEntityPair(roleEntityS) + config := testAccProvider.Meta().(*Config) + + _, err := config.clientStorage.ObjectAccessControls.Get(bucket, + object, roleEntity.Entity).Do() + + if err != nil { + return nil + } + + return fmt.Errorf("Error, Entity still exists %s", roleEntity.Entity) + } +} + +func testAccStorageObjectAclDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_storage_bucket_acl" { + continue + } + + bucket := rs.Primary.Attributes["bucket"] + object := rs.Primary.Attributes["object"] + + _, err := config.clientStorage.ObjectAccessControls.List(bucket, object).Do() + + if err == nil { + return fmt.Errorf("Acl for bucket %s still exists", bucket) + } + } + + return nil +} + +func testGoogleStorageObjectsAclBasicDelete(bucketName string, objectName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = [] +} +`, bucketName, objectName, tfObjectAcl.Name()) +} + +func testGoogleStorageObjectsAclBasic1(bucketName string, objectName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, bucketName, objectName, tfObjectAcl.Name(), + roleEntityBasic1, roleEntityBasic2) +} + +func testGoogleStorageObjectsAclBasic2(bucketName string, objectName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, bucketName, objectName, tfObjectAcl.Name(), + roleEntityBasic2, roleEntityBasic3_owner) +} + +func testGoogleStorageObjectsAclBasic3(bucketName string, objectName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s"] +} +`, bucketName, objectName, tfObjectAcl.Name(), + roleEntityBasic2, roleEntityBasic3_reader) +} + +func testGoogleStorageObjectsAclPredefined(bucketName string, objectName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + predefined_acl = "projectPrivate" +} +`, bucketName, objectName, tfObjectAcl.Name()) +} + +func testGoogleStorageObjectAclUnordered(bucketName, objectName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "bucket" { + name = "%s" +} + +resource "google_storage_bucket_object" "object" { + name = "%s" + bucket = "${google_storage_bucket.bucket.name}" + source = "%s" +} + +resource "google_storage_object_acl" "acl" { + object = "${google_storage_bucket_object.object.name}" + bucket = "${google_storage_bucket.bucket.name}" + role_entity = ["%s", "%s", "%s", "%s", "%s"] +} +`, bucketName, objectName, tfObjectAcl.Name(), roleEntityBasic1, roleEntityViewers, roleEntityOwners, roleEntityBasic2, roleEntityEditors) +} diff --git a/provider/terraform/tests/resource_usage_export_bucket_test.go b/provider/terraform/tests/resource_usage_export_bucket_test.go new file mode 100644 index 000000000000..4a5b86c155db --- /dev/null +++ b/provider/terraform/tests/resource_usage_export_bucket_test.go @@ -0,0 +1,59 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccComputeResourceUsageExportBucket(t *testing.T) { + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + + baseProject := "ub-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccResourceUsageExportBucket(baseProject, org, billingId), + }, + // Test import. + resource.TestStep{ + ResourceName: "google_project_usage_export_bucket.ueb", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccResourceUsageExportBucket(baseProject, org, billingId string) string { + return fmt.Sprintf(` +resource "google_project" "base" { + project_id = "%s" + name = "Export Bucket Base" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "service" { + project = "${google_project.base.project_id}" + service = "compute.googleapis.com" +} + +resource "google_storage_bucket" "bucket" { + name = "b-${google_project.base.project_id}" + project = "${google_project_service.service.project}" +} + +resource "google_project_usage_export_bucket" "ueb" { + project = "${google_project.base.project_id}" + bucket_name = "${google_storage_bucket.bucket.name}" + prefix = "foobar" +} +`, baseProject, org, billingId) +} From 1f6c48c3a1746a8ca0e13989c260801c10818883 Mon Sep 17 00:00:00 2001 From: emily Date: Thu, 18 Oct 2018 10:53:23 -0700 Subject: [PATCH 10/11] nested exclude for versioned properties (#575) Merged PR #575. --- api/type.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/api/type.rb b/api/type.rb index 8bebe90df7be..0c13a411a51d 100644 --- a/api/type.rb +++ b/api/type.rb @@ -310,6 +310,12 @@ def requires end [property_file] end + + def exclude_if_not_in_version(version) + super + @item_type.exclude_if_not_in_version(version) \ + if @item_type.is_a? NestedObject + end end # Represents an enum, and store is valid values @@ -499,6 +505,11 @@ def all_properties def properties @properties.reject(&:exclude) end + + def exclude_if_not_in_version(version) + super + @properties.each { |p| p.exclude_if_not_in_version(version) } + end end # Represents an array of name=value pairs, and stores its items' type From 17398759688fca0da1a9085c7b332b5d6ac34b2f Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Fri, 19 Oct 2018 16:24:11 +0000 Subject: [PATCH 11/11] Update tracked submodules -> HEAD on Fri Oct 19 16:24:11 UTC 2018 Tracked submodules are build/puppet/_bundle build/puppet/auth build/puppet/bigquery build/puppet/compute build/puppet/sql build/puppet/storage build/puppet/spanner build/puppet/container build/puppet/dns build/puppet/pubsub build/puppet/resourcemanager build/chef/_bundle build/chef/auth build/chef/compute build/chef/sql build/chef/storage build/chef/spanner build/chef/container build/chef/dns build/chef/iam build/terraform build/ansible build/inspec. --- build/terraform | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/terraform b/build/terraform index 8f7102b72d06..f6e614601289 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 8f7102b72d061951034a6e8bf404754f4d6b86d5 +Subproject commit f6e61460128913c6e00862a50b065fd3ab4228d4