From 783b8a44ae7b6466eb194d8390bda4a83e7cf060 Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Mon, 15 Oct 2018 11:24:36 -0700 Subject: [PATCH 01/15] Add ObjectAccessControl to Terraform. --- products/storage/api.yaml | 4 + products/storage/object_access_control.yaml | 5 +- 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 + .../set_resourceref_as_string.go.erb | 17 +++ ...object_access_control_public_object.tf.erb | 16 +++ 8 files changed, 222 insertions(+), 1 deletion(-) 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/set_resourceref_as_string.go.erb create mode 100644 templates/terraform/examples/storage_object_access_control_public_object.tf.erb 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..41d7e17d8052 100644 --- a/products/storage/object_access_control.yaml +++ b/products/storage/object_access_control.yaml @@ -27,6 +27,7 @@ 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 @@ -43,9 +44,9 @@ 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 - !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 +64,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 +79,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..d3e3f369b2ab --- /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/set_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..1906b6329b50 --- /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{ + 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{ + resource.TestStep{ + Config: testGoogleStorageObjectAccessControlBasic(bucketName, objectName, "READER", "allUsers"), + }, + { + ResourceName: "google_storage_object_access_control.default", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + 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/set_resourceref_as_string.go.erb b/templates/terraform/custom_expand/set_resourceref_as_string.go.erb new file mode 100644 index 000000000000..cf008609db00 --- /dev/null +++ b/templates/terraform/custom_expand/set_resourceref_as_string.go.erb @@ -0,0 +1,17 @@ +<%# The license inside this block applies to this file. + # 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. +-%> +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 ab55b43cd94be8d936a94e30c0b6d978434f370a Mon Sep 17 00:00:00 2001 From: Chris Stephens Date: Tue, 16 Oct 2018 10:47:07 -0700 Subject: [PATCH 02/15] Adding all the compute tests to magic modules (#568) Merged PR #568. --- .../data_source_compute_lb_ip_ranges_test.go | 34 + ...data_source_google_compute_address_test.go | 165 + ...rce_google_compute_backend_service_test.go | 85 + ...le_compute_default_service_account_test.go | 31 + ...rce_google_compute_forwarding_rule_test.go | 95 + ...urce_google_compute_global_address_test.go | 84 + .../data_source_google_compute_image_test.go | 100 + ...urce_google_compute_instance_group_test.go | 326 ++ ...ata_source_google_compute_instance_test.go | 133 + ...data_source_google_compute_network_test.go | 75 + ...ogle_compute_region_instance_group_test.go | 89 + ...data_source_google_compute_regions_test.go | 72 + ...a_source_google_compute_ssl_policy_test.go | 83 + ...a_source_google_compute_subnetwork_test.go | 96 + ..._source_google_compute_vpn_gateway_test.go | 77 + .../data_source_google_compute_zones_test.go | 72 + .../resource_compute_attached_disk_test.go | 222 ++ .../resource_compute_backend_bucket_test.go | 202 ++ ...ce_compute_backend_service_migrate_test.go | 95 + .../resource_compute_backend_service_test.go | 1026 ++++++ .../tests/resource_compute_disk_test.go | 772 +++++ .../resource_compute_firewall_migrate_test.go | 81 + .../resource_compute_forwarding_rule_test.go | 301 ++ ...rce_compute_global_forwarding_rule_test.go | 579 ++++ ...resource_compute_http_health_check_test.go | 189 ++ ...esource_compute_https_health_check_test.go | 189 ++ .../tests/resource_compute_image_test.go | 335 ++ ...rce_compute_instance_from_template_test.go | 114 + ...rce_compute_instance_group_manager_test.go | 1385 ++++++++ ...rce_compute_instance_group_migrate_test.go | 111 + .../resource_compute_instance_group_test.go | 588 ++++ .../resource_compute_instance_migrate_test.go | 877 +++++ ..._compute_instance_template_migrate_test.go | 137 + ...resource_compute_instance_template_test.go | 1392 ++++++++ .../tests/resource_compute_instance_test.go | 3015 +++++++++++++++++ .../resource_compute_network_peering_test.go | 125 + .../tests/resource_compute_network_test.go | 302 ++ ...urce_compute_project_metadata_item_test.go | 174 + .../resource_compute_project_metadata_test.go | 302 ++ ...rce_compute_region_backend_service_test.go | 405 +++ ...pute_region_instance_group_manager_test.go | 1319 +++++++ .../tests/resource_compute_route_test.go | 204 ++ .../resource_compute_router_interface_test.go | 289 ++ .../resource_compute_router_peer_test.go | 305 ++ .../resource_compute_security_policy_test.go | 202 ++ .../tests/resource_compute_shared_vpc_test.go | 160 + .../tests/resource_compute_snapshot_test.go | 260 ++ .../tests/resource_compute_ssl_policy_test.go | 402 +++ .../resource_compute_subnetwork_iam_test.go | 247 ++ .../tests/resource_compute_subnetwork_test.go | 421 +++ ...resource_compute_target_http_proxy_test.go | 250 ++ .../resource_compute_target_pool_test.go | 151 + .../resource_compute_target_tcp_proxy_test.go | 165 + .../tests/resource_compute_url_map_test.go | 411 +++ .../resource_compute_vpn_gateway_test.go | 105 + 55 files changed, 19426 insertions(+) create mode 100644 provider/terraform/tests/data_source_compute_lb_ip_ranges_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_address_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_backend_service_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_default_service_account_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_forwarding_rule_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_global_address_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_image_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_instance_group_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_instance_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_network_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_region_instance_group_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_regions_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_ssl_policy_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_subnetwork_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_vpn_gateway_test.go create mode 100644 provider/terraform/tests/data_source_google_compute_zones_test.go create mode 100644 provider/terraform/tests/resource_compute_attached_disk_test.go create mode 100644 provider/terraform/tests/resource_compute_backend_bucket_test.go create mode 100644 provider/terraform/tests/resource_compute_backend_service_migrate_test.go create mode 100644 provider/terraform/tests/resource_compute_backend_service_test.go create mode 100644 provider/terraform/tests/resource_compute_disk_test.go create mode 100644 provider/terraform/tests/resource_compute_firewall_migrate_test.go create mode 100644 provider/terraform/tests/resource_compute_forwarding_rule_test.go create mode 100644 provider/terraform/tests/resource_compute_global_forwarding_rule_test.go create mode 100644 provider/terraform/tests/resource_compute_http_health_check_test.go create mode 100644 provider/terraform/tests/resource_compute_https_health_check_test.go create mode 100644 provider/terraform/tests/resource_compute_image_test.go create mode 100644 provider/terraform/tests/resource_compute_instance_from_template_test.go create mode 100644 provider/terraform/tests/resource_compute_instance_group_manager_test.go create mode 100644 provider/terraform/tests/resource_compute_instance_group_migrate_test.go create mode 100644 provider/terraform/tests/resource_compute_instance_group_test.go create mode 100644 provider/terraform/tests/resource_compute_instance_migrate_test.go create mode 100644 provider/terraform/tests/resource_compute_instance_template_migrate_test.go create mode 100644 provider/terraform/tests/resource_compute_instance_template_test.go create mode 100644 provider/terraform/tests/resource_compute_instance_test.go create mode 100644 provider/terraform/tests/resource_compute_network_peering_test.go create mode 100644 provider/terraform/tests/resource_compute_network_test.go create mode 100644 provider/terraform/tests/resource_compute_project_metadata_item_test.go create mode 100644 provider/terraform/tests/resource_compute_project_metadata_test.go create mode 100644 provider/terraform/tests/resource_compute_region_backend_service_test.go create mode 100644 provider/terraform/tests/resource_compute_region_instance_group_manager_test.go create mode 100644 provider/terraform/tests/resource_compute_route_test.go create mode 100644 provider/terraform/tests/resource_compute_router_interface_test.go create mode 100644 provider/terraform/tests/resource_compute_router_peer_test.go create mode 100644 provider/terraform/tests/resource_compute_security_policy_test.go create mode 100644 provider/terraform/tests/resource_compute_shared_vpc_test.go create mode 100644 provider/terraform/tests/resource_compute_snapshot_test.go create mode 100644 provider/terraform/tests/resource_compute_ssl_policy_test.go create mode 100644 provider/terraform/tests/resource_compute_subnetwork_iam_test.go create mode 100644 provider/terraform/tests/resource_compute_subnetwork_test.go create mode 100644 provider/terraform/tests/resource_compute_target_http_proxy_test.go create mode 100644 provider/terraform/tests/resource_compute_target_pool_test.go create mode 100644 provider/terraform/tests/resource_compute_target_tcp_proxy_test.go create mode 100644 provider/terraform/tests/resource_compute_url_map_test.go create mode 100644 provider/terraform/tests/resource_compute_vpn_gateway_test.go diff --git a/provider/terraform/tests/data_source_compute_lb_ip_ranges_test.go b/provider/terraform/tests/data_source_compute_lb_ip_ranges_test.go new file mode 100644 index 000000000000..d70580bc9ba1 --- /dev/null +++ b/provider/terraform/tests/data_source_compute_lb_ip_ranges_test.go @@ -0,0 +1,34 @@ +package google + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceComputeLbIpRanges_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeLbIpRangesConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr("data.google_compute_lb_ip_ranges.some", + "network.#", regexp.MustCompile("^[1-9]+[0-9]*$")), + resource.TestMatchResourceAttr("data.google_compute_lb_ip_ranges.some", + "network.0", regexp.MustCompile("^[0-9./]+$")), + resource.TestMatchResourceAttr("data.google_compute_lb_ip_ranges.some", + "http_ssl_tcp_internal.#", regexp.MustCompile("^[1-9]+[0-9]*$")), + resource.TestMatchResourceAttr("data.google_compute_lb_ip_ranges.some", + "http_ssl_tcp_internal.0", regexp.MustCompile("^[0-9./]+$")), + ), + }, + }, + }) +} + +const testAccComputeLbIpRangesConfig = ` +data "google_compute_lb_ip_ranges" "some" {} +` diff --git a/provider/terraform/tests/data_source_google_compute_address_test.go b/provider/terraform/tests/data_source_google_compute_address_test.go new file mode 100644 index 000000000000..9f1e97989471 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_address_test.go @@ -0,0 +1,165 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestComputeAddressIdParsing(t *testing.T) { + cases := map[string]struct { + ImportId string + ExpectedError bool + ExpectedCanonicalId string + Config *Config + }{ + "id is a full self link": { + ImportId: "https://www.googleapis.com/compute/v1/projects/test-project/regions/us-central1/addresses/test-address", + ExpectedError: false, + ExpectedCanonicalId: "projects/test-project/regions/us-central1/addresses/test-address", + }, + "id is a partial self link": { + ImportId: "projects/test-project/regions/us-central1/addresses/test-address", + ExpectedError: false, + ExpectedCanonicalId: "projects/test-project/regions/us-central1/addresses/test-address", + }, + "id is project/region/address": { + ImportId: "test-project/us-central1/test-address", + ExpectedError: false, + ExpectedCanonicalId: "projects/test-project/regions/us-central1/addresses/test-address", + }, + "id is region/address": { + ImportId: "us-central1/test-address", + ExpectedError: false, + ExpectedCanonicalId: "projects/default-project/regions/us-central1/addresses/test-address", + Config: &Config{Project: "default-project"}, + }, + "id is address": { + ImportId: "test-address", + ExpectedError: false, + ExpectedCanonicalId: "projects/default-project/regions/us-east1/addresses/test-address", + Config: &Config{Project: "default-project", Region: "us-east1"}, + }, + "id has invalid format": { + ImportId: "i/n/v/a/l/i/d", + ExpectedError: true, + }, + } + + for tn, tc := range cases { + addressId, err := parseComputeAddressId(tc.ImportId, tc.Config) + + if tc.ExpectedError && err == nil { + t.Fatalf("bad: %s, expected an error", tn) + } + + if err != nil { + if tc.ExpectedError { + continue + } + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + if addressId.canonicalId() != tc.ExpectedCanonicalId { + t.Fatalf("bad: %s, expected canonical id to be `%s` but is `%s`", tn, tc.ExpectedCanonicalId, addressId.canonicalId()) + } + } +} + +func TestAccDataSourceComputeAddress(t *testing.T) { + t.Parallel() + + rsName := "foobar" + rsFullName := fmt.Sprintf("google_compute_address.%s", rsName) + dsName := "my_address" + dsFullName := fmt.Sprintf("data.google_compute_address.%s", dsName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDataSourceComputeAddressDestroy(rsFullName), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceComputeAddressConfig(rsName, dsName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceComputeAddressCheck(dsFullName, rsFullName), + ), + }, + }, + }) +} + +func testAccDataSourceComputeAddressCheck(data_source_name string, resource_name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ds, ok := s.RootModule().Resources[data_source_name] + if !ok { + return fmt.Errorf("root module has no resource called %s", data_source_name) + } + + rs, ok := s.RootModule().Resources[resource_name] + if !ok { + return fmt.Errorf("can't find %s in state", resource_name) + } + + ds_attr := ds.Primary.Attributes + rs_attr := rs.Primary.Attributes + + address_attrs_to_test := []string{ + "self_link", + "name", + "address", + } + + for _, attr_to_check := range address_attrs_to_test { + if ds_attr[attr_to_check] != rs_attr[attr_to_check] { + return fmt.Errorf( + "%s is %s; want %s", + attr_to_check, + ds_attr[attr_to_check], + rs_attr[attr_to_check], + ) + } + } + + if ds_attr["status"] != "RESERVED" { + return fmt.Errorf("status is %s; want RESERVED", ds_attr["status"]) + } + + return nil + } +} + +func testAccCheckDataSourceComputeAddressDestroy(resource_name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + rs, ok := s.RootModule().Resources[resource_name] + if !ok { + return fmt.Errorf("can't find %s in state", resource_name) + } + + addressId, err := parseComputeAddressId(rs.Primary.ID, nil) + + _, err = config.clientCompute.Addresses.Get( + config.Project, addressId.Region, addressId.Name).Do() + if err == nil { + return fmt.Errorf("Address still exists") + } + + return nil + } +} + +func testAccDataSourceComputeAddressConfig(rsName, dsName string) string { + return fmt.Sprintf(` +resource "google_compute_address" "%s" { + name = "address-test" +} + +data "google_compute_address" "%s" { + name = "${google_compute_address.%s.name}" +} +`, rsName, dsName, rsName) +} diff --git a/provider/terraform/tests/data_source_google_compute_backend_service_test.go b/provider/terraform/tests/data_source_google_compute_backend_service_test.go new file mode 100644 index 000000000000..c6b4212ef6e6 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_backend_service_test.go @@ -0,0 +1,85 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceComputeBackendService_basic(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceComputeBackendService_basic(serviceName, checkName), + Check: testAccDataSourceComputeBackendServiceCheck("data.google_compute_backend_service.baz", "google_compute_backend_service.foobar"), + }, + }, + }) +} + +func testAccDataSourceComputeBackendServiceCheck(dsName, rsName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rsName] + if !ok { + return fmt.Errorf("can't find resource called %s in state", rsName) + } + + ds, ok := s.RootModule().Resources[dsName] + if !ok { + return fmt.Errorf("can't find data source called %s in state", dsName) + } + + dsAttr := ds.Primary.Attributes + rsAttr := rs.Primary.Attributes + + attrsToTest := []string{ + "id", + "name", + "description", + "self_link", + "fingerprint", + "port_name", + "protocol", + } + + for _, attrToTest := range attrsToTest { + if dsAttr[attrToTest] != rsAttr[attrToTest] { + return fmt.Errorf("%s is %s; want %s", attrToTest, dsAttr[attrToTest], rsAttr[attrToTest]) + } + } + + return nil + } +} + +func testAccDataSourceComputeBackendService_basic(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + description = "foobar backend service" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +data "google_compute_backend_service" "baz" { + name = "${google_compute_backend_service.foobar.name}" +} +`, serviceName, checkName) +} diff --git a/provider/terraform/tests/data_source_google_compute_default_service_account_test.go b/provider/terraform/tests/data_source_google_compute_default_service_account_test.go new file mode 100644 index 000000000000..83e8929cb08f --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_default_service_account_test.go @@ -0,0 +1,31 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceGoogleComputeDefaultServiceAccount_basic(t *testing.T) { + t.Parallel() + + resourceName := "data.google_compute_default_service_account.default" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleComputeDefaultServiceAccount_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "email"), + ), + }, + }, + }) +} + +const testAccCheckGoogleComputeDefaultServiceAccount_basic = ` +data "google_compute_default_service_account" "default" { } +` diff --git a/provider/terraform/tests/data_source_google_compute_forwarding_rule_test.go b/provider/terraform/tests/data_source_google_compute_forwarding_rule_test.go new file mode 100644 index 000000000000..abb321a68c7d --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_forwarding_rule_test.go @@ -0,0 +1,95 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceGoogleForwardingRule(t *testing.T) { + t.Parallel() + + poolName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + ruleName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceGoogleForwardingRuleConfig(poolName, ruleName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceGoogleForwardingRuleCheck("data.google_compute_forwarding_rule.my_forwarding_rule", "google_compute_forwarding_rule.foobar-fr"), + ), + }, + }, + }) +} + +func testAccDataSourceGoogleForwardingRuleCheck(data_source_name string, resource_name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ds, ok := s.RootModule().Resources[data_source_name] + if !ok { + return fmt.Errorf("root module has no resource called %s", data_source_name) + } + + rs, ok := s.RootModule().Resources[resource_name] + if !ok { + return fmt.Errorf("can't find %s in state", resource_name) + } + + ds_attr := ds.Primary.Attributes + rs_attr := rs.Primary.Attributes + forwarding_rule_attrs_to_test := []string{ + "id", + "self_link", + "name", + "description", + "region", + "port_range", + "ports", + "target", + "ip_address", + "ip_protocol", + "load_balancing_scheme", + "backend_service", + "network", + "subnetwork", + } + + for _, attr_to_check := range forwarding_rule_attrs_to_test { + if ds_attr[attr_to_check] != rs_attr[attr_to_check] { + return fmt.Errorf( + "%s is %s; want %s", + attr_to_check, + ds_attr[attr_to_check], + rs_attr[attr_to_check], + ) + } + } + return nil + } +} + +func testAccDataSourceGoogleForwardingRuleConfig(poolName, ruleName string) string { + return fmt.Sprintf(` + resource "google_compute_target_pool" "foobar-tp" { + description = "Resource created for Terraform acceptance testing" + instances = ["us-central1-a/foo", "us-central1-b/bar"] + name = "%s" + } + resource "google_compute_forwarding_rule" "foobar-fr" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "UDP" + name = "%s" + port_range = "80-81" + target = "${google_compute_target_pool.foobar-tp.self_link}" + } + data "google_compute_forwarding_rule" "my_forwarding_rule" { + name = "${google_compute_forwarding_rule.foobar-fr.name}" + } +`, poolName, ruleName) +} diff --git a/provider/terraform/tests/data_source_google_compute_global_address_test.go b/provider/terraform/tests/data_source_google_compute_global_address_test.go new file mode 100644 index 000000000000..9cf0ef5233a2 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_global_address_test.go @@ -0,0 +1,84 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceComputeGlobalAddress(t *testing.T) { + t.Parallel() + + rsName := "foobar" + rsFullName := fmt.Sprintf("google_compute_global_address.%s", rsName) + dsName := "my_address" + dsFullName := fmt.Sprintf("data.google_compute_global_address.%s", dsName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeGlobalAddressDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceComputeGlobalAddressConfig(rsName, dsName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceComputeGlobalAddressCheck(dsFullName, rsFullName), + ), + }, + }, + }) +} + +func testAccDataSourceComputeGlobalAddressCheck(data_source_name string, resource_name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ds, ok := s.RootModule().Resources[data_source_name] + if !ok { + return fmt.Errorf("root module has no resource called %s", data_source_name) + } + + rs, ok := s.RootModule().Resources[resource_name] + if !ok { + return fmt.Errorf("can't find %s in state", resource_name) + } + + ds_attr := ds.Primary.Attributes + rs_attr := rs.Primary.Attributes + + address_attrs_to_test := []string{ + "self_link", + "name", + "address", + } + + for _, attr_to_check := range address_attrs_to_test { + if ds_attr[attr_to_check] != rs_attr[attr_to_check] { + return fmt.Errorf( + "%s is %s; want %s", + attr_to_check, + ds_attr[attr_to_check], + rs_attr[attr_to_check], + ) + } + } + + if ds_attr["status"] != "RESERVED" { + return fmt.Errorf("status is %s; want RESERVED", ds_attr["status"]) + } + + return nil + } +} + +func testAccDataSourceComputeGlobalAddressConfig(rsName, dsName string) string { + return fmt.Sprintf(` +resource "google_compute_global_address" "%s" { + name = "address-test" +} + +data "google_compute_global_address" "%s" { + name = "${google_compute_global_address.%s.name}" +} +`, rsName, dsName, rsName) +} diff --git a/provider/terraform/tests/data_source_google_compute_image_test.go b/provider/terraform/tests/data_source_google_compute_image_test.go new file mode 100644 index 000000000000..83a866fce655 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_image_test.go @@ -0,0 +1,100 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceComputeImage(t *testing.T) { + t.Parallel() + + family := acctest.RandomWithPrefix("tf-test") + name := acctest.RandomWithPrefix("tf-test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeImageDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourcePublicImageConfig, + Check: resource.ComposeTestCheckFunc( + testAccDataSourceCheckPublicImage(), + ), + }, + { + Config: testAccDataSourceCustomImageConfig(family, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_compute_image.from_name", + "name", name), + resource.TestCheckResourceAttr("data.google_compute_image.from_name", + "family", family), + resource.TestCheckResourceAttrSet("data.google_compute_image.from_name", + "self_link"), + ), + }, + }, + }) +} + +func testAccDataSourceCheckPublicImage() resource.TestCheckFunc { + return func(s *terraform.State) error { + data_source_name := "data.google_compute_image.debian" + ds, ok := s.RootModule().Resources[data_source_name] + if !ok { + return fmt.Errorf("root module has no resource called %s", data_source_name) + } + + ds_attr := ds.Primary.Attributes + attrs_to_test := map[string]string{ + "self_link": "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-9-stretch-v20171129", + "family": "debian-9", + } + + for attr, expect_value := range attrs_to_test { + if ds_attr[attr] != expect_value { + return fmt.Errorf( + "%s is %s; want %s", + attr, + ds_attr[attr], + expect_value, + ) + } + } + + return nil + } +} + +var testAccDataSourcePublicImageConfig = ` +data "google_compute_image" "debian" { + project = "debian-cloud" + name = "debian-9-stretch-v20171129" +} +` + +func testAccDataSourceCustomImageConfig(family, name string) string { + return fmt.Sprintf(` +resource "google_compute_image" "image" { + family = "%s" + name = "%s" + source_disk = "${google_compute_disk.disk.self_link}" +} +resource "google_compute_disk" "disk" { + name = "%s-disk" + zone = "us-central1-b" +} +data "google_compute_image" "from_name" { + project = "${google_compute_image.image.project}" + name = "${google_compute_image.image.name}" +} +data "google_compute_image" "from_family" { + project = "${google_compute_image.image.project}" + family = "${google_compute_image.image.family}" +} +`, family, name, name) +} diff --git a/provider/terraform/tests/data_source_google_compute_instance_group_test.go b/provider/terraform/tests/data_source_google_compute_instance_group_test.go new file mode 100644 index 000000000000..bc42d7805093 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_instance_group_test.go @@ -0,0 +1,326 @@ +package google + +import ( + "errors" + "fmt" + "reflect" + "sort" + "strconv" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceGoogleComputeInstanceGroup_basic(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckDataSourceGoogleComputeInstanceGroupConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataSourceGoogleComputeInstanceGroup("data.google_compute_instance_group.test"), + ), + }, + }, + }) +} + +func TestAccDataSourceGoogleComputeInstanceGroup_withNamedPort(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckDataSourceGoogleComputeInstanceGroupConfigWithNamedPort(), + Check: resource.ComposeTestCheckFunc( + testAccCheckDataSourceGoogleComputeInstanceGroup("data.google_compute_instance_group.test"), + ), + }, + }, + }) +} + +func TestAccDataSourceGoogleComputeInstanceGroup_fromIGM(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckDataSourceGoogleComputeInstanceGroup_fromIGM(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_compute_instance_group.test", "instances.#", "10"), + ), + }, + }, + }) +} + +func testAccCheckDataSourceGoogleComputeInstanceGroup(dataSourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + dsFullName := "data.google_compute_instance_group.test" + rsFullName := "google_compute_instance_group.test" + ds, ok := s.RootModule().Resources[dsFullName] + if !ok { + return fmt.Errorf("cant' find data source called %s in state", dsFullName) + } + + rs, ok := s.RootModule().Resources[rsFullName] + if !ok { + return fmt.Errorf("can't find resource called %s in state", rsFullName) + } + + dsAttrs := ds.Primary.Attributes + rsAttrs := rs.Primary.Attributes + + attrsToTest := []string{ + "id", + "name", + "zone", + "project", + "description", + "network", + "self_link", + "size", + } + + for _, attrToTest := range attrsToTest { + if dsAttrs[attrToTest] != rsAttrs[attrToTest] { + return fmt.Errorf("%s is %s; want %s", attrToTest, dsAttrs[attrToTest], rsAttrs[attrToTest]) + } + } + + dsNamedPortsCount, ok := dsAttrs["named_port.#"] + if !ok { + return errors.New("can't find 'named_port' attribute in data source") + } + + dsNoOfNamedPorts, err := strconv.Atoi(dsNamedPortsCount) + if err != nil { + return errors.New("failed to read number of named ports in data source") + } + + rsNamedPortsCount, ok := rsAttrs["named_port.#"] + if !ok { + return errors.New("can't find 'named_port' attribute in resource") + } + + rsNoOfNamedPorts, err := strconv.Atoi(rsNamedPortsCount) + if err != nil { + return errors.New("failed to read number of named ports in resource") + } + + if dsNoOfNamedPorts != rsNoOfNamedPorts { + return fmt.Errorf( + "expected %d number of named port, received %d, this is most likely a bug", + rsNoOfNamedPorts, + dsNoOfNamedPorts, + ) + } + + namedPortItemKeys := []string{"name", "value"} + for i := 0; i < dsNoOfNamedPorts; i++ { + for _, key := range namedPortItemKeys { + idx := fmt.Sprintf("named_port.%d.%s", i, key) + if dsAttrs[idx] != rsAttrs[idx] { + return fmt.Errorf("%s is %s; want %s", idx, dsAttrs[idx], rsAttrs[idx]) + } + } + } + + dsInstancesCount, ok := dsAttrs["instances.#"] + if !ok { + return errors.New("can't find 'instances' attribute in data source") + } + + dsNoOfInstances, err := strconv.Atoi(dsInstancesCount) + if err != nil { + return errors.New("failed to read number of named ports in data source") + } + + rsInstancesCount, ok := rsAttrs["instances.#"] + if !ok { + return errors.New("can't find 'instances' attribute in resource") + } + + rsNoOfInstances, err := strconv.Atoi(rsInstancesCount) + if err != nil { + return errors.New("failed to read number of instances in resource") + } + + if dsNoOfInstances != rsNoOfInstances { + return fmt.Errorf( + "expected %d number of instances, received %d, this is most likely a bug", + rsNoOfInstances, + dsNoOfInstances, + ) + } + + // We don't know the exact keys of the elements, so go through the whole list looking for matching ones + dsInstancesValues := []string{} + for k, v := range dsAttrs { + if strings.HasPrefix(k, "instances") && !strings.HasSuffix(k, "#") { + dsInstancesValues = append(dsInstancesValues, v) + } + } + + rsInstancesValues := []string{} + for k, v := range rsAttrs { + if strings.HasPrefix(k, "instances") && !strings.HasSuffix(k, "#") { + rsInstancesValues = append(rsInstancesValues, v) + } + } + + sort.Strings(dsInstancesValues) + sort.Strings(rsInstancesValues) + + if !reflect.DeepEqual(dsInstancesValues, rsInstancesValues) { + return fmt.Errorf("expected %v list of instances, received %v", rsInstancesValues, dsInstancesValues) + } + + return nil + } +} + +func testAccCheckDataSourceGoogleComputeInstanceGroupConfig() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "test" { + name = "tf-test-%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + + access_config { + // Ephemeral IP + } + } +} + +resource "google_compute_instance_group" "test" { + name = "tf-test-%s" + zone = "${google_compute_instance.test.zone}" + + instances = [ + "${google_compute_instance.test.self_link}", + ] +} + +data "google_compute_instance_group" "test" { + name = "${google_compute_instance_group.test.name}" + zone = "${google_compute_instance_group.test.zone}" +} +`, acctest.RandString(10), acctest.RandString(10)) +} + +func testAccCheckDataSourceGoogleComputeInstanceGroupConfigWithNamedPort() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "test" { + name = "tf-test-%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + + access_config { + // Ephemeral IP + } + } +} + +resource "google_compute_instance_group" "test" { + name = "tf-test-%s" + zone = "${google_compute_instance.test.zone}" + + named_port { + name = "http" + port = "8080" + } + + named_port { + name = "https" + port = "8443" + } + + instances = [ + "${google_compute_instance.test.self_link}", + ] +} + +data "google_compute_instance_group" "test" { + name = "${google_compute_instance_group.test.name}" + zone = "${google_compute_instance_group.test.zone}" +} +`, acctest.RandString(10), acctest.RandString(10)) +} + +func testAccCheckDataSourceGoogleComputeInstanceGroup_fromIGM() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } +} + +resource "google_compute_instance_group_manager" "igm" { + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm" + zone = "us-central1-a" + target_size = 10 + + wait_for_instances = true +} + +data "google_compute_instance_group" "test" { + self_link = "${google_compute_instance_group_manager.igm.instance_group}" +} +`, acctest.RandomWithPrefix("test-igm"), acctest.RandomWithPrefix("test-igm")) +} diff --git a/provider/terraform/tests/data_source_google_compute_instance_test.go b/provider/terraform/tests/data_source_google_compute_instance_test.go new file mode 100644 index 000000000000..af8fb2eebfdd --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_instance_test.go @@ -0,0 +1,133 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceComputeInstance_basic(t *testing.T) { + t.Parallel() + + instanceName := fmt.Sprintf("data-instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceComputeInstanceConfig(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceComputeInstanceCheck("data.google_compute_instance.bar", "google_compute_instance.foo"), + resource.TestCheckResourceAttr("data.google_compute_instance.bar", "network_interface.#", "1"), + resource.TestCheckResourceAttr("data.google_compute_instance.bar", "boot_disk.0.initialize_params.0.size", "10"), + resource.TestCheckResourceAttr("data.google_compute_instance.bar", "boot_disk.0.initialize_params.0.type", "pd-standard"), + resource.TestCheckResourceAttr("data.google_compute_instance.bar", "scratch_disk.0.interface", "SCSI"), + resource.TestCheckResourceAttr("data.google_compute_instance.bar", "network_interface.0.access_config.0.network_tier", "PREMIUM"), + ), + }, + }, + }) +} + +func testAccDataSourceComputeInstanceCheck(datasourceName string, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ds, ok := s.RootModule().Resources[datasourceName] + if !ok { + return fmt.Errorf("root module has no resource called %s", datasourceName) + } + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("can't find %s in state", resourceName) + } + + datasourceAttributes := ds.Primary.Attributes + resourceAttributes := rs.Primary.Attributes + + instanceAttrsToTest := []string{ + "name", + "machine_type", + "can_ip_forward", + "description", + "deletion_protection", + "labels", + "metadata", + "min_cpu_platform", + "project", + "tags", + "zone", + "cpu_platform", + "instance_id", + "label_fingerprint", + "metadata_fingerprint", + "self_link", + "tags_fingerprint", + } + + for _, attrToCheck := range instanceAttrsToTest { + if datasourceAttributes[attrToCheck] != resourceAttributes[attrToCheck] { + return fmt.Errorf( + "%s is %s; want %s", + attrToCheck, + datasourceAttributes[attrToCheck], + resourceAttributes[attrToCheck], + ) + } + } + + return nil + } +} + +func testAccDataSourceComputeInstanceConfig(instanceName string) string { + return fmt.Sprintf(` +resource "google_compute_instance" "foo" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "debian-8-jessie-v20160803" + } + } + + scratch_disk { + } + + network_interface { + network = "default" + + access_config { + // Ephemeral IP + } + } + + metadata { + foo = "bar" + baz = "qux" + } + + metadata { + startup-script = "echo Hello" + } + + labels { + my_key = "my_value" + my_other_key = "my_other_value" + } +} + +data "google_compute_instance" "bar" { + name = "${google_compute_instance.foo.name}" + zone = "us-central1-a" +} +`, instanceName) +} diff --git a/provider/terraform/tests/data_source_google_compute_network_test.go b/provider/terraform/tests/data_source_google_compute_network_test.go new file mode 100644 index 000000000000..cd80228fc369 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_network_test.go @@ -0,0 +1,75 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceGoogleNetwork(t *testing.T) { + t.Parallel() + + networkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceGoogleNetworkConfig(networkName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceGoogleNetworkCheck("data.google_compute_network.my_network", "google_compute_network.foobar"), + ), + }, + }, + }) +} + +func testAccDataSourceGoogleNetworkCheck(data_source_name string, resource_name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ds, ok := s.RootModule().Resources[data_source_name] + if !ok { + return fmt.Errorf("root module has no resource called %s", data_source_name) + } + + rs, ok := s.RootModule().Resources[resource_name] + if !ok { + return fmt.Errorf("can't find %s in state", resource_name) + } + + ds_attr := ds.Primary.Attributes + rs_attr := rs.Primary.Attributes + network_attrs_to_test := []string{ + "id", + "self_link", + "name", + "description", + } + + for _, attr_to_check := range network_attrs_to_test { + if ds_attr[attr_to_check] != rs_attr[attr_to_check] { + return fmt.Errorf( + "%s is %s; want %s", + attr_to_check, + ds_attr[attr_to_check], + rs_attr[attr_to_check], + ) + } + } + return nil + } +} + +func testAccDataSourceGoogleNetworkConfig(name string) string { + return fmt.Sprintf(` +resource "google_compute_network" "foobar" { + name = "%s" + description = "my-description" +} + +data "google_compute_network" "my_network" { + name = "${google_compute_network.foobar.name}" +}`, name) +} diff --git a/provider/terraform/tests/data_source_google_compute_region_instance_group_test.go b/provider/terraform/tests/data_source_google_compute_region_instance_group_test.go new file mode 100644 index 000000000000..e72824673347 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_region_instance_group_test.go @@ -0,0 +1,89 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceRegionInstanceGroup(t *testing.T) { + t.Parallel() + name := "acctest-" + acctest.RandString(6) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceRegionInstanceGroup_basic(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_compute_region_instance_group.data_source", "name", name), + resource.TestCheckResourceAttr("data.google_compute_region_instance_group.data_source", "project", getTestProjectFromEnv()), + resource.TestCheckResourceAttr("data.google_compute_region_instance_group.data_source", "instances.#", "10")), + }, + }, + }) +} + +func testAccDataSourceRegionInstanceGroup_basic(instanceManagerName string) string { + return fmt.Sprintf(` +resource "google_compute_health_check" "autohealing" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + healthy_threshold = 2 + unhealthy_threshold = 10 + + http_health_check { + request_path = "/" + port = "80" + } +} + +resource "google_compute_target_pool" "foo" { + name = "%s" +} + +data "google_compute_image" "debian" { + project = "debian-cloud" + name = "debian-9-stretch-v20171129" +} + +resource "google_compute_instance_template" "foo" { + machine_type = "n1-standard-1" + disk { + source_image = "${data.google_compute_image.debian.self_link}" + } + network_interface { + access_config { + } + network = "default" + } +} + +resource "google_compute_region_instance_group_manager" "foo" { + name = "%s" + base_instance_name = "foo" + instance_template = "${google_compute_instance_template.foo.self_link}" + region = "us-central1" + target_pools = ["${google_compute_target_pool.foo.self_link}"] + target_size = 1 + + named_port { + name = "web" + port = 80 + } + wait_for_instances = true + + auto_healing_policies { + health_check = "${google_compute_health_check.autohealing.self_link}" + initial_delay_sec = 10 + } +} + +data "google_compute_region_instance_group" "data_source" { + self_link = "${google_compute_region_instance_group_manager.foo.instance_group}" +} +`, acctest.RandomWithPrefix("test-rigm-"), acctest.RandomWithPrefix("test-rigm-"), instanceManagerName) +} diff --git a/provider/terraform/tests/data_source_google_compute_regions_test.go b/provider/terraform/tests/data_source_google_compute_regions_test.go new file mode 100644 index 000000000000..99a2c923f28f --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_regions_test.go @@ -0,0 +1,72 @@ +package google + +import ( + "errors" + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeRegions_basic(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleComputeRegionsConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleComputeRegionsMeta("data.google_compute_regions.available"), + ), + }, + }, + }) +} + +func testAccCheckGoogleComputeRegionsMeta(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Can't find regions data source: %s", n) + } + + if rs.Primary.ID == "" { + return errors.New("regions data source ID not set.") + } + + count, ok := rs.Primary.Attributes["names.#"] + if !ok { + return errors.New("can't find 'names' attribute") + } + + noOfNames, err := strconv.Atoi(count) + if err != nil { + return errors.New("failed to read number of regions") + } + if noOfNames < 2 { + return fmt.Errorf("expected at least 2 regions, received %d, this is most likely a bug", + noOfNames) + } + + for i := 0; i < noOfNames; i++ { + idx := "names." + strconv.Itoa(i) + v, ok := rs.Primary.Attributes[idx] + if !ok { + return fmt.Errorf("region list is corrupt (%q not found), this is definitely a bug", idx) + } + if len(v) < 1 { + return fmt.Errorf("Empty region name (%q), this is definitely a bug", idx) + } + } + + return nil + } +} + +var testAccCheckGoogleComputeRegionsConfig = ` +data "google_compute_regions" "available" {} +` diff --git a/provider/terraform/tests/data_source_google_compute_ssl_policy_test.go b/provider/terraform/tests/data_source_google_compute_ssl_policy_test.go new file mode 100644 index 000000000000..8b7462dba3ed --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_ssl_policy_test.go @@ -0,0 +1,83 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceGoogleSslPolicy(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceGoogleSslPolicy(), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceGoogleSslPolicyCheck("data.google_compute_ssl_policy.ssl_policy", "google_compute_ssl_policy.foobar"), + ), + }, + }, + }) +} + +func testAccDataSourceGoogleSslPolicyCheck(data_source_name string, resource_name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ds, ok := s.RootModule().Resources[data_source_name] + if !ok { + return fmt.Errorf("root module has no resource called %s", data_source_name) + } + + rs, ok := s.RootModule().Resources[resource_name] + if !ok { + return fmt.Errorf("can't find %s in state", resource_name) + } + + ds_attr := ds.Primary.Attributes + rs_attr := rs.Primary.Attributes + + ssl_policy_attrs_to_test := []string{ + "id", + "self_link", + "name", + "description", + "min_tls_version", + "profile", + "custom_features", + } + + for _, attr_to_check := range ssl_policy_attrs_to_test { + if ds_attr[attr_to_check] != rs_attr[attr_to_check] { + return fmt.Errorf( + "%s is %s; want %s", + attr_to_check, + ds_attr[attr_to_check], + rs_attr[attr_to_check], + ) + } + } + + return nil + } +} + +func testAccDataSourceGoogleSslPolicy() string { + return fmt.Sprintf(` + +resource "google_compute_ssl_policy" "foobar" { + name = "%s" + description = "my-description" + min_tls_version = "TLS_1_2" + profile = "MODERN" +} + +data "google_compute_ssl_policy" "ssl_policy" { + name = "${google_compute_ssl_policy.foobar.name}" +} +`, acctest.RandomWithPrefix("test-ssl-policy")) +} diff --git a/provider/terraform/tests/data_source_google_compute_subnetwork_test.go b/provider/terraform/tests/data_source_google_compute_subnetwork_test.go new file mode 100644 index 000000000000..7c44f89c4585 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_subnetwork_test.go @@ -0,0 +1,96 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceGoogleSubnetwork(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceGoogleSubnetwork(), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceGoogleSubnetworkCheck("data.google_compute_subnetwork.my_subnetwork", "google_compute_subnetwork.foobar"), + ), + }, + }, + }) +} + +func testAccDataSourceGoogleSubnetworkCheck(data_source_name string, resource_name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ds, ok := s.RootModule().Resources[data_source_name] + if !ok { + return fmt.Errorf("root module has no resource called %s", data_source_name) + } + + rs, ok := s.RootModule().Resources[resource_name] + if !ok { + return fmt.Errorf("can't find %s in state", resource_name) + } + + ds_attr := ds.Primary.Attributes + rs_attr := rs.Primary.Attributes + + subnetwork_attrs_to_test := []string{ + "id", + "self_link", + "name", + "description", + "ip_cidr_range", + "private_ip_google_access", + "secondary_ip_range", + } + + for _, attr_to_check := range subnetwork_attrs_to_test { + if ds_attr[attr_to_check] != rs_attr[attr_to_check] { + return fmt.Errorf( + "%s is %s; want %s", + attr_to_check, + ds_attr[attr_to_check], + rs_attr[attr_to_check], + ) + } + } + + if v1RsNetwork := ConvertSelfLinkToV1(rs_attr["network"]); ds_attr["network"] != v1RsNetwork { + return fmt.Errorf("network is %s; want %s", ds_attr["network"], v1RsNetwork) + } + + return nil + } +} + +func testAccDataSourceGoogleSubnetwork() string { + return fmt.Sprintf(` + +resource "google_compute_network" "foobar" { + name = "%s" + description = "my-description" +} +resource "google_compute_subnetwork" "foobar" { + name = "subnetwork-test" + description = "my-description" + ip_cidr_range = "10.0.0.0/24" + network = "${google_compute_network.foobar.self_link}" + private_ip_google_access = true + secondary_ip_range { + range_name = "tf-test-secondary-range" + ip_cidr_range = "192.168.1.0/24" + } +} + +data "google_compute_subnetwork" "my_subnetwork" { + name = "${google_compute_subnetwork.foobar.name}" +} +`, acctest.RandomWithPrefix("network-test")) +} diff --git a/provider/terraform/tests/data_source_google_compute_vpn_gateway_test.go b/provider/terraform/tests/data_source_google_compute_vpn_gateway_test.go new file mode 100644 index 000000000000..cec78918e0a4 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_vpn_gateway_test.go @@ -0,0 +1,77 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceGoogleVpnGateway(t *testing.T) { + t.Parallel() + + vpnGatewayName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceGoogleVpnGatewayConfig(vpnGatewayName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceGoogleVpnGatewayCheck("data.google_compute_vpn_gateway.my_vpn_gateway", "google_compute_vpn_gateway.foobar"), + ), + }, + }, + }) +} + +func testAccDataSourceGoogleVpnGatewayCheck(data_source_name string, resource_name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ds, ok := s.RootModule().Resources[data_source_name] + if !ok { + return fmt.Errorf("root module has no resource called %s", data_source_name) + } + + rs, ok := s.RootModule().Resources[resource_name] + if !ok { + return fmt.Errorf("can't find %s in state", resource_name) + } + + ds_attr := ds.Primary.Attributes + rs_attr := rs.Primary.Attributes + vpn_gateway_attrs_to_test := []string{ + "id", + "self_link", + "name", + "description", + "network", + } + + for _, attr_to_check := range vpn_gateway_attrs_to_test { + if ds_attr[attr_to_check] != rs_attr[attr_to_check] { + return fmt.Errorf( + "%s is %s; want %s", + attr_to_check, + ds_attr[attr_to_check], + rs_attr[attr_to_check], + ) + } + } + return nil + } +} + +func testAccDataSourceGoogleVpnGatewayConfig(name string) string { + return fmt.Sprintf(` +resource "google_compute_vpn_gateway" "foobar" { + name = "%s" + description = "my-description" + network = "default" +} + +data "google_compute_vpn_gateway" "my_vpn_gateway" { + name = "${google_compute_vpn_gateway.foobar.name}" +}`, name) +} diff --git a/provider/terraform/tests/data_source_google_compute_zones_test.go b/provider/terraform/tests/data_source_google_compute_zones_test.go new file mode 100644 index 000000000000..9e47bdb353d0 --- /dev/null +++ b/provider/terraform/tests/data_source_google_compute_zones_test.go @@ -0,0 +1,72 @@ +package google + +import ( + "errors" + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeZones_basic(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleComputeZonesConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleComputeZonesMeta("data.google_compute_zones.available"), + ), + }, + }, + }) +} + +func testAccCheckGoogleComputeZonesMeta(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Can't find zones data source: %s", n) + } + + if rs.Primary.ID == "" { + return errors.New("zones data source ID not set.") + } + + count, ok := rs.Primary.Attributes["names.#"] + if !ok { + return errors.New("can't find 'names' attribute") + } + + noOfNames, err := strconv.Atoi(count) + if err != nil { + return errors.New("failed to read number of zones") + } + if noOfNames < 2 { + return fmt.Errorf("expected at least 2 zones, received %d, this is most likely a bug", + noOfNames) + } + + for i := 0; i < noOfNames; i++ { + idx := "names." + strconv.Itoa(i) + v, ok := rs.Primary.Attributes[idx] + if !ok { + return fmt.Errorf("zone list is corrupt (%q not found), this is definitely a bug", idx) + } + if len(v) < 1 { + return fmt.Errorf("Empty zone name (%q), this is definitely a bug", idx) + } + } + + return nil + } +} + +var testAccCheckGoogleComputeZonesConfig = ` +data "google_compute_zones" "available" {} +` diff --git a/provider/terraform/tests/resource_compute_attached_disk_test.go b/provider/terraform/tests/resource_compute_attached_disk_test.go new file mode 100644 index 000000000000..9d9cef7087f9 --- /dev/null +++ b/provider/terraform/tests/resource_compute_attached_disk_test.go @@ -0,0 +1,222 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeAttachedDisk_basic(t *testing.T) { + t.Parallel() + + diskName := acctest.RandomWithPrefix("tf-test-disk") + instanceName := acctest.RandomWithPrefix("tf-test-inst") + importID := fmt.Sprintf("%s/us-central1-a/%s:%s", getTestProjectFromEnv(), instanceName, diskName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + // Check destroy isn't a good test here, see comment on testCheckAttachedDiskIsNowDetached + CheckDestroy: nil, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAttachedDiskResource(diskName, instanceName) + testAttachedDiskResourceAttachment(), + }, + resource.TestStep{ + ResourceName: "google_compute_attached_disk.test", + ImportStateId: importID, + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAttachedDiskResource(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testCheckAttachedDiskIsNowDetached(instanceName, diskName), + ), + }, + }, + }) +} + +func TestAccComputeAttachedDisk_full(t *testing.T) { + t.Parallel() + + diskName := acctest.RandomWithPrefix("tf-test") + instanceName := acctest.RandomWithPrefix("tf-test") + importID := fmt.Sprintf("%s/us-central1-a/%s:%s", getTestProjectFromEnv(), instanceName, diskName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + // Check destroy isn't a good test here, see comment on testCheckAttachedDiskIsNowDetached + CheckDestroy: nil, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAttachedDiskResource(diskName, instanceName) + testAttachedDiskResourceAttachmentFull(), + }, + resource.TestStep{ + ResourceName: "google_compute_attached_disk.test", + ImportStateId: importID, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + +} + +func TestAccComputeAttachedDisk_count(t *testing.T) { + t.Parallel() + + diskPrefix := acctest.RandomWithPrefix("tf-test") + instanceName := acctest.RandomWithPrefix("tf-test") + count := 2 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAttachedDiskResourceCount(diskPrefix, instanceName, count), + Check: resource.ComposeTestCheckFunc( + testCheckAttachedDiskContainsManyDisks(instanceName, count), + ), + }, + }, + }) + +} + +// testCheckAttachedDiskIsNowDetached queries a compute instance and iterates through the attached +// disks to confirm that a specific disk is no longer attached to the instance +// +// This is being used instead of a CheckDestory method because destory will delete both the compute +// instance and the disk, whereas destroying just the attached disk should only detach the disk but +// leave the instance and disk around. So just using a normal check destroy could end up with a +// situation where the detach fails but since the instance/disk get destroyed we wouldn't notice. +func testCheckAttachedDiskIsNowDetached(instanceName, diskName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + instance, err := config.clientCompute.Instances.Get(getTestProjectFromEnv(), "us-central1-a", instanceName).Do() + if err != nil { + return err + } + + ad := findDiskByName(instance.Disks, diskName) + if ad != nil { + return fmt.Errorf("compute disk is still attached to compute instance") + } + + return nil + } +} + +func testCheckAttachedDiskContainsManyDisks(instanceName string, count int) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + instance, err := config.clientCompute.Instances.Get(getTestProjectFromEnv(), "us-central1-a", instanceName).Do() + if err != nil { + return err + } + + // There will be 1 extra disk because of the compute instance's boot disk + if (count + 1) != len(instance.Disks) { + return fmt.Errorf("expected %d disks to be attached, found %d", count+1, len(instance.Disks)) + } + + return nil + } +} + +func testAttachedDiskResourceAttachment() string { + return fmt.Sprintf(` +resource "google_compute_attached_disk" "test" { + disk = "${google_compute_disk.test1.self_link}" + instance = "${google_compute_instance.test.self_link}" +}`) +} + +func testAttachedDiskResourceAttachmentFull() string { + return fmt.Sprintf(` +resource "google_compute_attached_disk" "test" { + disk = "${google_compute_disk.test1.self_link}" + instance = "${google_compute_instance.test.self_link}" + mode = "READ_ONLY" + device_name = "test-device-name" +}`) +} + +func testAttachedDiskResource(diskName, instanceName string) string { + return fmt.Sprintf(` +resource "google_compute_disk" "test1" { + name = "%s" + zone = "us-central1-a" + size = 10 +} + +resource "google_compute_instance" "test" { + name = "%s" + machine_type = "f1-micro" + zone = "us-central1-a" + + lifecycle { + ignore_changes = [ + "attached_disk", + ] + } + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + + network_interface { + network = "default" + } +}`, diskName, instanceName) +} + +func testAttachedDiskResourceCount(diskPrefix, instanceName string, count int) string { + return fmt.Sprintf(` +resource "google_compute_disk" "many" { + name = "%s-${count.index}" + zone = "us-central1-a" + size = 10 + count = %d +} + +resource "google_compute_instance" "test" { + name = "%s" + machine_type = "f1-micro" + zone = "us-central1-a" + + lifecycle { + ignore_changes = [ + "attached_disk", + ] + } + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + + network_interface { + network = "default" + } +} + +resource "google_compute_attached_disk" "test" { + count = "${google_compute_disk.many.count}" + disk = "${google_compute_disk.many.*.self_link[count.index]}" + instance = "${google_compute_instance.test.self_link}" +}`, diskPrefix, count, instanceName) +} diff --git a/provider/terraform/tests/resource_compute_backend_bucket_test.go b/provider/terraform/tests/resource_compute_backend_bucket_test.go new file mode 100644 index 000000000000..fbfc3a95cb49 --- /dev/null +++ b/provider/terraform/tests/resource_compute_backend_bucket_test.go @@ -0,0 +1,202 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeBackendBucket_basic(t *testing.T) { + t.Parallel() + + backendName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + storageName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendBucket + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendBucket_basic(backendName, storageName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendBucketExists( + "google_compute_backend_bucket.foobar", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_bucket.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + + if svc.BucketName != storageName { + t.Errorf("Expected BucketName to be %q, got %q", storageName, svc.BucketName) + } +} + +func TestAccComputeBackendBucket_basicModified(t *testing.T) { + t.Parallel() + + backendName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + storageName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + secondStorageName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendBucket + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendBucket_basic(backendName, storageName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendBucketExists( + "google_compute_backend_bucket.foobar", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendBucket_basicModified( + backendName, storageName, secondStorageName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendBucketExists( + "google_compute_backend_bucket.foobar", &svc), + ), + }, + }, + }) + + if svc.BucketName != secondStorageName { + t.Errorf("Expected BucketName to be %q, got %q", secondStorageName, svc.BucketName) + } +} + +func testAccCheckComputeBackendBucketDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_backend_bucket" { + continue + } + + _, err := config.clientCompute.BackendBuckets.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Backend bucket %s still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckComputeBackendBucketExists(n string, svc *compute.BackendBucket) 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.BackendBuckets.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Backend bucket %s not found", rs.Primary.ID) + } + + *svc = *found + + return nil + } +} + +func TestAccComputeBackendBucket_withCdnEnabled(t *testing.T) { + t.Parallel() + + backendName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + storageName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendBucket + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendBucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendBucket_withCdnEnabled( + backendName, storageName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendBucketExists( + "google_compute_backend_bucket.foobar", &svc), + ), + }, + }, + }) + + if svc.EnableCdn != true { + t.Errorf("Expected EnableCdn == true, got %t", svc.EnableCdn) + } +} + +func testAccComputeBackendBucket_basic(backendName, storageName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_bucket" "foobar" { + name = "%s" + bucket_name = "${google_storage_bucket.bucket_one.name}" +} + +resource "google_storage_bucket" "bucket_one" { + name = "%s" + location = "EU" +} +`, backendName, storageName) +} + +func testAccComputeBackendBucket_basicModified(backendName, bucketOne, bucketTwo string) string { + return fmt.Sprintf(` +resource "google_compute_backend_bucket" "foobar" { + name = "%s" + bucket_name = "${google_storage_bucket.bucket_two.name}" +} + +resource "google_storage_bucket" "bucket_one" { + name = "%s" + location = "EU" +} + +resource "google_storage_bucket" "bucket_two" { + name = "%s" + location = "EU" +} +`, backendName, bucketOne, bucketTwo) +} + +func testAccComputeBackendBucket_withCdnEnabled(backendName, storageName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_bucket" "foobar" { + name = "%s" + bucket_name = "${google_storage_bucket.bucket.name}" + enable_cdn = true +} + +resource "google_storage_bucket" "bucket" { + name = "%s" + location = "EU" +} +`, backendName, storageName) +} diff --git a/provider/terraform/tests/resource_compute_backend_service_migrate_test.go b/provider/terraform/tests/resource_compute_backend_service_migrate_test.go new file mode 100644 index 000000000000..1d162f5207c2 --- /dev/null +++ b/provider/terraform/tests/resource_compute_backend_service_migrate_test.go @@ -0,0 +1,95 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestComputeBackendServiceMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + Attributes map[string]string + ExpectedAttributes map[string]string + Meta interface{} + }{ + "v0 to v1": { + StateVersion: 0, + Attributes: map[string]string{ + "backend.#": "1", + "backend.242332812.group": "https://www.googleapis.com/compute/v1/projects/project_name/zones/zone_name/instances/instanceGroups/igName", + "backend.242332812.balancing_mode": "UTILIZATION", + "backend.242332812.max_utilization": "0.8", + }, + ExpectedAttributes: map[string]string{ + "backend.#": "1", + "backend.2573491210.group": "https://www.googleapis.com/compute/v1/projects/project_name/zones/zone_name/instances/instanceGroups/igName", + "backend.2573491210.balancing_mode": "UTILIZATION", + "backend.2573491210.max_utilization": "0.8", + }, + Meta: &Config{}, + }, + } + + for tn, tc := range cases { + is := &terraform.InstanceState{ + ID: "i-abc123", + Attributes: tc.Attributes, + } + is, err := resourceComputeBackendServiceMigrateState( + tc.StateVersion, is, tc.Meta) + + if err != nil { + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + for k, v := range tc.ExpectedAttributes { + 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) + } + } + + for k, v := range is.Attributes { + if tc.ExpectedAttributes[k] != v { + t.Fatalf( + "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", + tn, k, tc.ExpectedAttributes[k], k, v, is.Attributes) + } + } + } +} + +func TestComputeBackendServiceMigrateState_empty(t *testing.T) { + cases := map[string]struct { + StateVersion int + }{ + "v0": { + StateVersion: 0, + }, + } + + for tn, tc := range cases { + var is *terraform.InstanceState + var meta *Config + + // should handle nil + is, err := resourceComputeBackendServiceMigrateState(tc.StateVersion, is, meta) + + if err != nil { + t.Fatalf("bad %s, err: %#v", tn, err) + } + if is != nil { + t.Fatalf("bad %s, expected nil instancestate, got: %#v", tn, is) + } + + // should handle non-nil but empty + is = &terraform.InstanceState{} + is, err = resourceComputeBackendServiceMigrateState(tc.StateVersion, is, meta) + + if err != nil { + t.Fatalf("bad %s, err: %#v", tn, err) + } + } +} diff --git a/provider/terraform/tests/resource_compute_backend_service_test.go b/provider/terraform/tests/resource_compute_backend_service_test.go new file mode 100644 index 000000000000..19d8116a6ad5 --- /dev/null +++ b/provider/terraform/tests/resource_compute_backend_service_test.go @@ -0,0 +1,1026 @@ +package google + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeBackendService_basic(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + extraCheckName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_basic(serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendService_basicModified( + serviceName, checkName, extraCheckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeBackendService_withBackend(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withBackend( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendService_withBackend( + serviceName, igName, itName, checkName, 20), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.lipsum", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + + if svc.TimeoutSec != 20 { + t.Errorf("Expected TimeoutSec == 20, got %d", svc.TimeoutSec) + } + if svc.Protocol != "HTTP" { + t.Errorf("Expected Protocol to be HTTP, got %q", svc.Protocol) + } + if len(svc.Backends) != 1 { + t.Errorf("Expected 1 backend, got %d", len(svc.Backends)) + } +} + +func TestAccComputeBackendService_withBackendAndIAP(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withBackendAndIAP( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExistsWithIAP( + "google_compute_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.lipsum", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeBackendService_withBackend( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExistsWithoutIAP( + "google_compute_backend_service.lipsum", &svc), + ), + }, + }, + }) + + if svc.TimeoutSec != 10 { + t.Errorf("Expected TimeoutSec == 10, got %d", svc.TimeoutSec) + } + if svc.Protocol != "HTTP" { + t.Errorf("Expected Protocol to be HTTP, got %q", svc.Protocol) + } + if len(svc.Backends) != 1 { + t.Errorf("Expected 1 backend, got %d", len(svc.Backends)) + } + +} + +func TestAccComputeBackendService_updatePreservesOptionalParameters(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withSessionAffinity( + serviceName, checkName, "initial-description", "GENERATED_COOKIE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendService_withSessionAffinity( + serviceName, checkName, "updated-description", "GENERATED_COOKIE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.SessionAffinity != "GENERATED_COOKIE" { + t.Errorf("Expected SessionAffinity == \"GENERATED_COOKIE\", got %s", svc.SessionAffinity) + } +} + +func TestAccComputeBackendService_withConnectionDraining(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withConnectionDraining(serviceName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + + if svc.ConnectionDraining.DrainingTimeoutSec != 10 { + t.Errorf("Expected ConnectionDraining.DrainingTimeoutSec == 10, got %d", svc.ConnectionDraining.DrainingTimeoutSec) + } +} + +func TestAccComputeBackendService_withConnectionDrainingAndUpdate(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withConnectionDraining(serviceName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendService_basic(serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.ConnectionDraining.DrainingTimeoutSec != 300 { + t.Errorf("Expected ConnectionDraining.DrainingTimeoutSec == 300, got %d", svc.ConnectionDraining.DrainingTimeoutSec) + } +} + +func TestAccComputeBackendService_withHttpsHealthCheck(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withHttpsHealthCheck(serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeBackendService_withCdnPolicy(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withCdnPolicy(serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeBackendService_withSecurityPolicy(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + polName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withSecurityPolicy(serviceName, checkName, polName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + resource.TestMatchResourceAttr("google_compute_backend_service.foobar", "security_policy", regexp.MustCompile(polName)), + ), + }, + }, + }) +} + +func testAccCheckComputeBackendServiceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_backend_service" { + continue + } + + _, err := config.clientCompute.BackendServices.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Backend service %s still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckComputeBackendServiceExists(n string, svc *compute.BackendService) 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.BackendServices.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Backend service %s not found", rs.Primary.ID) + } + + *svc = *found + + return nil + } +} + +func testAccCheckComputeBackendServiceExistsWithIAP(n string, svc *compute.BackendService) 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.BackendServices.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Backend service %s not found", rs.Primary.ID) + } + + if found.Iap == nil || found.Iap.Enabled == false { + return fmt.Errorf("IAP not found or not enabled.") + } + + *svc = *found + + return nil + } +} + +func testAccCheckComputeBackendServiceExistsWithoutIAP(n string, svc *compute.BackendService) 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.BackendServices.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Backend service %s not found", rs.Primary.ID) + } + + if found.Iap != nil && found.Iap.Enabled == true { + return fmt.Errorf("IAP enabled when it should be disabled") + } + + *svc = *found + + return nil + } +} +func TestAccComputeBackendService_withCDNEnabled(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withCDNEnabled( + serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.EnableCDN != true { + t.Errorf("Expected EnableCDN == true, got %t", svc.EnableCDN) + } +} + +func TestAccComputeBackendService_withSessionAffinity(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withSessionAffinity( + serviceName, checkName, "description", "CLIENT_IP"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendService_withSessionAffinity( + serviceName, checkName, "description", "GENERATED_COOKIE"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.SessionAffinity != "GENERATED_COOKIE" { + t.Errorf("Expected SessionAffinity == \"GENERATED_COOKIE\", got %s", svc.SessionAffinity) + } +} + +func TestAccComputeBackendService_withMaxConnections(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withMaxConnections( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendService_withMaxConnections( + serviceName, igName, itName, checkName, 20), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.lipsum", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + + if svc.Backends[0].MaxConnections != 20 { + t.Errorf("Expected MaxConnections == 20, got %d", svc.Backends[0].MaxConnections) + } +} + +func TestAccComputeBackendService_withMaxConnectionsPerInstance(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withMaxConnectionsPerInstance( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendService_withMaxConnectionsPerInstance( + serviceName, igName, itName, checkName, 20), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExists( + "google_compute_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.lipsum", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + + if svc.Backends[0].MaxConnectionsPerInstance != 20 { + t.Errorf("Expected MaxConnectionsPerInstance == 20, got %d", svc.Backends[0].MaxConnectionsPerInstance) + } +} + +func TestAccComputeBackendService_withCustomHeaders(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withCustomHeaders(serviceName, checkName), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.foobar", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeBackendService_basic(serviceName, checkName), + }, + resource.TestStep{ + ResourceName: "google_compute_backend_service.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccComputeBackendService_basic(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, checkName) +} + +func testAccComputeBackendService_withCDNEnabled(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + enable_cdn = true +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, checkName) +} + +func testAccComputeBackendService_basicModified(serviceName, checkOne, checkTwo string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.one.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_http_health_check" "one" { + name = "%s" + request_path = "/one" + check_interval_sec = 30 + timeout_sec = 30 +} +`, serviceName, checkOne, checkTwo) +} + +func testAccComputeBackendService_withBackend( + serviceName, igName, itName, checkName string, timeout int64) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_backend_service" "lipsum" { + name = "%s" + description = "Hello World 1234" + port_name = "http" + protocol = "HTTP" + timeout_sec = %v + + backend { + group = "${google_compute_instance_group_manager.foobar.instance_group}" + } + + health_checks = ["${google_compute_http_health_check.default.self_link}"] +} + +resource "google_compute_instance_group_manager" "foobar" { + name = "%s" + instance_template = "${google_compute_instance_template.foobar.self_link}" + base_instance_name = "foobar" + zone = "us-central1-f" + target_size = 1 + auto_healing_policies { + health_check = "${google_compute_http_health_check.default.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + + network_interface { + network = "default" + } + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } +} + +resource "google_compute_http_health_check" "default" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, timeout, igName, itName, checkName) +} + +func testAccComputeBackendService_withBackendAndIAP( + serviceName, igName, itName, checkName string, timeout int64) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_backend_service" "lipsum" { + name = "%s" + description = "Hello World 1234" + port_name = "http" + protocol = "HTTP" + timeout_sec = %v + + backend { + group = "${google_compute_instance_group_manager.foobar.instance_group}" + } + + iap { + oauth2_client_id = "test" + oauth2_client_secret = "test" + } + + health_checks = ["${google_compute_http_health_check.default.self_link}"] +} + +resource "google_compute_instance_group_manager" "foobar" { + name = "%s" + instance_template = "${google_compute_instance_template.foobar.self_link}" + base_instance_name = "foobar" + zone = "us-central1-f" + target_size = 1 +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + + network_interface { + network = "default" + } + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } +} + +resource "google_compute_http_health_check" "default" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, timeout, igName, itName, checkName) +} + +func testAccComputeBackendService_withSessionAffinity(serviceName, checkName, description, affinityName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + description = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + session_affinity = "%s" +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, description, affinityName, checkName) +} + +func testAccComputeBackendService_withConnectionDraining(serviceName, checkName string, drainingTimeout int64) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + connection_draining_timeout_sec = %v +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, drainingTimeout, checkName) +} + +func testAccComputeBackendService_withHttpsHealthCheck(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_https_health_check.zero.self_link}"] + protocol = "HTTPS" +} + +resource "google_compute_https_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, checkName) +} + +func testAccComputeBackendService_withCdnPolicy(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + + cdn_policy { + cache_key_policy { + include_protocol = true + include_host = true + include_query_string = true + query_string_whitelist = ["foo", "bar"] + } + } +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, checkName) +} + +func testAccComputeBackendService_withSecurityPolicy(serviceName, checkName, polName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + security_policy = "${google_compute_security_policy.policy.self_link}" +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "basic security policy" +} +`, serviceName, checkName, polName) +} + +func testAccComputeBackendService_withMaxConnections( + serviceName, igName, itName, checkName string, maxConnections int64) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_backend_service" "lipsum" { + name = "%s" + description = "Hello World 1234" + port_name = "http" + protocol = "TCP" + + backend { + group = "${google_compute_instance_group_manager.foobar.instance_group}" + max_connections = %v + } + + health_checks = ["${google_compute_health_check.default.self_link}"] +} + +resource "google_compute_instance_group_manager" "foobar" { + name = "%s" + instance_template = "${google_compute_instance_template.foobar.self_link}" + base_instance_name = "foobar" + zone = "us-central1-f" + target_size = 1 + auto_healing_policies { + health_check = "${google_compute_health_check.default.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + + network_interface { + network = "default" + } + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } +} + +resource "google_compute_health_check" "default" { + name = "%s" + tcp_health_check { + port = "110" + } +} +`, serviceName, maxConnections, igName, itName, checkName) +} + +func testAccComputeBackendService_withMaxConnectionsPerInstance( + serviceName, igName, itName, checkName string, maxConnectionsPerInstance int64) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_backend_service" "lipsum" { + name = "%s" + description = "Hello World 1234" + port_name = "http" + protocol = "TCP" + + backend { + group = "${google_compute_instance_group_manager.foobar.instance_group}" + max_connections_per_instance = %v + } + + health_checks = ["${google_compute_health_check.default.self_link}"] +} + +resource "google_compute_instance_group_manager" "foobar" { + name = "%s" + instance_template = "${google_compute_instance_template.foobar.self_link}" + base_instance_name = "foobar" + zone = "us-central1-f" + target_size = 1 + auto_healing_policies { + health_check = "${google_compute_health_check.default.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + + network_interface { + network = "default" + } + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } +} + +resource "google_compute_health_check" "default" { + name = "%s" + tcp_health_check { + port = "110" + } +} +`, serviceName, maxConnectionsPerInstance, igName, itName, checkName) +} + +func testAccComputeBackendService_withCustomHeaders(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + + custom_request_headers = ["Client-Region: {client_region}", "Client-Rtt: {client_rtt_msec}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, checkName) +} diff --git a/provider/terraform/tests/resource_compute_disk_test.go b/provider/terraform/tests/resource_compute_disk_test.go new file mode 100644 index 000000000000..74dd3abb487f --- /dev/null +++ b/provider/terraform/tests/resource_compute_disk_test.go @@ -0,0 +1,772 @@ +package google + +import ( + "fmt" + "os" + "regexp" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func TestDiskImageDiffSuppress(t *testing.T) { + cases := map[string]struct { + Old, New string + ExpectDiffSuppress bool + }{ + // Full & partial links + "matching self_link with different api version": { + Old: "https://www.googleapis.com/compute/beta/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + ExpectDiffSuppress: true, + }, + "matching image partial self_link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "projects/debian-cloud/global/images/debian-8-jessie-v20171213", + ExpectDiffSuppress: true, + }, + "matching image partial no project self_link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "global/images/debian-8-jessie-v20171213", + ExpectDiffSuppress: true, + }, + "different image self_link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-jessie-v20171213", + ExpectDiffSuppress: false, + }, + "different image partial self_link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "projects/debian-cloud/global/images/debian-7-jessie-v20171213", + ExpectDiffSuppress: false, + }, + "different image partial no project self_link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "global/images/debian-7-jessie-v20171213", + ExpectDiffSuppress: false, + }, + // Image name + "matching image name": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "debian-8-jessie-v20171213", + ExpectDiffSuppress: true, + }, + "different image name": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "debian-7-jessie-v20171213", + ExpectDiffSuppress: false, + }, + // Image short hand + "matching image short hand": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "debian-cloud/debian-8-jessie-v20171213", + ExpectDiffSuppress: true, + }, + "matching image short hand but different project": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "different-cloud/debian-8-jessie-v20171213", + ExpectDiffSuppress: false, + }, + "different image short hand": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "debian-cloud/debian-7-jessie-v20171213", + ExpectDiffSuppress: false, + }, + // Image Family + "matching image family": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "family/debian-8", + ExpectDiffSuppress: true, + }, + "matching image family self link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/family/debian-8", + ExpectDiffSuppress: true, + }, + "matching unconventional image family self link": { + Old: "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1404-trusty-v20180122", + New: "https://www.googleapis.com/compute/v1/projects/projects/ubuntu-os-cloud/global/images/family/ubuntu-1404-lts", + ExpectDiffSuppress: true, + }, + "matching image family partial self link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "projects/debian-cloud/global/images/family/debian-8", + ExpectDiffSuppress: true, + }, + "matching unconventional image family partial self link": { + Old: "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1404-trusty-v20180122", + New: "projects/ubuntu-os-cloud/global/images/family/ubuntu-1404-lts", + ExpectDiffSuppress: true, + }, + "matching image family partial no project self link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "global/images/family/debian-8", + ExpectDiffSuppress: true, + }, + "matching image family short hand": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "debian-cloud/debian-8", + ExpectDiffSuppress: true, + }, + "matching image family short hand with project short name": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "debian/debian-8", + ExpectDiffSuppress: true, + }, + "matching unconventional image family short hand": { + Old: "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1404-trusty-v20180122", + New: "ubuntu-os-cloud/ubuntu-1404-lts", + ExpectDiffSuppress: true, + }, + "matching unconventional image family - minimal": { + Old: "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-minimal-1804-bionic-v20180705", + New: "ubuntu-minimal-1804-lts", + ExpectDiffSuppress: true, + }, + "different image family": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "family/debian-7", + ExpectDiffSuppress: false, + }, + "different image family self link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/family/debian-7", + ExpectDiffSuppress: false, + }, + "different image family partial self link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "projects/debian-cloud/global/images/family/debian-7", + ExpectDiffSuppress: false, + }, + "different image family partial no project self link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "global/images/family/debian-7", + ExpectDiffSuppress: false, + }, + "matching image family but different project in self link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "https://www.googleapis.com/compute/v1/projects/other-cloud/global/images/family/debian-8", + ExpectDiffSuppress: false, + }, + "different image family but different project in partial self link": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "projects/other-cloud/global/images/family/debian-8", + ExpectDiffSuppress: false, + }, + "different image family short hand": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "debian-cloud/debian-7", + ExpectDiffSuppress: false, + }, + "matching image family shorthand but different project": { + Old: "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20171213", + New: "different-cloud/debian-8", + ExpectDiffSuppress: false, + }, + } + + for tn, tc := range cases { + if diskImageDiffSuppress("image", tc.Old, tc.New, nil) != tc.ExpectDiffSuppress { + t.Errorf("bad: %s, %q => %q expect DiffSuppress to return %t", tn, tc.Old, tc.New, tc.ExpectDiffSuppress) + } + } +} + +// Test that all the naming pattern for public images are supported. +func TestAccComputeDisk_imageDiffSuppressPublicVendorsFamilyNames(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + + config := getInitializedConfig(t) + + for _, publicImageProject := range imageMap { + token := "" + for paginate := true; paginate; { + resp, err := config.clientCompute.Images.List(publicImageProject).Filter("deprecated.replacement ne .*images.*").PageToken(token).Do() + if err != nil { + t.Fatalf("Can't list public images for project %q", publicImageProject) + } + + for _, image := range resp.Items { + if !diskImageDiffSuppress("image", image.SelfLink, "family/"+image.Family, nil) { + t.Errorf("should suppress diff for image %q and family %q", image.SelfLink, image.Family) + } + } + token := resp.NextPageToken + paginate = token != "" + } + } +} + +func TestAccComputeDisk_basic(t *testing.T) { + t.Parallel() + + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var disk compute.Disk + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeDisk_basic(diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foobar", &disk), + testAccCheckComputeDiskHasLabel(&disk, "my-label", "my-label-value"), + testAccCheckComputeDiskHasLabelFingerprint(&disk, "google_compute_disk.foobar"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_disk.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeDisk_timeout(t *testing.T) { + t.Parallel() + + diskName := acctest.RandomWithPrefix("tf-test-disk") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeDisk_timeout(diskName), + ExpectError: regexp.MustCompile("timeout"), + }, + }, + }) +} + +func TestAccComputeDisk_update(t *testing.T) { + t.Parallel() + + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var disk compute.Disk + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccComputeDisk_basic(diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foobar", &disk), + resource.TestCheckResourceAttr("google_compute_disk.foobar", "size", "50"), + testAccCheckComputeDiskHasLabel(&disk, "my-label", "my-label-value"), + testAccCheckComputeDiskHasLabelFingerprint(&disk, "google_compute_disk.foobar"), + ), + }, + { + Config: testAccComputeDisk_updated(diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foobar", &disk), + resource.TestCheckResourceAttr("google_compute_disk.foobar", "size", "100"), + testAccCheckComputeDiskHasLabel(&disk, "my-label", "my-updated-label-value"), + testAccCheckComputeDiskHasLabel(&disk, "a-new-label", "a-new-label-value"), + testAccCheckComputeDiskHasLabelFingerprint(&disk, "google_compute_disk.foobar"), + ), + }, + }, + }) +} + +func TestAccComputeDisk_fromSnapshot(t *testing.T) { + t.Parallel() + + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + firstDiskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + snapshotName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + projectName := getTestProjectFromEnv() + + var disk compute.Disk + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeDisk_fromSnapshot(projectName, firstDiskName, snapshotName, diskName, "self_link"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.seconddisk", &disk), + ), + }, + resource.TestStep{ + Config: testAccComputeDisk_fromSnapshot(projectName, firstDiskName, snapshotName, diskName, "name"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.seconddisk", &disk), + ), + }, + }, + }) +} + +func TestAccComputeDisk_encryption(t *testing.T) { + t.Parallel() + + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var disk compute.Disk + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeDisk_encryption(diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foobar", &disk), + testAccCheckEncryptionKey( + "google_compute_disk.foobar", &disk), + ), + }, + }, + }) +} + +func TestAccComputeDisk_deleteDetach(t *testing.T) { + t.Parallel() + + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var disk compute.Disk + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeDisk_deleteDetach(instanceName, diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foo", &disk), + ), + }, + // this needs to be a second step so we refresh and see the instance + // listed as attached to the disk; the instance is created after the + // disk. and the disk's properties aren't refreshed unless there's + // another step + resource.TestStep{ + Config: testAccComputeDisk_deleteDetach(instanceName, diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foo", &disk), + testAccCheckComputeDiskInstances( + "google_compute_disk.foo", &disk), + ), + }, + }, + }) +} + +func TestAccComputeDisk_deleteDetachIGM(t *testing.T) { + t.Parallel() + + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + diskName2 := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + mgrName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var disk compute.Disk + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeDiskDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeDisk_deleteDetachIGM(diskName, mgrName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foo", &disk), + ), + }, + // this needs to be a second step so we refresh and see the instance + // listed as attached to the disk; the instance is created after the + // disk. and the disk's properties aren't refreshed unless there's + // another step + resource.TestStep{ + Config: testAccComputeDisk_deleteDetachIGM(diskName, mgrName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foo", &disk), + testAccCheckComputeDiskInstances( + "google_compute_disk.foo", &disk), + ), + }, + // Change the disk name to recreate the instances + resource.TestStep{ + Config: testAccComputeDisk_deleteDetachIGM(diskName2, mgrName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foo", &disk), + ), + }, + // Add the extra step like before + resource.TestStep{ + Config: testAccComputeDisk_deleteDetachIGM(diskName2, mgrName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeDiskExists( + "google_compute_disk.foo", &disk), + testAccCheckComputeDiskInstances( + "google_compute_disk.foo", &disk), + ), + }, + }, + }) +} + +func TestAccComputeDisk_computeDiskUserRegex(t *testing.T) { + + shouldPass := []string{ + + "https://www.googleapis.com/compute/v1/projects/project-id/zones/us-central1/instances/123", + "https://www.googleapis.com/compute/v1/projects/123123/zones/us-central1/instances/123", + "https://www.googleapis.com/compute/v1/projects/hashicorptest.net:project-123/zones/us-central1/instances/123", + "https://www.googleapis.com/compute/v1/projects/123/zones/456/instances/789", + } + + shouldFail := []string{ + "https://www.googleapis.com/compute/v1/projects/project#/zones/us-central1/instances/123", + "https://www.googleapis.com/compute/v1/projects/project/zones/us-central#/instances/123", + "https://www.googleapis.com/compute/v1/projects/project/zones/us-central1/instances/?", + "https://www.googleapis.com/compute/v1/projects/foo.com:bar:baz/zones/us-central1/instances/?", + "https://www.googleapis.com/compute/v1/projects/foo.com:/zones/us-central1/instances/?", + } + + for _, element := range shouldPass { + if !computeDiskUserRegex.MatchString(element) { + t.Error("computeDiskUserRegex should match on '" + element + "' but doesn't") + } + } + + for _, element := range shouldFail { + if computeDiskUserRegex.MatchString(element) { + t.Error("computeDiskUserRegex shouldn't match on '" + element + "' but does") + } + } + +} + +func testAccCheckComputeDiskDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_disk" { + continue + } + + _, err := config.clientCompute.Disks.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Disk still exists") + } + } + + return nil +} + +func testAccCheckComputeDiskExists(n string, disk *compute.Disk) resource.TestCheckFunc { + return func(s *terraform.State) error { + p := getTestProjectFromEnv() + 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.Disks.Get( + p, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Disk not found") + } + + *disk = *found + + return nil + } +} + +func testAccCheckComputeDiskHasLabel(disk *compute.Disk, key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + val, ok := disk.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 testAccCheckComputeDiskHasLabelFingerprint(disk *compute.Disk, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + state := s.RootModule().Resources[resourceName] + if state == nil { + return fmt.Errorf("Unable to find resource named %s", resourceName) + } + + labelFingerprint := state.Primary.Attributes["label_fingerprint"] + if labelFingerprint != disk.LabelFingerprint { + return fmt.Errorf("Label fingerprints do not match: api returned %s but state has %s", + disk.LabelFingerprint, labelFingerprint) + } + + return nil + } +} + +func testAccCheckEncryptionKey(n string, disk *compute.Disk) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + attr := rs.Primary.Attributes["disk_encryption_key.0.sha256"] + if disk.DiskEncryptionKey == nil { + return fmt.Errorf("Disk %s has mismatched encryption key.\nTF State: %+v\nGCP State: ", n, attr) + } else if attr != disk.DiskEncryptionKey.Sha256 { + return fmt.Errorf("Disk %s has mismatched encryption key.\nTF State: %+v.\nGCP State: %+v", + n, attr, disk.DiskEncryptionKey.Sha256) + } + return nil + } +} + +func testAccCheckComputeDiskInstances(n string, disk *compute.Disk) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + attr := rs.Primary.Attributes["users.#"] + if strconv.Itoa(len(disk.Users)) != attr { + return fmt.Errorf("Disk %s has mismatched users.\nTF State: %+v\nGCP State: %+v", n, rs.Primary.Attributes["users"], disk.Users) + } + + for pos, user := range disk.Users { + if rs.Primary.Attributes["users."+strconv.Itoa(pos)] != user { + return fmt.Errorf("Disk %s has mismatched users.\nTF State: %+v.\nGCP State: %+v", + n, rs.Primary.Attributes["users"], disk.Users) + } + } + return nil + } +} + +func testAccComputeDisk_basic(diskName string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 50 + type = "pd-ssd" + zone = "us-central1-a" + labels { + my-label = "my-label-value" + } +}`, diskName) +} + +func testAccComputeDisk_timeout(diskName string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + type = "pd-ssd" + zone = "us-central1-a" + + timeouts { + create = "1s" + } +}`, diskName) +} + +func testAccComputeDisk_updated(diskName string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 100 + type = "pd-ssd" + zone = "us-central1-a" + labels { + my-label = "my-updated-label-value" + a-new-label = "a-new-label-value" + } +}`, diskName) +} + +func testAccComputeDisk_fromSnapshot(projectName, firstDiskName, snapshotName, diskName, ref_selector string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "d1-%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 50 + type = "pd-ssd" + zone = "us-central1-a" + project = "%s" +} + +resource "google_compute_snapshot" "snapdisk" { + name = "%s" + source_disk = "${google_compute_disk.foobar.name}" + zone = "us-central1-a" + project = "%s" +} + +resource "google_compute_disk" "seconddisk" { + name = "d2-%s" + snapshot = "${google_compute_snapshot.snapdisk.%s}" + type = "pd-ssd" + zone = "us-central1-a" + project = "%s" +}`, firstDiskName, projectName, snapshotName, projectName, diskName, ref_selector, projectName) +} + +func testAccComputeDisk_encryption(diskName string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 50 + type = "pd-ssd" + zone = "us-central1-a" + disk_encryption_key { + raw_key = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" + } +}`, diskName) +} + +func testAccComputeDisk_deleteDetach(instanceName, diskName string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foo" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 50 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance" "bar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + attached_disk { + source = "${google_compute_disk.foo.self_link}" + } + + network_interface { + network = "default" + } +}`, diskName, instanceName) +} + +func testAccComputeDisk_deleteDetachIGM(diskName, mgrName string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foo" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 50 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance_template" "template" { + machine_type = "g1-small" + + disk { + boot = true + source = "${google_compute_disk.foo.name}" + auto_delete = false + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group_manager" "manager" { + name = "%s" + base_instance_name = "disk-igm" + instance_template = "${google_compute_instance_template.template.self_link}" + zone = "us-central1-a" + target_size = 1 +}`, diskName, mgrName) +} diff --git a/provider/terraform/tests/resource_compute_firewall_migrate_test.go b/provider/terraform/tests/resource_compute_firewall_migrate_test.go new file mode 100644 index 000000000000..e28d607f358c --- /dev/null +++ b/provider/terraform/tests/resource_compute_firewall_migrate_test.go @@ -0,0 +1,81 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestComputeFirewallMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + Attributes map[string]string + Expected map[string]string + Meta interface{} + }{ + "change scope from list to set": { + StateVersion: 0, + Attributes: map[string]string{ + "allow.#": "1", + "allow.0.protocol": "udp", + "allow.0.ports.#": "4", + "allow.0.ports.1693978638": "8080", + "allow.0.ports.172152165": "8081", + "allow.0.ports.299962681": "7072", + "allow.0.ports.3435931483": "4044", + }, + Expected: map[string]string{ + "allow.#": "1", + "allow.0.protocol": "udp", + "allow.0.ports.#": "4", + "allow.0.ports.0": "8080", + "allow.0.ports.1": "8081", + "allow.0.ports.2": "7072", + "allow.0.ports.3": "4044", + }, + }, + } + for tn, tc := range cases { + is := &terraform.InstanceState{ + ID: "i-abc123", + Attributes: tc.Attributes, + } + is, err := resourceComputeFirewallMigrateState( + 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 TestComputeFirewallMigrateState_empty(t *testing.T) { + var is *terraform.InstanceState + var meta interface{} + + // should handle nil + is, err := resourceComputeFirewallMigrateState(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 = resourceComputeFirewallMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } +} diff --git a/provider/terraform/tests/resource_compute_forwarding_rule_test.go b/provider/terraform/tests/resource_compute_forwarding_rule_test.go new file mode 100644 index 000000000000..bc936e080b7e --- /dev/null +++ b/provider/terraform/tests/resource_compute_forwarding_rule_test.go @@ -0,0 +1,301 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeForwardingRule_update(t *testing.T) { + t.Parallel() + + poolName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + ruleName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeForwardingRule_basic(poolName, ruleName), + }, + resource.TestStep{ + ResourceName: "google_compute_forwarding_rule.foobar", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeForwardingRule_update(poolName, ruleName), + }, + resource.TestStep{ + ResourceName: "google_compute_forwarding_rule.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeForwardingRule_singlePort(t *testing.T) { + t.Parallel() + + poolName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + ruleName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeForwardingRule_singlePort(poolName, ruleName), + }, + resource.TestStep{ + ResourceName: "google_compute_forwarding_rule.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeForwardingRule_ip(t *testing.T) { + t.Parallel() + + addrName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + poolName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + ruleName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeForwardingRule_ip(addrName, poolName, ruleName), + }, + resource.TestStep{ + ResourceName: "google_compute_forwarding_rule.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeForwardingRule_internalLoadBalancing(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + networkName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + ruleName1 := fmt.Sprintf("tf-%s", acctest.RandString(10)) + ruleName2 := fmt.Sprintf("tf-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeForwardingRule_internalLoadBalancing(serviceName, checkName, networkName, ruleName1, ruleName2), + }, + resource.TestStep{ + ResourceName: "google_compute_forwarding_rule.foobar", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_compute_forwarding_rule.foobar2", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeForwardingRule_networkTier(t *testing.T) { + t.Parallel() + + poolName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + ruleName := fmt.Sprintf("tf-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeForwardingRule_networkTier(poolName, ruleName), + }, + + resource.TestStep{ + ResourceName: "google_compute_forwarding_rule.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckComputeForwardingRuleDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_forwarding_rule" { + continue + } + + _, err := config.clientCompute.ForwardingRules.Get( + config.Project, config.Region, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("ForwardingRule still exists") + } + } + + return nil +} + +func testAccComputeForwardingRule_basic(poolName, ruleName string) string { + return fmt.Sprintf(` +resource "google_compute_target_pool" "foo-tp" { + description = "Resource created for Terraform acceptance testing" + instances = ["us-central1-a/foo", "us-central1-b/bar"] + name = "foo-%s" +} +resource "google_compute_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "UDP" + name = "%s" + port_range = "80-81" + target = "${google_compute_target_pool.foo-tp.self_link}" + labels = {"foo" = "bar"} +} +`, poolName, ruleName) +} + +func testAccComputeForwardingRule_update(poolName, ruleName string) string { + return fmt.Sprintf(` +resource "google_compute_target_pool" "foo-tp" { + description = "Resource created for Terraform acceptance testing" + instances = ["us-central1-a/foo", "us-central1-b/bar"] + name = "foo-%s" +} +resource "google_compute_target_pool" "bar-tp" { + description = "Resource created for Terraform acceptance testing" + instances = ["us-central1-a/foo", "us-central1-b/bar"] + name = "bar-%s" +} +resource "google_compute_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "UDP" + name = "%s" + port_range = "80-81" + target = "${google_compute_target_pool.bar-tp.self_link}" + labels = {"baz" = "qux"} +} +`, poolName, poolName, ruleName) +} + +func testAccComputeForwardingRule_singlePort(poolName, ruleName string) string { + return fmt.Sprintf(` +resource "google_compute_target_pool" "foobar-tp" { + description = "Resource created for Terraform acceptance testing" + instances = ["us-central1-a/foo", "us-central1-b/bar"] + name = "%s" +} +resource "google_compute_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "UDP" + name = "%s" + port_range = "80" + target = "${google_compute_target_pool.foobar-tp.self_link}" +} +`, poolName, ruleName) +} + +func testAccComputeForwardingRule_ip(addrName, poolName, ruleName string) string { + return fmt.Sprintf(` +resource "google_compute_address" "foo" { + name = "%s" +} +resource "google_compute_target_pool" "foobar-tp" { + description = "Resource created for Terraform acceptance testing" + instances = ["us-central1-a/foo", "us-central1-b/bar"] + name = "%s" +} +resource "google_compute_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_address = "${google_compute_address.foo.address}" + ip_protocol = "TCP" + name = "%s" + port_range = "80-81" + target = "${google_compute_target_pool.foobar-tp.self_link}" +} +`, addrName, poolName, ruleName) +} + +func testAccComputeForwardingRule_internalLoadBalancing(serviceName, checkName, networkName, ruleName1, ruleName2 string) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar-bs" { + name = "%s" + description = "Resource created for Terraform acceptance testing" + health_checks = ["${google_compute_health_check.zero.self_link}"] + region = "us-central1" +} +resource "google_compute_health_check" "zero" { + name = "%s" + description = "Resource created for Terraform acceptance testing" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + port = "80" + } +} +resource "google_compute_network" "foobar" { + name = "%s" + auto_create_subnetworks = true +} +resource "google_compute_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + load_balancing_scheme = "INTERNAL" + backend_service = "${google_compute_region_backend_service.foobar-bs.self_link}" + ports = ["80"] + network = "${google_compute_network.foobar.name}" + subnetwork = "%s" +} +resource "google_compute_forwarding_rule" "foobar2" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + load_balancing_scheme = "INTERNAL" + backend_service = "${google_compute_region_backend_service.foobar-bs.self_link}" + ports = ["80"] + network = "${google_compute_network.foobar.self_link}" +} +`, serviceName, checkName, networkName, ruleName1, networkName, ruleName2) +} + +func testAccComputeForwardingRule_networkTier(poolName, ruleName string) string { + return fmt.Sprintf(` +resource "google_compute_target_pool" "foobar-tp" { + description = "Resource created for Terraform acceptance testing" + instances = ["us-central1-a/foo", "us-central1-b/bar"] + name = "%s" +} +resource "google_compute_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "UDP" + name = "%s" + port_range = "80-81" + target = "${google_compute_target_pool.foobar-tp.self_link}" + + network_tier = "STANDARD" +} +`, poolName, ruleName) +} diff --git a/provider/terraform/tests/resource_compute_global_forwarding_rule_test.go b/provider/terraform/tests/resource_compute_global_forwarding_rule_test.go new file mode 100644 index 000000000000..d6dc28934045 --- /dev/null +++ b/provider/terraform/tests/resource_compute_global_forwarding_rule_test.go @@ -0,0 +1,579 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + computeBeta "google.golang.org/api/compute/v0.beta" +) + +func TestAccComputeGlobalForwardingRule_basic(t *testing.T) { + t.Parallel() + + fr := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + proxy1 := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + proxy2 := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + backend := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + hc := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + urlmap := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeGlobalForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeGlobalForwardingRule_basic1(fr, proxy1, proxy2, backend, hc, urlmap), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeGlobalForwardingRuleExists( + "google_compute_global_forwarding_rule.foobar"), + + // Unlike GlobalAddress, IpVersion is "" instead of "IPV4" when this is made with a v1 API. + testAccCheckComputeBetaGlobalForwardingRuleIpVersion("google_compute_global_forwarding_rule.foobar", ""), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_global_forwarding_rule.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeGlobalForwardingRule_update(t *testing.T) { + t.Parallel() + + fr := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + proxy1 := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + proxy2 := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + backend := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + hc := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + urlmap := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeGlobalForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeGlobalForwardingRule_basic1(fr, proxy1, proxy2, backend, hc, urlmap), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeGlobalForwardingRuleExists( + "google_compute_global_forwarding_rule.foobar"), + ), + }, + + resource.TestStep{ + Config: testAccComputeGlobalForwardingRule_basic2(fr, proxy1, proxy2, backend, hc, urlmap), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeGlobalForwardingRuleExists( + "google_compute_global_forwarding_rule.foobar"), + ), + }, + }, + }) +} + +func TestAccComputeGlobalForwardingRule_ipv6(t *testing.T) { + t.Parallel() + + var frule computeBeta.ForwardingRule + + fr := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + proxy1 := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + proxy2 := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + backend := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + hc := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + urlmap := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeGlobalForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeGlobalForwardingRule_ipv6(fr, proxy1, proxy2, backend, hc, urlmap), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBetaGlobalForwardingRuleExists( + "google_compute_global_forwarding_rule.foobar", &frule), + testAccCheckComputeBetaGlobalForwardingRuleIpVersion("google_compute_global_forwarding_rule.foobar", "IPV6"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_global_forwarding_rule.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeGlobalForwardingRule_labels(t *testing.T) { + t.Parallel() + + var frule computeBeta.ForwardingRule + + fr := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + proxy1 := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + proxy2 := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + backend := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + hc := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + urlmap := fmt.Sprintf("forwardrule-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeGlobalForwardingRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeGlobalForwardingRule_labels(fr, proxy1, proxy2, backend, hc, urlmap), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBetaGlobalForwardingRuleExists( + "google_compute_global_forwarding_rule.foobar", &frule), + testAccCheckComputeBetaGlobalForwardingRuleHasLabel(&frule, "my-label", "my-label-value"), + testAccCheckComputeBetaGlobalForwardingRuleHasLabel(&frule, "my-second-label", "my-second-label-value"), + testAccCheckComputeBetaGlobalForwardingRuleHasCorrectLabelFingerprint(&frule, "google_compute_global_forwarding_rule.foobar"), + ), + }, + resource.TestStep{ + Config: testAccComputeGlobalForwardingRule_labelsUpdated(fr, proxy1, proxy2, backend, hc, urlmap), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBetaGlobalForwardingRuleExists( + "google_compute_global_forwarding_rule.foobar", &frule), + testAccCheckComputeBetaGlobalForwardingRuleHasLabel(&frule, "my-label", "my-label-value"), + testAccCheckComputeBetaGlobalForwardingRuleHasLabel(&frule, "my-third-label", "my-third-label-value"), + testAccCheckComputeBetaGlobalForwardingRuleHasCorrectLabelFingerprint(&frule, "google_compute_global_forwarding_rule.foobar"), + ), + }, + }, + }) +} + +func testAccCheckComputeGlobalForwardingRuleDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_global_forwarding_rule" { + continue + } + + _, err := config.clientCompute.GlobalForwardingRules.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Global Forwarding Rule still exists") + } + } + + return nil +} + +func testAccCheckComputeGlobalForwardingRuleExists(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) + + found, err := config.clientCompute.GlobalForwardingRules.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Global Forwarding Rule not found") + } + + return nil + } +} + +func testAccCheckComputeBetaGlobalForwardingRuleExists(n string, frule *computeBeta.ForwardingRule) 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.GlobalForwardingRules.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Global Forwarding Rule not found") + } + + *frule = *found + + return nil + } +} + +func testAccCheckComputeBetaGlobalForwardingRuleIpVersion(n, version 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) + + frule, err := config.clientComputeBeta.GlobalForwardingRules.Get(config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if frule.IpVersion != version { + return fmt.Errorf("Expected IP version to be %s, got %s", version, frule.IpVersion) + } + + return nil + } +} + +func testAccCheckComputeBetaGlobalForwardingRuleHasLabel(frule *computeBeta.ForwardingRule, key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + val, ok := frule.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 testAccCheckComputeBetaGlobalForwardingRuleHasCorrectLabelFingerprint( + frule *computeBeta.ForwardingRule, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + tfLabelFingerprint := s.RootModule().Resources[resourceName].Primary.Attributes["label_fingerprint"] + remoteLabelFingerprint := frule.LabelFingerprint + + if tfLabelFingerprint != remoteLabelFingerprint { + return fmt.Errorf("Label fingerprint mismatch: remote has %#v but terraform has %#v", + remoteLabelFingerprint, tfLabelFingerprint) + } + return nil + } +} + +func testAccComputeGlobalForwardingRule_basic1(fr, proxy1, proxy2, backend, hc, urlmap string) string { + return fmt.Sprintf(` + resource "google_compute_global_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "TCP" + name = "%s" + port_range = "80" + target = "${google_compute_target_http_proxy.foobar1.self_link}" + } + + resource "google_compute_target_http_proxy" "foobar1" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_target_http_proxy" "foobar2" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + } + + resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 + } + + resource "google_compute_url_map" "foobar" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + }`, fr, proxy1, proxy2, backend, hc, urlmap) +} + +func testAccComputeGlobalForwardingRule_labels(fr, proxy1, proxy2, backend, hc, urlmap string) string { + return fmt.Sprintf(` + resource "google_compute_global_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "TCP" + name = "%s" + port_range = "80" + target = "${google_compute_target_http_proxy.foobar1.self_link}" + + labels { + my-label = "my-label-value" + my-second-label = "my-second-label-value" + } + } + + resource "google_compute_target_http_proxy" "foobar1" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_target_http_proxy" "foobar2" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + } + + resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 + } + + resource "google_compute_url_map" "foobar" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + }`, fr, proxy1, proxy2, backend, hc, urlmap) +} + +func testAccComputeGlobalForwardingRule_labelsUpdated(fr, proxy1, proxy2, backend, hc, urlmap string) string { + return fmt.Sprintf(` + resource "google_compute_global_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "TCP" + name = "%s" + port_range = "80" + target = "${google_compute_target_http_proxy.foobar1.self_link}" + + labels { + my-label = "my-label-value" + my-third-label = "my-third-label-value" + } + } + + resource "google_compute_target_http_proxy" "foobar1" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_target_http_proxy" "foobar2" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + } + + resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 + } + + resource "google_compute_url_map" "foobar" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + }`, fr, proxy1, proxy2, backend, hc, urlmap) +} + +func testAccComputeGlobalForwardingRule_basic2(fr, proxy1, proxy2, backend, hc, urlmap string) string { + return fmt.Sprintf(` + resource "google_compute_global_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "TCP" + name = "%s" + port_range = "80" + target = "${google_compute_target_http_proxy.foobar2.self_link}" + } + + resource "google_compute_target_http_proxy" "foobar1" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_target_http_proxy" "foobar2" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + } + + resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 + } + + resource "google_compute_url_map" "foobar" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + }`, fr, proxy1, proxy2, backend, hc, urlmap) +} + +func testAccComputeGlobalForwardingRule_ipv6(fr, proxy1, proxy2, backend, hc, urlmap string) string { + return fmt.Sprintf(` + resource "google_compute_global_forwarding_rule" "foobar" { + description = "Resource created for Terraform acceptance testing" + ip_protocol = "TCP" + name = "%s" + port_range = "80" + target = "${google_compute_target_http_proxy.foobar1.self_link}" + ip_version = "IPV6" + } + + resource "google_compute_target_http_proxy" "foobar1" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_target_http_proxy" "foobar2" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar.self_link}" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + } + + resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 + } + + resource "google_compute_url_map" "foobar" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + }`, fr, proxy1, proxy2, backend, hc, urlmap) +} diff --git a/provider/terraform/tests/resource_compute_http_health_check_test.go b/provider/terraform/tests/resource_compute_http_health_check_test.go new file mode 100644 index 000000000000..cf12e72b9642 --- /dev/null +++ b/provider/terraform/tests/resource_compute_http_health_check_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" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeHttpHealthCheck_basic(t *testing.T) { + t.Parallel() + + var healthCheck compute.HttpHealthCheck + + hhckName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeHttpHealthCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeHttpHealthCheck_basic(hhckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHttpHealthCheckExists( + "google_compute_http_health_check.foobar", &healthCheck), + testAccCheckComputeHttpHealthCheckRequestPath( + "/health_check", &healthCheck), + testAccCheckComputeHttpHealthCheckThresholds( + 3, 3, &healthCheck), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_http_health_check.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeHttpHealthCheck_update(t *testing.T) { + t.Parallel() + + var healthCheck compute.HttpHealthCheck + + hhckName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeHttpHealthCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeHttpHealthCheck_update1(hhckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHttpHealthCheckExists( + "google_compute_http_health_check.foobar", &healthCheck), + testAccCheckComputeHttpHealthCheckRequestPath( + "/not_default", &healthCheck), + testAccCheckComputeHttpHealthCheckThresholds( + 2, 2, &healthCheck), + ), + }, + resource.TestStep{ + Config: testAccComputeHttpHealthCheck_update2(hhckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHttpHealthCheckExists( + "google_compute_http_health_check.foobar", &healthCheck), + testAccCheckComputeHttpHealthCheckRequestPath( + "/", &healthCheck), + testAccCheckComputeHttpHealthCheckThresholds( + 10, 10, &healthCheck), + ), + }, + }, + }) +} + +func testAccCheckComputeHttpHealthCheckDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_http_health_check" { + continue + } + + _, err := config.clientCompute.HttpHealthChecks.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("HttpHealthCheck still exists") + } + } + + return nil +} + +func testAccCheckComputeHttpHealthCheckExists(n string, healthCheck *compute.HttpHealthCheck) 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.HttpHealthChecks.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("HttpHealthCheck not found") + } + + *healthCheck = *found + + return nil + } +} + +func testAccCheckComputeHttpHealthCheckRequestPath(path string, healthCheck *compute.HttpHealthCheck) resource.TestCheckFunc { + return func(s *terraform.State) error { + if healthCheck.RequestPath != path { + return fmt.Errorf("RequestPath doesn't match: expected %s, got %s", path, healthCheck.RequestPath) + } + + return nil + } +} + +func testAccCheckComputeHttpHealthCheckThresholds(healthy, unhealthy int64, healthCheck *compute.HttpHealthCheck) resource.TestCheckFunc { + return func(s *terraform.State) error { + if healthCheck.HealthyThreshold != healthy { + return fmt.Errorf("HealthyThreshold doesn't match: expected %d, got %d", healthy, healthCheck.HealthyThreshold) + } + + if healthCheck.UnhealthyThreshold != unhealthy { + return fmt.Errorf("UnhealthyThreshold doesn't match: expected %d, got %d", unhealthy, healthCheck.UnhealthyThreshold) + } + + return nil + } +} + +func testAccComputeHttpHealthCheck_basic(hhckName string) string { + return fmt.Sprintf(` +resource "google_compute_http_health_check" "foobar" { + name = "%s" + check_interval_sec = 3 + description = "Resource created for Terraform acceptance testing" + healthy_threshold = 3 + host = "foobar" + port = "80" + request_path = "/health_check" + timeout_sec = 2 + unhealthy_threshold = 3 +} +`, hhckName) +} + +func testAccComputeHttpHealthCheck_update1(hhckName string) string { + return fmt.Sprintf(` +resource "google_compute_http_health_check" "foobar" { + name = "%s" + description = "Resource created for Terraform acceptance testing" + request_path = "/not_default" +} +`, hhckName) +} + +func testAccComputeHttpHealthCheck_update2(hhckName string) string { + return fmt.Sprintf(` +resource "google_compute_http_health_check" "foobar" { + name = "%s" + description = "Resource updated for Terraform acceptance testing" + healthy_threshold = 10 + unhealthy_threshold = 10 +} +`, hhckName) +} diff --git a/provider/terraform/tests/resource_compute_https_health_check_test.go b/provider/terraform/tests/resource_compute_https_health_check_test.go new file mode 100644 index 000000000000..ee32427ff9cf --- /dev/null +++ b/provider/terraform/tests/resource_compute_https_health_check_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" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeHttpsHealthCheck_basic(t *testing.T) { + t.Parallel() + + var healthCheck compute.HttpsHealthCheck + + hhckName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeHttpsHealthCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeHttpsHealthCheck_basic(hhckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHttpsHealthCheckExists( + "google_compute_https_health_check.foobar", &healthCheck), + testAccCheckComputeHttpsHealthCheckRequestPath( + "/health_check", &healthCheck), + testAccCheckComputeHttpsHealthCheckThresholds( + 3, 3, &healthCheck), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_https_health_check.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeHttpsHealthCheck_update(t *testing.T) { + t.Parallel() + + var healthCheck compute.HttpsHealthCheck + + hhckName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeHttpsHealthCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeHttpsHealthCheck_update1(hhckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHttpsHealthCheckExists( + "google_compute_https_health_check.foobar", &healthCheck), + testAccCheckComputeHttpsHealthCheckRequestPath( + "/not_default", &healthCheck), + testAccCheckComputeHttpsHealthCheckThresholds( + 2, 2, &healthCheck), + ), + }, + resource.TestStep{ + Config: testAccComputeHttpsHealthCheck_update2(hhckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeHttpsHealthCheckExists( + "google_compute_https_health_check.foobar", &healthCheck), + testAccCheckComputeHttpsHealthCheckRequestPath( + "/", &healthCheck), + testAccCheckComputeHttpsHealthCheckThresholds( + 10, 10, &healthCheck), + ), + }, + }, + }) +} + +func testAccCheckComputeHttpsHealthCheckDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_https_health_check" { + continue + } + + _, err := config.clientCompute.HttpsHealthChecks.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("HttpsHealthCheck still exists") + } + } + + return nil +} + +func testAccCheckComputeHttpsHealthCheckExists(n string, healthCheck *compute.HttpsHealthCheck) 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.HttpsHealthChecks.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("HttpsHealthCheck not found") + } + + *healthCheck = *found + + return nil + } +} + +func testAccCheckComputeHttpsHealthCheckRequestPath(path string, healthCheck *compute.HttpsHealthCheck) resource.TestCheckFunc { + return func(s *terraform.State) error { + if healthCheck.RequestPath != path { + return fmt.Errorf("RequestPath doesn't match: expected %s, got %s", path, healthCheck.RequestPath) + } + + return nil + } +} + +func testAccCheckComputeHttpsHealthCheckThresholds(healthy, unhealthy int64, healthCheck *compute.HttpsHealthCheck) resource.TestCheckFunc { + return func(s *terraform.State) error { + if healthCheck.HealthyThreshold != healthy { + return fmt.Errorf("HealthyThreshold doesn't match: expected %d, got %d", healthy, healthCheck.HealthyThreshold) + } + + if healthCheck.UnhealthyThreshold != unhealthy { + return fmt.Errorf("UnhealthyThreshold doesn't match: expected %d, got %d", unhealthy, healthCheck.UnhealthyThreshold) + } + + return nil + } +} + +func testAccComputeHttpsHealthCheck_basic(hhckName string) string { + return fmt.Sprintf(` +resource "google_compute_https_health_check" "foobar" { + check_interval_sec = 3 + description = "Resource created for Terraform acceptance testing" + healthy_threshold = 3 + host = "foobar" + name = "%s" + port = "80" + request_path = "/health_check" + timeout_sec = 2 + unhealthy_threshold = 3 +} +`, hhckName) +} + +func testAccComputeHttpsHealthCheck_update1(hhckName string) string { + return fmt.Sprintf(` +resource "google_compute_https_health_check" "foobar" { + name = "%s" + description = "Resource created for Terraform acceptance testing" + request_path = "/not_default" +} +`, hhckName) +} + +func testAccComputeHttpsHealthCheck_update2(hhckName string) string { + return fmt.Sprintf(` +resource "google_compute_https_health_check" "foobar" { + name = "%s" + description = "Resource updated for Terraform acceptance testing" + healthy_threshold = 10 + unhealthy_threshold = 10 +} +`, hhckName) +} diff --git a/provider/terraform/tests/resource_compute_image_test.go b/provider/terraform/tests/resource_compute_image_test.go new file mode 100644 index 000000000000..66fe34c165e6 --- /dev/null +++ b/provider/terraform/tests/resource_compute_image_test.go @@ -0,0 +1,335 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeImage_basic(t *testing.T) { + t.Parallel() + + var image compute.Image + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeImageDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeImage_basic("image-test-" + acctest.RandString(10)), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeImageExists( + "google_compute_image.foobar", &image), + testAccCheckComputeImageDescription(&image, "description-test"), + testAccCheckComputeImageFamily(&image, "family-test"), + testAccCheckComputeImageContainsLabel(&image, "my-label", "my-label-value"), + testAccCheckComputeImageContainsLabel(&image, "empty-label", ""), + testAccCheckComputeImageHasComputedFingerprint(&image, "google_compute_image.foobar"), + ), + }, + }, + }) +} + +func TestAccComputeImage_withLicense(t *testing.T) { + t.Parallel() + + var image compute.Image + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeImageDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeImage_license("image-test-" + acctest.RandString(10)), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeImageExists( + "google_compute_image.foobar", &image), + testAccCheckComputeImageDescription(&image, "description-test"), + testAccCheckComputeImageFamily(&image, "family-test"), + testAccCheckComputeImageContainsLabel(&image, "my-label", "my-label-value"), + testAccCheckComputeImageContainsLabel(&image, "empty-label", ""), + testAccCheckComputeImageContainsLicense(&image, "https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx"), + testAccCheckComputeImageHasComputedFingerprint(&image, "google_compute_image.foobar"), + ), + }, + }, + }) +} + +func TestAccComputeImage_update(t *testing.T) { + t.Parallel() + + var image compute.Image + + name := "image-test-" + acctest.RandString(10) + // Only labels supports an update + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeImageDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeImage_basic(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeImageExists( + "google_compute_image.foobar", &image), + testAccCheckComputeImageContainsLabel(&image, "my-label", "my-label-value"), + testAccCheckComputeImageContainsLabel(&image, "empty-label", ""), + testAccCheckComputeImageHasComputedFingerprint(&image, "google_compute_image.foobar"), + ), + }, + resource.TestStep{ + Config: testAccComputeImage_update(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeImageExists( + "google_compute_image.foobar", &image), + testAccCheckComputeImageDoesNotContainLabel(&image, "my-label"), + testAccCheckComputeImageContainsLabel(&image, "empty-label", "oh-look-theres-a-label-now"), + testAccCheckComputeImageContainsLabel(&image, "new-field", "only-shows-up-when-updated"), + testAccCheckComputeImageHasComputedFingerprint(&image, "google_compute_image.foobar"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_image.foobar", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"raw_disk"}, + }, + }, + }) +} + +func TestAccComputeImage_basedondisk(t *testing.T) { + t.Parallel() + + var image compute.Image + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeImageDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeImage_basedondisk(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeImageExists( + "google_compute_image.foobar", &image), + testAccCheckComputeImageHasSourceDisk(&image), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_image.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckComputeImageDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_image" { + continue + } + + _, err := config.clientCompute.Images.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Image still exists") + } + } + + return nil +} + +func testAccCheckComputeImageExists(n string, image *compute.Image) 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.Images.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Image not found") + } + + *image = *found + + return nil + } +} + +func testAccCheckComputeImageDescription(image *compute.Image, description string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if image.Description != description { + return fmt.Errorf("Wrong image description: expected '%s' got '%s'", description, image.Description) + } + return nil + } +} + +func testAccCheckComputeImageFamily(image *compute.Image, family string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if image.Family != family { + return fmt.Errorf("Wrong image family: expected '%s' got '%s'", family, image.Family) + } + return nil + } +} + +func testAccCheckComputeImageContainsLabel(image *compute.Image, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + v, ok := image.Labels[key] + if !ok { + return fmt.Errorf("Expected label with key '%s' not found", key) + } + if v != value { + return fmt.Errorf("Incorrect label value for key '%s': expected '%s' but found '%s'", key, value, v) + } + return nil + } +} + +func testAccCheckComputeImageContainsLicense(image *compute.Image, expectedLicense string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + for _, thisLicense := range image.Licenses { + if thisLicense == expectedLicense { + return nil + } + } + + return fmt.Errorf("Expected license '%s' was not found", expectedLicense) + } +} + +func testAccCheckComputeImageDoesNotContainLabel(image *compute.Image, key string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if v, ok := image.Labels[key]; ok { + return fmt.Errorf("Expected no label for key '%s' but found one with value '%s'", key, v) + } + + return nil + } +} + +func testAccCheckComputeImageHasComputedFingerprint(image *compute.Image, resource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // First ensure we actually have a fingerprint + if image.LabelFingerprint == "" { + return fmt.Errorf("No fingerprint set in API read result") + } + + state := s.RootModule().Resources[resource] + if state == nil { + return fmt.Errorf("Unable to find resource named %s in resources", resource) + } + + storedFingerprint := state.Primary.Attributes["label_fingerprint"] + if storedFingerprint != image.LabelFingerprint { + return fmt.Errorf("Stored fingerprint doesn't match fingerprint found on server; stored '%s', server '%s'", + storedFingerprint, image.LabelFingerprint) + } + + return nil + } +} + +func testAccCheckComputeImageHasSourceDisk(image *compute.Image) resource.TestCheckFunc { + return func(s *terraform.State) error { + if image.SourceType == "" { + return fmt.Errorf("No source disk") + } + return nil + } +} + +func testAccComputeImage_basic(name string) string { + return fmt.Sprintf(` +resource "google_compute_image" "foobar" { + name = "%s" + description = "description-test" + family = "family-test" + raw_disk { + source = "https://storage.googleapis.com/bosh-cpi-artifacts/bosh-stemcell-3262.4-google-kvm-ubuntu-trusty-go_agent-raw.tar.gz" + } + labels = { + my-label = "my-label-value" + empty-label = "" + } +}`, name) +} + +func testAccComputeImage_license(name string) string { + return fmt.Sprintf(` +resource "google_compute_image" "foobar" { + name = "%s" + description = "description-test" + family = "family-test" + raw_disk { + source = "https://storage.googleapis.com/bosh-cpi-artifacts/bosh-stemcell-3262.4-google-kvm-ubuntu-trusty-go_agent-raw.tar.gz" + } + labels = { + my-label = "my-label-value" + empty-label = "" + } + licenses = [ + "https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx", + ] +}`, name) +} + +func testAccComputeImage_update(name string) string { + return fmt.Sprintf(` +resource "google_compute_image" "foobar" { + name = "%s" + description = "description-test" + family = "family-test" + raw_disk { + source = "https://storage.googleapis.com/bosh-cpi-artifacts/bosh-stemcell-3262.4-google-kvm-ubuntu-trusty-go_agent-raw.tar.gz" + } + labels = { + empty-label = "oh-look-theres-a-label-now" + new-field = "only-shows-up-when-updated" + } +}`, name) +} + +func testAccComputeImage_basedondisk() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "disk-test-%s" + zone = "us-central1-a" + image = "${data.google_compute_image.my_image.self_link}" +} +resource "google_compute_image" "foobar" { + name = "image-test-%s" + source_disk = "${google_compute_disk.foobar.self_link}" +}`, acctest.RandString(10), acctest.RandString(10)) +} diff --git a/provider/terraform/tests/resource_compute_instance_from_template_test.go b/provider/terraform/tests/resource_compute_instance_from_template_test.go new file mode 100644 index 000000000000..f250150ff0f8 --- /dev/null +++ b/provider/terraform/tests/resource_compute_instance_from_template_test.go @@ -0,0 +1,114 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + compute "google.golang.org/api/compute/v1" +) + +func TestAccComputeInstanceFromTemplate_basic(t *testing.T) { + t.Parallel() + + var instance compute.Instance + instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + templateName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + resourceName := "google_compute_instance_from_template.foobar" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceFromTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceFromTemplate_basic(instanceName, templateName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists(resourceName, &instance), + + // Check that fields were set based on the template + resource.TestCheckResourceAttr(resourceName, "machine_type", "n1-standard-1"), + resource.TestCheckResourceAttr(resourceName, "attached_disk.#", "1"), + ), + }, + }, + }) +} + +func testAccCheckComputeInstanceFromTemplateDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_instance_from_template" { + continue + } + + _, err := config.clientCompute.Instances.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Instance still exists") + } + } + + return nil +} + +func testAccComputeInstanceFromTemplate_basic(instance, template string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 100 + boot = true + } + + disk { + source = "${google_compute_disk.foobar.name}" + auto_delete = false + boot = false + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + can_ip_forward = true +} + +resource "google_compute_instance_from_template" "foobar" { + name = "%s" + zone = "us-central1-a" + + source_instance_template = "${google_compute_instance_template.foobar.self_link}" + + // Overrides + can_ip_forward = false + labels { + my_key = "my_value" + } +} +`, template, template, instance) +} diff --git a/provider/terraform/tests/resource_compute_instance_group_manager_test.go b/provider/terraform/tests/resource_compute_instance_group_manager_test.go new file mode 100644 index 000000000000..ef3d10e93cd5 --- /dev/null +++ b/provider/terraform/tests/resource_compute_instance_group_manager_test.go @@ -0,0 +1,1385 @@ +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" +) + +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)) + igm2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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, + }, + }, + }) +} + +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)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + ), + }, + }, + }) + + 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)) + template2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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{ + 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), + ), + }, + }, + }) +} + +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)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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{ + 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}), + ), + }, + }, + }) +} + +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{ + PreCheck: func() { testAccPreCheck(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"), + ), + }, + }, + }) +} + +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{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + 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"), + ), + }, + 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"), + ), + }, + }, + }) +} + +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)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + ), + }, + }, + }) +} + +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)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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, + }, + }, + }) +} + +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)) + hck := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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, + }, + }, + }) +} + +// This test is to make sure that a single version resource can link to a versioned resource +// without perpetual diffs because the self links mismatch. +// Once auto_healing_policies is no longer beta, we will need to use a new field or resource +// with Beta fields. +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)) + hck := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + autoscaler := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + }, + }, + }) +} + +func testAccCheckInstanceGroupManagerDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + 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() + 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 + } +} + +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"]) + } + 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 + } +} + +func testAccInstanceGroupManager_basic(template, target, igm1, igm2 string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" + } + + resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + } + + resource "google_compute_instance_group_manager" "igm-no-tp" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-no-tp" + zone = "us-central1-c" + target_size = 2 + } + `, template, target, igm1, igm2) +} + +func testAccInstanceGroupManager_targetSizeZero(template, igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-basic" + zone = "us-central1-c" + } + `, template, igm) +} + +func testAccInstanceGroupManager_update(template, target, igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-update" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_target_pool" "igm-update" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" + } + + resource "google_compute_instance_group_manager" "igm-update" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update.self_link}" + target_pools = ["${google_compute_target_pool.igm-update.self_link}"] + base_instance_name = "igm-update" + zone = "us-central1-c" + target_size = 2 + named_port { + name = "customhttp" + port = 8080 + } + }`, template, target, igm) +} + +// Change IGM's instance template and target size +func testAccInstanceGroupManager_update2(template1, target1, target2, template2, igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-update" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_target_pool" "igm-update" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" + } + + resource "google_compute_target_pool" "igm-update2" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" + } + + resource "google_compute_instance_template" "igm-update2" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_instance_group_manager" "igm-update" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update2.self_link}" + target_pools = [ + "${google_compute_target_pool.igm-update.self_link}", + "${google_compute_target_pool.igm-update2.self_link}", + ] + base_instance_name = "igm-update" + zone = "us-central1-c" + target_size = 3 + named_port { + name = "customhttp" + port = 8080 + } + named_port { + name = "customhttps" + port = 8443 + } + }`, template1, target1, target2, template2, igm) +} + +func testAccInstanceGroupManager_updateLifecycle(tag, igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-update" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["%s"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } + } + + resource "google_compute_instance_group_manager" "igm-update" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update.self_link}" + base_instance_name = "igm-update" + zone = "us-central1-c" + target_size = 2 + named_port { + name = "customhttp" + port = 8080 + } + }`, tag, igm) +} + +func testAccInstanceGroupManager_updateStrategy(igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-update-strategy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } + } + + resource "google_compute_instance_group_manager" "igm-update-strategy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update-strategy.self_link}" + base_instance_name = "igm-update-strategy" + zone = "us-central1-c" + target_size = 2 + update_strategy = "NONE" + named_port { + name = "customhttp" + port = 8080 + } + }`, igm) +} + +func testAccInstanceGroupManager_rollingUpdatePolicy(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + base_instance_name = "igm-rolling-update-policy" + zone = "us-central1-c" + target_size = 3 + update_strategy = "ROLLING_UPDATE" + rolling_update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_percent = 50 + max_unavailable_percent = 50 + min_ready_sec = 20 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccInstanceGroupManager_rollingUpdatePolicy2(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + base_instance_name = "igm-rolling-update-policy" + zone = "us-central1-c" + target_size = 3 + update_strategy = "ROLLING_UPDATE" + rolling_update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 2 + min_ready_sec = 20 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccInstanceGroupManager_separateRegions(igm1, igm2 string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-basic" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + } + + resource "google_compute_instance_group_manager" "igm-basic-2" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-basic-2" + zone = "us-west1-b" + target_size = 2 + } + `, igm1, igm2) +} + +func testAccInstanceGroupManager_autoHealingPolicies(template, target, igm, hck string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} + +resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + auto_healing_policies { + health_check = "${google_compute_http_health_check.zero.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + `, template, target, igm, hck) +} + +func testAccInstanceGroupManager_versions(primaryTemplate string, canaryTemplate string, igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-primary" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_instance_template" "igm-canary" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-primary.self_link}" + } + + version { + name = "canary" + instance_template = "${google_compute_instance_template.igm-canary.self_link}" + target_size { + fixed = 1 + } + } +} + `, primaryTemplate, canaryTemplate, igm) +} + +// This test is to make sure that a single version resource can link to a versioned resource +// without perpetual diffs because the self links mismatch. +// Once auto_healing_policies is no longer beta, we will need to use a new field or resource +// with Beta fields. +func testAccInstanceGroupManager_selfLinkStability(template, target, igm, hck, autoscaler string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} + +resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + auto_healing_policies { + health_check = "${google_compute_http_health_check.zero.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_autoscaler" "foobar" { + name = "%s" + zone = "us-central1-c" + target = "${google_compute_instance_group_manager.igm-basic.self_link}" + autoscaling_policy = { + max_replicas = 10 + min_replicas = 1 + cooldown_period = 60 + cpu_utilization = { + target = 0.5 + } + } +} +`, template, target, igm, hck, autoscaler) +} diff --git a/provider/terraform/tests/resource_compute_instance_group_migrate_test.go b/provider/terraform/tests/resource_compute_instance_group_migrate_test.go new file mode 100644 index 000000000000..1e6b6469b2bb --- /dev/null +++ b/provider/terraform/tests/resource_compute_instance_group_migrate_test.go @@ -0,0 +1,111 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestComputeInstanceGroupMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + Attributes map[string]string + ExpectedAttributes map[string]string + ExpectedId string + Meta interface{} + }{ + "v1 to v2": { + StateVersion: 1, + Attributes: map[string]string{ + "zone": "us-central1-c", + "name": "instancegroup-test", + }, + ExpectedAttributes: map[string]string{ + "zone": "us-central1-c", + "name": "instancegroup-test", + }, + ExpectedId: "us-central1-c/instancegroup-test", + Meta: &Config{}, + }, + "v0 to v2": { + StateVersion: 0, + Attributes: map[string]string{ + "zone": "us-central1-c", + "name": "instancegroup-test", + "instances.#": "1", + "instances.0": "https://www.googleapis.com/compute/v1/projects/project_name/zones/zone_name/instances/instancegroup-test-1", + "instances.1": "https://www.googleapis.com/compute/v1/projects/project_name/zones/zone_name/instances/instancegroup-test-0", + }, + ExpectedAttributes: map[string]string{ + "zone": "us-central1-c", + "name": "instancegroup-test", + "instances.#": "1", + "instances.764135222": "https://www.googleapis.com/compute/v1/projects/project_name/zones/zone_name/instances/instancegroup-test-1", + "instances.1519187872": "https://www.googleapis.com/compute/v1/projects/project_name/zones/zone_name/instances/instancegroup-test-0", + }, + ExpectedId: "us-central1-c/instancegroup-test", + Meta: &Config{}, + }, + } + + for tn, tc := range cases { + is := &terraform.InstanceState{ + ID: "i-abc123", + Attributes: tc.Attributes, + } + is, err := resourceComputeInstanceGroupMigrateState( + tc.StateVersion, is, tc.Meta) + + if err != nil { + t.Fatalf("bad: %s, err: %#v", tn, err) + } + + if is.ID != tc.ExpectedId { + t.Fatalf("bad: %s\n\n expected: %s\n got: %s", tn, tc.ExpectedId, is.ID) + } + + for k, v := range tc.ExpectedAttributes { + 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 TestComputeInstanceGroupMigrateState_empty(t *testing.T) { + cases := map[string]struct { + StateVersion int + }{ + "v0": { + StateVersion: 0, + }, + "v1": { + StateVersion: 1, + }, + } + + for tn, tc := range cases { + var is *terraform.InstanceState + var meta *Config + + // should handle nil + is, err := resourceComputeInstanceGroupMigrateState(tc.StateVersion, is, meta) + + if err != nil { + t.Fatalf("bad %s, err: %#v", tn, err) + } + if is != nil { + t.Fatalf("bad %s, expected nil instancestate, got: %#v", tn, is) + } + + // should handle non-nil but empty + is = &terraform.InstanceState{} + is, err = resourceComputeInstanceGroupMigrateState(tc.StateVersion, is, meta) + + if err != nil { + t.Fatalf("bad %s, err: %#v", tn, err) + } + } +} diff --git a/provider/terraform/tests/resource_compute_instance_group_test.go b/provider/terraform/tests/resource_compute_instance_group_test.go new file mode 100644 index 000000000000..ab14a3a01346 --- /dev/null +++ b/provider/terraform/tests/resource_compute_instance_group_test.go @@ -0,0 +1,588 @@ +package google + +import ( + "fmt" + "testing" + + "google.golang.org/api/compute/v1" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeInstanceGroup_basic(t *testing.T) { + t.Parallel() + + var instanceGroup compute.InstanceGroup + var instanceName = fmt.Sprintf("instancegroup-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccComputeInstanceGroup_destroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeInstanceGroup_basic(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.basic", &instanceGroup), + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.empty", &instanceGroup), + ), + }, + { + ResourceName: "google_compute_instance_group.basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceGroup_recreatedInstances(t *testing.T) { + t.Parallel() + + var instanceGroup compute.InstanceGroup + var instanceName = fmt.Sprintf("instancegroup-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccComputeInstanceGroup_destroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeInstanceGroup_update(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.update", &instanceGroup), + ), + }, + { + Config: testAccComputeInstanceGroup_recreateInstances(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.update", &instanceGroup), + testAccComputeInstanceGroup_updated( + "google_compute_instance_group.update", 2, &instanceGroup), + ), + }, + }, + }) +} + +func TestAccComputeInstanceGroup_update(t *testing.T) { + t.Parallel() + + var instanceGroup compute.InstanceGroup + var instanceName = fmt.Sprintf("instancegroup-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccComputeInstanceGroup_destroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeInstanceGroup_update(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.update", &instanceGroup), + testAccComputeInstanceGroup_named_ports( + "google_compute_instance_group.update", + map[string]int64{"http": 8080, "https": 8443}, + &instanceGroup), + ), + }, + { + Config: testAccComputeInstanceGroup_update2(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.update", &instanceGroup), + testAccComputeInstanceGroup_updated( + "google_compute_instance_group.update", 1, &instanceGroup), + testAccComputeInstanceGroup_named_ports( + "google_compute_instance_group.update", + map[string]int64{"http": 8081, "test": 8444}, + &instanceGroup), + ), + }, + }, + }) +} + +func TestAccComputeInstanceGroup_outOfOrderInstances(t *testing.T) { + t.Parallel() + + var instanceGroup compute.InstanceGroup + var instanceName = fmt.Sprintf("instancegroup-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccComputeInstanceGroup_destroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeInstanceGroup_outOfOrderInstances(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.group", &instanceGroup), + ), + }, + }, + }) +} + +func TestAccComputeInstanceGroup_network(t *testing.T) { + t.Parallel() + + var instanceGroup compute.InstanceGroup + var instanceName = fmt.Sprintf("instancegroup-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccComputeInstanceGroup_destroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceGroup_network(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.with_instance", &instanceGroup), + testAccComputeInstanceGroup_hasCorrectNetwork( + "google_compute_instance_group.with_instance", "google_compute_network.ig_network", &instanceGroup), + testAccComputeInstanceGroup_exists( + "google_compute_instance_group.without_instance", &instanceGroup), + testAccComputeInstanceGroup_hasCorrectNetwork( + "google_compute_instance_group.without_instance", "google_compute_network.ig_network", &instanceGroup), + ), + }, + }, + }) +} + +func testAccComputeInstanceGroup_destroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_instance_group" { + continue + } + _, err := config.clientCompute.InstanceGroups.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.Attributes["name"]).Do() + if err == nil { + return fmt.Errorf("InstanceGroup still exists") + } + } + + return nil +} + +func testAccComputeInstanceGroup_exists(n string, instanceGroup *compute.InstanceGroup) 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.InstanceGroups.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.Attributes["name"]).Do() + if err != nil { + return err + } + + *instanceGroup = *found + + return nil + } +} + +func testAccComputeInstanceGroup_updated(n string, size int64, instanceGroup *compute.InstanceGroup) 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) + + instanceGroup, err := config.clientCompute.InstanceGroups.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.Attributes["name"]).Do() + if err != nil { + return err + } + + // Cannot check the target pool as the instance creation is asynchronous. However, can + // check the target_size. + if instanceGroup.Size != size { + return fmt.Errorf("instance count incorrect") + } + + return nil + } +} + +func testAccComputeInstanceGroup_named_ports(n string, np map[string]int64, instanceGroup *compute.InstanceGroup) 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) + + instanceGroup, err := config.clientCompute.InstanceGroups.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.Attributes["name"]).Do() + if err != nil { + return err + } + + var found bool + for _, namedPort := range instanceGroup.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 testAccComputeInstanceGroup_hasCorrectNetwork(nInstanceGroup string, nNetwork string, instanceGroup *compute.InstanceGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + rsInstanceGroup, ok := s.RootModule().Resources[nInstanceGroup] + if !ok { + return fmt.Errorf("Not found: %s", nInstanceGroup) + } + if rsInstanceGroup.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + instanceGroup, err := config.clientCompute.InstanceGroups.Get( + config.Project, rsInstanceGroup.Primary.Attributes["zone"], rsInstanceGroup.Primary.Attributes["name"]).Do() + if err != nil { + return err + } + + rsNetwork, ok := s.RootModule().Resources[nNetwork] + if !ok { + return fmt.Errorf("Not found: %s", nNetwork) + } + if rsNetwork.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + network, err := config.clientCompute.Networks.Get( + config.Project, rsNetwork.Primary.ID).Do() + if err != nil { + return err + } + + if instanceGroup.Network != network.SelfLink { + return fmt.Errorf("network incorrect: actual=%s vs expected=%s", instanceGroup.Network, network.SelfLink) + } + + return nil + } +} + +func testAccComputeInstanceGroup_basic(instance string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance" "ig_instance" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + zone = "us-central1-c" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + } + + resource "google_compute_instance_group" "basic" { + description = "Terraform test instance group" + name = "%s" + zone = "us-central1-c" + instances = [ "${google_compute_instance.ig_instance.self_link}" ] + named_port { + name = "http" + port = "8080" + } + named_port { + name = "https" + port = "8443" + } + } + + resource "google_compute_instance_group" "empty" { + description = "Terraform test instance group empty" + name = "%s-empty" + zone = "us-central1-c" + named_port { + name = "http" + port = "8080" + } + named_port { + name = "https" + port = "8443" + } + }`, instance, instance, instance) +} + +func testAccComputeInstanceGroup_update(instance string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance" "ig_instance" { + name = "%s-${count.index}" + machine_type = "n1-standard-1" + can_ip_forward = false + zone = "us-central1-c" + count = 2 + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + } + + resource "google_compute_instance_group" "update" { + description = "Terraform test instance group" + name = "%s" + zone = "us-central1-c" + instances = [ "${google_compute_instance.ig_instance.*.self_link}" ] + named_port { + name = "http" + port = "8080" + } + named_port { + name = "https" + port = "8443" + } + }`, instance, instance) +} + +// Change IGM's instance template and target size +func testAccComputeInstanceGroup_update2(instance string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance" "ig_instance" { + name = "%s-${count.index}" + machine_type = "n1-standard-1" + can_ip_forward = false + zone = "us-central1-c" + count = 1 + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + } + + resource "google_compute_instance_group" "update" { + description = "Terraform test instance group" + name = "%s" + zone = "us-central1-c" + instances = [ "${google_compute_instance.ig_instance.*.self_link}" ] + + named_port { + name = "http" + port = "8081" + } + named_port { + name = "test" + port = "8444" + } + }`, instance, instance) +} + +func testAccComputeInstanceGroup_recreateInstances(instance string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance" "ig_instance" { + name = "%s-${count.index}" + machine_type = "n1-standard-1" + can_ip_forward = false + zone = "us-central1-c" + count = 2 + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + metadata_startup_script = "echo 'foo'" + + network_interface { + network = "default" + } + } + + resource "google_compute_instance_group" "update" { + description = "Terraform test instance group" + name = "%s" + zone = "us-central1-c" + instances = [ "${google_compute_instance.ig_instance.*.self_link}" ] + named_port { + name = "http" + port = "8080" + } + named_port { + name = "https" + port = "8443" + } + }`, instance, instance) +} + +func testAccComputeInstanceGroup_outOfOrderInstances(instance string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance" "ig_instance" { + name = "%s-1" + machine_type = "n1-standard-1" + can_ip_forward = false + zone = "us-central1-c" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + } + + resource "google_compute_instance" "ig_instance_2" { + name = "%s-2" + machine_type = "n1-standard-1" + can_ip_forward = false + zone = "us-central1-c" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + } + + resource "google_compute_instance_group" "group" { + description = "Terraform test instance group" + name = "%s" + zone = "us-central1-c" + instances = [ "${google_compute_instance.ig_instance_2.self_link}", "${google_compute_instance.ig_instance.self_link}" ] + named_port { + name = "http" + port = "8080" + } + named_port { + name = "https" + port = "8443" + } + }`, instance, instance, instance) +} + +func testAccComputeInstanceGroup_network(instance string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_network" "ig_network" { + name = "%[1]s" + auto_create_subnetworks = true + } + + resource "google_compute_instance" "ig_instance" { + name = "%[1]s" + machine_type = "n1-standard-1" + can_ip_forward = false + zone = "us-central1-c" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "${google_compute_network.ig_network.name}" + } + } + + resource "google_compute_instance_group" "with_instance" { + description = "Terraform test instance group" + name = "%[1]s-with-instance" + zone = "us-central1-c" + instances = [ "${google_compute_instance.ig_instance.self_link}" ] + } + + resource "google_compute_instance_group" "without_instance" { + description = "Terraform test instance group" + name = "%[1]s-without-instance" + zone = "us-central1-c" + network = "${google_compute_network.ig_network.self_link}" + }`, instance) +} diff --git a/provider/terraform/tests/resource_compute_instance_migrate_test.go b/provider/terraform/tests/resource_compute_instance_migrate_test.go new file mode 100644 index 000000000000..871e22b0a868 --- /dev/null +++ b/provider/terraform/tests/resource_compute_instance_migrate_test.go @@ -0,0 +1,877 @@ +package google + +import ( + "fmt" + "log" + "os" + "testing" + + "google.golang.org/api/compute/v1" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestComputeInstanceMigrateState(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + cases := map[string]struct { + StateVersion int + Attributes map[string]string + Expected map[string]string + }{ + "v0.4.2 and earlier": { + StateVersion: 0, + Attributes: map[string]string{ + "metadata.#": "2", + "metadata.0.foo": "bar", + "metadata.1.baz": "qux", + "metadata.2.with.dots": "should.work", + }, + Expected: map[string]string{ + "metadata.foo": "bar", + "metadata.baz": "qux", + "metadata.with.dots": "should.work", + }, + }, + "change scope from list to set": { + StateVersion: 1, + Attributes: map[string]string{ + "service_account.#": "1", + "service_account.0.email": "xxxxxx-compute@developer.gserviceaccount.com", + "service_account.0.scopes.#": "4", + "service_account.0.scopes.0": "https://www.googleapis.com/auth/compute", + "service_account.0.scopes.1": "https://www.googleapis.com/auth/datastore", + "service_account.0.scopes.2": "https://www.googleapis.com/auth/devstorage.full_control", + "service_account.0.scopes.3": "https://www.googleapis.com/auth/logging.write", + }, + Expected: map[string]string{ + "service_account.#": "1", + "service_account.0.email": "xxxxxx-compute@developer.gserviceaccount.com", + "service_account.0.scopes.#": "4", + "service_account.0.scopes.1693978638": "https://www.googleapis.com/auth/devstorage.full_control", + "service_account.0.scopes.172152165": "https://www.googleapis.com/auth/logging.write", + "service_account.0.scopes.299962681": "https://www.googleapis.com/auth/compute", + "service_account.0.scopes.3435931483": "https://www.googleapis.com/auth/datastore", + }, + }, + "add new create_timeout attribute": { + StateVersion: 2, + Attributes: map[string]string{}, + Expected: map[string]string{ + "create_timeout": "4", + }, + }, + "remove empty initialize_params": { + StateVersion: 5, + Attributes: map[string]string{ + "boot_disk.0.initialize_params.#": "1", + "boot_disk.0.initialize_params.0.size": "0", + }, + Expected: map[string]string{ + "boot_disk.0.initialize_params.#": "0", + }, + }, + } + + config := getInitializedConfig(t) + for tn, tc := range cases { + runInstanceMigrateTest(t, "i-abc123", tn, tc.StateVersion, tc.Attributes, tc.Expected, config) + } +} + +func TestComputeInstanceMigrateState_empty(t *testing.T) { + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + var is *terraform.InstanceState + var meta interface{} + + // should handle nil + is, err := resourceComputeInstanceMigrateState(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 = resourceComputeInstanceMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } +} + +func TestAccComputeInstanceMigrateState_bootDisk(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "disk.#": "1", + "disk.0.disk": "disk-1", + "disk.0.type": "pd-ssd", + "disk.0.auto_delete": "false", + "disk.0.size": "12", + "disk.0.device_name": "persistent-disk-0", + "disk.0.disk_encryption_key_raw": "encrypt-key", + "disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "boot_disk.0.auto_delete": "false", + "boot_disk.0.device_name": "persistent-disk-0", + "boot_disk.0.disk_encryption_key_raw": "encrypt-key", + "boot_disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "boot_disk.0.initialize_params.#": "1", + "boot_disk.0.initialize_params.0.size": "12", + "boot_disk.0.initialize_params.0.type": "pd-ssd", + "boot_disk.0.source": instanceName, + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to boot disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixBootDisk(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "disk.#": "1", + "disk.0.disk": "disk-1", + "disk.0.type": "pd-ssd", + "disk.0.auto_delete": "false", + "disk.0.size": "12", + "disk.0.device_name": "persistent-disk-0", + "disk.0.disk_encryption_key_raw": "encrypt-key", + "disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "boot_disk.0.auto_delete": "false", + "boot_disk.0.device_name": "persistent-disk-0", + "boot_disk.0.disk_encryption_key_raw": "encrypt-key", + "boot_disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "boot_disk.0.initialize_params.#": "1", + "boot_disk.0.initialize_params.0.size": "12", + "boot_disk.0.initialize_params.0.type": "pd-ssd", + "boot_disk.0.source": instanceName, + "zone": zone, + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to boot disk", 4 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_attachedDiskFromSource(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + diskName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + disk := &compute.Disk{ + Name: diskName, + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + Zone: zone, + } + op, err := config.clientCompute.Disks.Insert(config.Project, zone, disk).Do() + if err != nil { + t.Fatalf("Error creating disk: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "disk to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpDisk(config, diskName, zone) + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + Source: "projects/" + config.Project + "/zones/" + zone + "/disks/" + diskName, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err = config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr = computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.disk": diskName, + "disk.0.device_name": "persistent-disk-1", + "disk.0.disk_encryption_key_raw": "encrypt-key", + "disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "1", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + diskName, + "attached_disk.0.device_name": "persistent-disk-1", + "attached_disk.0.disk_encryption_key_raw": "encrypt-key", + "attached_disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixAttachedDiskFromSource(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + diskName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + disk := &compute.Disk{ + Name: diskName, + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + Zone: zone, + } + op, err := config.clientCompute.Disks.Insert(config.Project, zone, disk).Do() + if err != nil { + t.Fatalf("Error creating disk: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "disk to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpDisk(config, diskName, zone) + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + Source: "projects/" + config.Project + "/zones/" + zone + "/disks/" + diskName, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err = config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr = computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.disk": diskName, + "disk.0.device_name": "persistent-disk-1", + "disk.0.disk_encryption_key_raw": "encrypt-key", + "disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "1", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + diskName, + "attached_disk.0.device_name": "persistent-disk-1", + "attached_disk.0.disk_encryption_key_raw": "encrypt-key", + "attached_disk.0.disk_encryption_key_sha256": "encrypt-key-sha", + "zone": zone, + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 4 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_attachedDiskFromEncryptionKey(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + DiskEncryptionKey: &compute.CustomerEncryptionKey{ + RawKey: "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.image": "projects/debian-cloud/global/images/family/debian-9", + "disk.0.disk_encryption_key_raw": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + "disk.0.disk_encryption_key_sha256": "esTuF7d4eatX4cnc4JsiEiaI+Rff78JgPhA/v1zxX9E=", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "1", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-1", + "attached_disk.0.device_name": "persistent-disk-1", + "attached_disk.0.disk_encryption_key_raw": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + "attached_disk.0.disk_encryption_key_sha256": "esTuF7d4eatX4cnc4JsiEiaI+Rff78JgPhA/v1zxX9E=", + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixAttachedDiskFromEncryptionKey(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + DiskEncryptionKey: &compute.CustomerEncryptionKey{ + RawKey: "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.image": "projects/debian-cloud/global/images/family/debian-9", + "disk.0.disk_encryption_key_raw": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + "disk.0.disk_encryption_key_sha256": "esTuF7d4eatX4cnc4JsiEiaI+Rff78JgPhA/v1zxX9E=", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "1", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-1", + "attached_disk.0.device_name": "persistent-disk-1", + "attached_disk.0.disk_encryption_key_raw": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=", + "attached_disk.0.disk_encryption_key_sha256": "esTuF7d4eatX4cnc4JsiEiaI+Rff78JgPhA/v1zxX9E=", + "zone": zone, + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 4 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_attachedDiskFromAutoDeleteAndImage(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/debian-9-stretch-v20180814", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "2", + "disk.0.image": "projects/debian-cloud/global/images/debian-9-stretch-v20180814", + "disk.0.auto_delete": "true", + "disk.1.image": "global/images/family/debian-9", + "disk.1.auto_delete": "true", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "2", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-2", + "attached_disk.0.device_name": "persistent-disk-2", + "attached_disk.1.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-1", + "attached_disk.1.device_name": "persistent-disk-1", + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixAttachedDiskFromAutoDeleteAndImage(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + AutoDelete: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/debian-9-stretch-v20180814", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "2", + "disk.0.image": "projects/debian-cloud/global/images/debian-9-stretch-v20180814", + "disk.0.auto_delete": "true", + "disk.1.image": "global/images/family/debian-9", + "disk.1.auto_delete": "true", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "attached_disk.#": "2", + "attached_disk.0.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-2", + "attached_disk.0.device_name": "persistent-disk-2", + "attached_disk.1.source": "https://www.googleapis.com/compute/v1/projects/" + config.Project + "/zones/" + zone + "/disks/" + instanceName + "-1", + "attached_disk.1.device_name": "persistent-disk-1", + "zone": zone, + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to attached disk", 4 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_scratchDisk(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + AutoDelete: true, + Type: "SCRATCH", + InitializeParams: &compute.AttachedDiskInitializeParams{ + DiskType: "zones/" + zone + "/diskTypes/local-ssd", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.auto_delete": "true", + "disk.0.type": "local-ssd", + "disk.0.scratch": "true", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "scratch_disk.#": "1", + "scratch_disk.0.interface": "SCSI", + "zone": zone, + "create_timeout": "4", + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to scratch disk", 2 /* state version */, attributes, expected, config) +} + +func TestAccComputeInstanceMigrateState_v4FixScratchDisk(t *testing.T) { + t.Parallel() + + if os.Getenv(resource.TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) + } + config := getInitializedConfig(t) + zone := "us-central1-f" + + // Seed test data + instanceName := fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + instance := &compute.Instance{ + Name: instanceName, + Disks: []*compute.AttachedDisk{ + { + Boot: true, + InitializeParams: &compute.AttachedDiskInitializeParams{ + SourceImage: "projects/debian-cloud/global/images/family/debian-9", + }, + }, + { + AutoDelete: true, + Type: "SCRATCH", + InitializeParams: &compute.AttachedDiskInitializeParams{ + DiskType: "zones/" + zone + "/diskTypes/local-ssd", + }, + }, + }, + MachineType: "zones/" + zone + "/machineTypes/n1-standard-1", + NetworkInterfaces: []*compute.NetworkInterface{ + { + Network: "global/networks/default", + }, + }, + } + op, err := config.clientCompute.Instances.Insert(config.Project, zone, instance).Do() + if err != nil { + t.Fatalf("Error creating instance: %s", err) + } + waitErr := computeSharedOperationWait(config.clientCompute, op, config.Project, "instance to create") + if waitErr != nil { + t.Fatal(waitErr) + } + defer cleanUpInstance(config, instanceName, zone) + + attributes := map[string]string{ + "boot_disk.#": "1", + "disk.#": "1", + "disk.0.auto_delete": "true", + "disk.0.type": "local-ssd", + "disk.0.scratch": "true", + "zone": zone, + } + expected := map[string]string{ + "boot_disk.#": "1", + "scratch_disk.#": "1", + "scratch_disk.0.interface": "SCSI", + "zone": zone, + } + + runInstanceMigrateTest(t, instanceName, "migrate disk to scratch disk", 4 /* state version */, attributes, expected, config) +} + +func runInstanceMigrateTest(t *testing.T, id, testName string, version int, attributes, expected map[string]string, meta interface{}) { + is := &terraform.InstanceState{ + ID: id, + Attributes: attributes, + } + is, err := resourceComputeInstanceMigrateState(version, is, meta) + if err != nil { + t.Fatal(err) + } + + for k, v := range expected { + if attributes[k] != v { + t.Fatalf( + "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", + testName, k, expected[k], k, attributes[k], attributes) + } + } + + for k, v := range attributes { + if expected[k] != v { + t.Fatalf( + "bad: %s\n\n expected: %#v -> %#v\n got: %#v -> %#v\n in: %#v", + testName, k, expected[k], k, attributes[k], attributes) + } + } +} + +func cleanUpInstance(config *Config, instanceName, zone string) { + op, err := config.clientCompute.Instances.Delete(config.Project, zone, instanceName).Do() + if err != nil { + log.Printf("[WARNING] Error deleting instance %q, dangling resources may exist: %s", instanceName, err) + return + } + + // Wait for the operation to complete + opErr := computeOperationWait(config.clientCompute, op, config.Project, "instance to delete") + if opErr != nil { + log.Printf("[WARNING] Error deleting instance %q, dangling resources may exist: %s", instanceName, opErr) + } +} + +func cleanUpDisk(config *Config, diskName, zone string) { + op, err := config.clientCompute.Disks.Delete(config.Project, zone, diskName).Do() + if err != nil { + log.Printf("[WARNING] Error deleting disk %q, dangling resources may exist: %s", diskName, err) + return + } + + // Wait for the operation to complete + opErr := computeOperationWait(config.clientCompute, op, config.Project, "disk to delete") + if opErr != nil { + log.Printf("[WARNING] Error deleting disk %q, dangling resources may exist: %s", diskName, opErr) + } +} + +func getInitializedConfig(t *testing.T) *Config { + // Check that all required environment variables are set + testAccPreCheck(t) + + config := &Config{ + Project: getTestProjectFromEnv(), + Credentials: getTestCredsFromEnv(), + Region: getTestRegionFromEnv(), + } + err := config.loadAndValidate() + if err != nil { + t.Fatal(err) + } + return config +} diff --git a/provider/terraform/tests/resource_compute_instance_template_migrate_test.go b/provider/terraform/tests/resource_compute_instance_template_migrate_test.go new file mode 100644 index 000000000000..cc334b4f6098 --- /dev/null +++ b/provider/terraform/tests/resource_compute_instance_template_migrate_test.go @@ -0,0 +1,137 @@ +package google + +import ( + "testing" + + "reflect" + "strings" + + "github.com/hashicorp/terraform/terraform" +) + +func TestComputeInstanceTemplateMigrateState(t *testing.T) { + cases := map[string]struct { + StateVersion int + BeforeAttributes map[string]string + AfterAttributes map[string]string + ErrorStringExpected string + Meta interface{} + }{ + "invalid state version": { + StateVersion: -1, + ErrorStringExpected: "Unexpected schema version: -1", + }, + "simple automatic_restart case removal": { + StateVersion: 0, + BeforeAttributes: map[string]string{ + "automatic_restart": "true", + "scheduling.#": "1", + "scheduling.0.automatic_restart": "true", + }, + AfterAttributes: map[string]string{ + "scheduling.#": "1", + "scheduling.0.automatic_restart": "true", + }, + }, + "simple on_host_maintenance removal": { + StateVersion: 0, + BeforeAttributes: map[string]string{ + "on_host_maintenance": "MIGRATE", + "automatic_restart": "true", + "scheduling.#": "1", + "scheduling.0.automatic_restart": "true", + }, + AfterAttributes: map[string]string{ + "scheduling.#": "1", + "scheduling.0.automatic_restart": "true", + }, + }, + "missing scheduling block": { + StateVersion: 0, + BeforeAttributes: map[string]string{ + "automatic_restart": "true", + }, + AfterAttributes: map[string]string{ + "scheduling.#": "1", + "scheduling.0.automatic_restart": "true", + }, + }, + "empty scheduling block": { + StateVersion: 0, + BeforeAttributes: map[string]string{ + "automatic_restart": "true", + "scheduling.#": "0", + }, + AfterAttributes: map[string]string{ + "scheduling.#": "1", + "scheduling.0.automatic_restart": "true", + }, + }, + "error upon multiple scheduling block": { + StateVersion: 0, + BeforeAttributes: map[string]string{ + "automatic_restart": "true", + "scheduling.#": "2", + "scheduling.0.automatic_restart": "true", + "scheduling.1.automatic_restart": "true", + }, + ErrorStringExpected: "Found multiple scheduling blocks when there should only be one", + }, + "error upon differing automatic_restart values": { + StateVersion: 0, + BeforeAttributes: map[string]string{ + "automatic_restart": "true", + "scheduling.#": "1", + "scheduling.0.automatic_restart": "false", + }, + ErrorStringExpected: "Found differing values for automatic_restart in state, unsure how to proceed.", + }, + } + + for tn, tc := range cases { + is := &terraform.InstanceState{ + ID: "i-abc123", + Attributes: tc.BeforeAttributes, + } + is, err := resourceComputeInstanceTemplateMigrateState( + tc.StateVersion, is, tc.Meta) + + if err != nil { + if tc.ErrorStringExpected == "" { + t.Fatalf("bad: %s, err: %#v", tn, err) + } else if !strings.Contains(err.Error(), tc.ErrorStringExpected) { + t.Fatalf("Expected error containing string %s, instead found %#v", tc.ErrorStringExpected, err) + } else { + continue + } + } + + // Compare both maps for identity + if !reflect.DeepEqual(is.Attributes, tc.AfterAttributes) { + t.Fatalf("Expected attributes %#v, got attributes %#v", tc.AfterAttributes, is.Attributes) + } + } +} + +func TestComputeInstanceTemplateMigrateState_empty(t *testing.T) { + var is *terraform.InstanceState + var meta interface{} + + // should handle nil + is, err := resourceComputeInstanceTemplateMigrateState(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 = resourceComputeInstanceTemplateMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } +} diff --git a/provider/terraform/tests/resource_compute_instance_template_test.go b/provider/terraform/tests/resource_compute_instance_template_test.go new file mode 100644 index 000000000000..f15c36fb4114 --- /dev/null +++ b/provider/terraform/tests/resource_compute_instance_template_test.go @@ -0,0 +1,1392 @@ +package google + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +const DEFAULT_MIN_CPU_TEST_VALUE = "Intel Haswell" + +func TestAccComputeInstanceTemplate_basic(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateTag(&instanceTemplate, "foo"), + testAccCheckComputeInstanceTemplateMetadata(&instanceTemplate, "foo", "bar"), + testAccCheckComputeInstanceTemplateContainsLabel(&instanceTemplate, "my_label", "foobar"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_imageShorthand(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_imageShorthand(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_preemptible(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_preemptible(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateAutomaticRestart(&instanceTemplate, false), + testAccCheckComputeInstanceTemplatePreemptible(&instanceTemplate, true), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_IP(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_ip(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateNetwork(&instanceTemplate), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_networkTier(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_networkTier(), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_networkIP(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + networkIP := "10.128.0.2" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_networkIP(networkIP), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateNetwork(&instanceTemplate), + testAccCheckComputeInstanceTemplateNetworkIP( + "google_compute_instance_template.foobar", networkIP, &instanceTemplate), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} +func TestAccComputeInstanceTemplate_networkIPAddress(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + ipAddress := "10.128.0.2" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_networkIPAddress(ipAddress), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateNetwork(&instanceTemplate), + testAccCheckComputeInstanceTemplateNetworkIPAddress( + "google_compute_instance_template.foobar", ipAddress, &instanceTemplate), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_disks(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_disks(), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_regionDisks(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_regionDisks(), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_subnet_auto(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + network := "network-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_subnet_auto(network), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateNetworkName(&instanceTemplate, network), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_subnet_custom(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_subnet_custom(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateSubnetwork(&instanceTemplate), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_subnet_xpn(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + projectName := fmt.Sprintf("tf-xpntest-%d", time.Now().Unix()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_subnet_xpn(org, billingId, projectName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExistsInProject( + "google_compute_instance_template.foobar", fmt.Sprintf("%s-service", projectName), + &instanceTemplate), + testAccCheckComputeInstanceTemplateSubnetwork(&instanceTemplate), + ), + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_metadata_startup_script(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_startup_script(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists( + "google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateStartupScript(&instanceTemplate, "echo 'Hello'"), + ), + }, + }, + }) +} +func TestAccComputeInstanceTemplate_primaryAliasIpRange(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_primaryAliasIpRange(acctest.RandString(10)), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists("google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateHasAliasIpRange(&instanceTemplate, "", "/24"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_secondaryAliasIpRange(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_secondaryAliasIpRange(acctest.RandString(10)), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists("google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateHasAliasIpRange(&instanceTemplate, "inst-test-secondary", "/24"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeInstanceTemplate_guestAccelerator(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_guestAccelerator(acctest.RandString(10), 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists("google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateHasGuestAccelerator(&instanceTemplate, "nvidia-tesla-k80", 1), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + +} + +func TestAccComputeInstanceTemplate_guestAcceleratorSkip(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_guestAccelerator(acctest.RandString(10), 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists("google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateLacksGuestAccelerator(&instanceTemplate), + ), + }, + }, + }) + +} + +func TestAccComputeInstanceTemplate_minCpuPlatform(t *testing.T) { + t.Parallel() + + var instanceTemplate compute.InstanceTemplate + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstanceTemplate_minCpuPlatform(acctest.RandString(10)), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceTemplateExists("google_compute_instance_template.foobar", &instanceTemplate), + testAccCheckComputeInstanceTemplateHasMinCpuPlatform(&instanceTemplate, DEFAULT_MIN_CPU_TEST_VALUE), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckComputeInstanceTemplateDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_instance_template" { + continue + } + + _, err := config.clientCompute.InstanceTemplates.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Instance template still exists") + } + } + + return nil +} + +func testAccCheckComputeInstanceTemplateExists(n string, instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc { + return testAccCheckComputeInstanceTemplateExistsInProject(n, getTestProjectFromEnv(), instanceTemplate) +} + +func testAccCheckComputeInstanceTemplateExistsInProject(n, p string, instanceTemplate *compute.InstanceTemplate) 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.InstanceTemplates.Get( + p, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Instance template not found") + } + + *instanceTemplate = *found + + return nil + } +} + +func testAccCheckComputeInstanceTemplateMetadata( + instanceTemplate *compute.InstanceTemplate, + k string, v string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instanceTemplate.Properties.Metadata == nil { + return fmt.Errorf("no metadata") + } + + for _, item := range instanceTemplate.Properties.Metadata.Items { + if k != item.Key { + continue + } + + if item.Value != nil && v == *item.Value { + return nil + } + + return fmt.Errorf("bad value for %s: %s", k, *item.Value) + } + + return fmt.Errorf("metadata not found: %s", k) + } +} + +func testAccCheckComputeInstanceTemplateNetwork(instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instanceTemplate.Properties.NetworkInterfaces { + for _, c := range i.AccessConfigs { + if c.NatIP == "" { + return fmt.Errorf("no NAT IP") + } + } + } + + return nil + } +} + +func testAccCheckComputeInstanceTemplateNetworkName(instanceTemplate *compute.InstanceTemplate, network string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instanceTemplate.Properties.NetworkInterfaces { + if !strings.Contains(i.Network, network) { + return fmt.Errorf("Network doesn't match expected value, Expected: %s Actual: %s", network, i.Network[strings.LastIndex("/", i.Network)+1:]) + } + } + + return nil + } +} + +func testAccCheckComputeInstanceTemplateSubnetwork(instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instanceTemplate.Properties.NetworkInterfaces { + if i.Subnetwork == "" { + return fmt.Errorf("no subnet") + } + } + + return nil + } +} + +func testAccCheckComputeInstanceTemplateTag(instanceTemplate *compute.InstanceTemplate, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instanceTemplate.Properties.Tags == nil { + return fmt.Errorf("no tags") + } + + for _, k := range instanceTemplate.Properties.Tags.Items { + if k == n { + return nil + } + } + + return fmt.Errorf("tag not found: %s", n) + } +} + +func testAccCheckComputeInstanceTemplatePreemptible(instanceTemplate *compute.InstanceTemplate, preemptible bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instanceTemplate.Properties.Scheduling.Preemptible != preemptible { + return fmt.Errorf("Expected preemptible value %v, got %v", preemptible, instanceTemplate.Properties.Scheduling.Preemptible) + } + return nil + } +} + +func testAccCheckComputeInstanceTemplateAutomaticRestart(instanceTemplate *compute.InstanceTemplate, automaticRestart bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + ar := instanceTemplate.Properties.Scheduling.AutomaticRestart + if ar == nil { + return fmt.Errorf("Expected to see a value for AutomaticRestart, but got nil") + } + if *ar != automaticRestart { + return fmt.Errorf("Expected automatic restart value %v, got %v", automaticRestart, ar) + } + return nil + } +} + +func testAccCheckComputeInstanceTemplateStartupScript(instanceTemplate *compute.InstanceTemplate, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instanceTemplate.Properties.Metadata == nil && n == "" { + return nil + } else if instanceTemplate.Properties.Metadata == nil && n != "" { + return fmt.Errorf("Expected metadata.startup-script to be '%s', metadata wasn't set at all", n) + } + for _, item := range instanceTemplate.Properties.Metadata.Items { + if item.Key != "startup-script" { + continue + } + if item.Value != nil && *item.Value == n { + return nil + } else if item.Value == nil && n == "" { + return nil + } else if item.Value == nil && n != "" { + return fmt.Errorf("Expected metadata.startup-script to be '%s', wasn't set", n) + } else if *item.Value != n { + return fmt.Errorf("Expected metadata.startup-script to be '%s', got '%s'", n, *item.Value) + } + } + return fmt.Errorf("This should never be reached.") + } +} + +func testAccCheckComputeInstanceTemplateNetworkIP(n, networkIP string, instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc { + return func(s *terraform.State) error { + ip := instanceTemplate.Properties.NetworkInterfaces[0].NetworkIP + err := resource.TestCheckResourceAttr(n, "network_interface.0.network_ip", ip)(s) + if err != nil { + return err + } + return resource.TestCheckResourceAttr(n, "network_interface.0.network_ip", networkIP)(s) + } +} + +func testAccCheckComputeInstanceTemplateNetworkIPAddress(n, ipAddress string, instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc { + return func(s *terraform.State) error { + ip := instanceTemplate.Properties.NetworkInterfaces[0].NetworkIP + err := resource.TestCheckResourceAttr(n, "network_interface.0.network_ip", ip)(s) + if err != nil { + return err + } + return resource.TestCheckResourceAttr(n, "network_interface.0.network_ip", ipAddress)(s) + } +} + +func testAccCheckComputeInstanceTemplateContainsLabel(instanceTemplate *compute.InstanceTemplate, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + v, ok := instanceTemplate.Properties.Labels[key] + if !ok { + return fmt.Errorf("Expected label with key '%s' not found", key) + } + if v != value { + return fmt.Errorf("Incorrect label value for key '%s': expected '%s' but found '%s'", key, value, v) + } + return nil + } +} + +func testAccCheckComputeInstanceTemplateHasAliasIpRange(instanceTemplate *compute.InstanceTemplate, subnetworkRangeName, iPCidrRange string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, networkInterface := range instanceTemplate.Properties.NetworkInterfaces { + for _, aliasIpRange := range networkInterface.AliasIpRanges { + if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || ipCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { + return nil + } + } + } + + return fmt.Errorf("Alias ip range with name %s and cidr %s not present", subnetworkRangeName, iPCidrRange) + } +} + +func testAccCheckComputeInstanceTemplateHasGuestAccelerator(instanceTemplate *compute.InstanceTemplate, acceleratorType string, acceleratorCount int64) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(instanceTemplate.Properties.GuestAccelerators) != 1 { + return fmt.Errorf("Expected only one guest accelerator") + } + + if !strings.HasSuffix(instanceTemplate.Properties.GuestAccelerators[0].AcceleratorType, acceleratorType) { + return fmt.Errorf("Wrong accelerator type: expected %v, got %v", acceleratorType, instanceTemplate.Properties.GuestAccelerators[0].AcceleratorType) + } + + if instanceTemplate.Properties.GuestAccelerators[0].AcceleratorCount != acceleratorCount { + return fmt.Errorf("Wrong accelerator acceleratorCount: expected %d, got %d", acceleratorCount, instanceTemplate.Properties.GuestAccelerators[0].AcceleratorCount) + } + + return nil + } +} + +func testAccCheckComputeInstanceTemplateLacksGuestAccelerator(instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(instanceTemplate.Properties.GuestAccelerators) > 0 { + return fmt.Errorf("Expected no guest accelerators") + } + + return nil + } +} + +func testAccCheckComputeInstanceTemplateHasMinCpuPlatform(instanceTemplate *compute.InstanceTemplate, minCpuPlatform string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instanceTemplate.Properties.MinCpuPlatform != minCpuPlatform { + return fmt.Errorf("Wrong minimum CPU platform: expected %s, got %s", minCpuPlatform, instanceTemplate.Properties.MinCpuPlatform) + } + + return nil + } +} + +func testAccComputeInstanceTemplate_basic() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + scheduling { + preemptible = false + automatic_restart = true + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + labels { + my_label = "foobar" + } +}`, acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_imageShorthand() string { + return fmt.Sprintf(` +resource "google_compute_image" "foobar" { + name = "test-%s" + description = "description-test" + family = "family-test" + raw_disk { + source = "https://storage.googleapis.com/bosh-cpi-artifacts/bosh-stemcell-3262.4-google-kvm-ubuntu-trusty-go_agent-raw.tar.gz" + } + labels = { + my-label = "my-label-value" + empty-label = "" + } + timeouts { + create = "5m" + } +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${google_compute_image.foobar.name}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + scheduling { + preemptible = false + automatic_restart = true + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + labels { + my_label = "foobar" + } +}`, acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_preemptible() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + scheduling { + preemptible = true + automatic_restart = false + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +}`, acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_ip() string { + return fmt.Sprintf(` +resource "google_compute_address" "foo" { + name = "instancet-test-%s" +} + +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + } + + network_interface { + network = "default" + access_config { + nat_ip = "${google_compute_address.foo.address}" + } + } + + metadata { + foo = "bar" + } +}`, acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_networkTier() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + } + + network_interface { + network = "default" + access_config { + network_tier = "STANDARD" + } + } +}`, acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_networkIP(networkIP string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + } + + network_interface { + network = "default" + network_ip = "%s" + } + + metadata { + foo = "bar" + } +}`, acctest.RandString(10), networkIP) +} + +func testAccComputeInstanceTemplate_networkIPAddress(ipAddress string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + } + + network_interface { + network = "default" + network_ip = "%s" + } + + metadata { + foo = "bar" + } +}`, acctest.RandString(10), ipAddress) +} + +func testAccComputeInstanceTemplate_disks() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "instancet-test-%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 100 + boot = true + } + + disk { + source = "${google_compute_disk.foobar.name}" + auto_delete = false + boot = false + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +}`, acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_regionDisks() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_region_disk" "foobar" { + name = "instancet-test-%s" + size = 10 + type = "pd-ssd" + region = "us-central1" + replica_zones = ["us-central1-a", "us-central1-f"] +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 100 + boot = true + } + + disk { + source = "${google_compute_region_disk.foobar.name}" + auto_delete = false + boot = false + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +}`, acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_subnet_auto(network string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_network" "auto-network" { + name = "%s" + auto_create_subnetworks = true + } + + resource "google_compute_instance_template" "foobar" { + name = "instance-tpl-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + network_interface { + network = "${google_compute_network.auto-network.name}" + } + + metadata { + foo = "bar" + } + }`, network, acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_subnet_custom() string { + return fmt.Sprintf(` +resource "google_compute_network" "network" { + name = "network-%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "subnetwork-%s" + ip_cidr_range = "10.0.0.0/24" + region = "us-central1" + network = "${google_compute_network.network.self_link}" +} + +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instance-test-%s" + machine_type = "n1-standard-1" + region = "us-central1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + network_interface { + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + } + + metadata { + foo = "bar" + } +}`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_subnet_xpn(org, billingId, projectName string) string { + return fmt.Sprintf(` + resource "google_project" "host_project" { + name = "Test Project XPN Host" + project_id = "%s-host" + org_id = "%s" + billing_account = "%s" + } + + resource "google_project_service" "host_project" { + project = "${google_project.host_project.project_id}" + service = "compute.googleapis.com" + } + + resource "google_compute_shared_vpc_host_project" "host_project" { + project = "${google_project_service.host_project.project}" + } + + resource "google_project" "service_project" { + name = "Test Project XPN Service" + project_id = "%s-service" + org_id = "%s" + billing_account = "%s" + } + + resource "google_project_service" "service_project" { + project = "${google_project.service_project.project_id}" + service = "compute.googleapis.com" + } + + resource "google_compute_shared_vpc_service_project" "service_project" { + host_project = "${google_compute_shared_vpc_host_project.host_project.project}" + service_project = "${google_project_service.service_project.project}" + } + + resource "google_compute_network" "network" { + name = "network-%s" + auto_create_subnetworks = false + project = "${google_compute_shared_vpc_host_project.host_project.project}" + } + + resource "google_compute_subnetwork" "subnetwork" { + name = "subnetwork-%s" + ip_cidr_range = "10.0.0.0/24" + region = "us-central1" + network = "${google_compute_network.network.self_link}" + project = "${google_compute_shared_vpc_host_project.host_project.project}" + } + + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "foobar" { + name = "instance-test-%s" + machine_type = "n1-standard-1" + region = "us-central1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + network_interface { + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + subnetwork_project = "${google_compute_subnetwork.subnetwork.project}" + } + + metadata { + foo = "bar" + } + project = "${google_compute_shared_vpc_service_project.service_project.service_project}" + }`, projectName, org, billingId, projectName, org, billingId, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_startup_script() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instance-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + metadata { + foo = "bar" + } + + network_interface{ + network = "default" + } + + metadata_startup_script = "echo 'Hello'" +}`, acctest.RandString(10)) +} + +func testAccComputeInstanceTemplate_primaryAliasIpRange(i string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instance-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + metadata { + foo = "bar" + } + + network_interface{ + network = "default" + alias_ip_range { + ip_cidr_range = "/24" + } + } +}`, i) +} + +func testAccComputeInstanceTemplate_secondaryAliasIpRange(i string) string { + return fmt.Sprintf(` +resource "google_compute_network" "inst-test-network" { + name = "inst-test-network-%s" +} +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "inst-test-subnetwork-%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-east1" + network = "${google_compute_network.inst-test-network.self_link}" + secondary_ip_range { + range_name = "inst-test-secondary" + ip_cidr_range = "172.16.0.0/20" + } +} +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} +resource "google_compute_instance_template" "foobar" { + name = "instance-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + metadata { + foo = "bar" + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.self_link}" + + // Note that unlike compute instances, instance templates seem to be + // only able to specify the netmask here. Trying a full CIDR string + // results in: + // Invalid value for field 'resource.properties.networkInterfaces[0].aliasIpRanges[0].ipCidrRange': + // '172.16.0.0/24'. Alias IP CIDR range must be a valid netmask starting with '/' (e.g. '/24') + alias_ip_range { + subnetwork_range_name = "${google_compute_subnetwork.inst-test-subnetwork.secondary_ip_range.0.range_name}" + ip_cidr_range = "/24" + } + } +}`, i, i, i) +} + +func testAccComputeInstanceTemplate_guestAccelerator(i string, count uint8) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instance-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + network_interface { + network = "default" + } + + scheduling { + # Instances with guest accelerators do not support live migration. + on_host_maintenance = "TERMINATE" + } + + guest_accelerator { + count = %d + type = "nvidia-tesla-k80" + } +}`, i, count) +} + +func testAccComputeInstanceTemplate_minCpuPlatform(i string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instance-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + disk_size_gb = 10 + boot = true + } + + network_interface { + network = "default" + } + + scheduling { + # Instances with guest accelerators do not support live migration. + on_host_maintenance = "TERMINATE" + } + + min_cpu_platform = "%s" +}`, i, DEFAULT_MIN_CPU_TEST_VALUE) +} diff --git a/provider/terraform/tests/resource_compute_instance_test.go b/provider/terraform/tests/resource_compute_instance_test.go new file mode 100644 index 000000000000..34690170f03e --- /dev/null +++ b/provider/terraform/tests/resource_compute_instance_test.go @@ -0,0 +1,3015 @@ +package google + +import ( + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func computeInstanceImportStep(zone, instanceName string, additionalImportIgnores []string) resource.TestStep { + // metadata is only read into state if set in the config + // since importing doesn't know whether metadata.startup_script vs metadata_startup_script is set in the config, + // it guesses metadata_startup_script + ignores := []string{"metadata.%", "metadata.startup-script", "metadata_startup_script"} + + return resource.TestStep{ + ResourceName: "google_compute_instance.foobar", + ImportState: true, + ImportStateId: fmt.Sprintf("%s/%s/%s", getTestProjectFromEnv(), zone, instanceName), + ImportStateVerify: true, + ImportStateVerifyIgnore: append(ignores, additionalImportIgnores...), + } +} + +func TestAccComputeInstance_basic1(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasInstanceId(&instance, "google_compute_instance.foobar"), + testAccCheckComputeInstanceTag(&instance, "foo"), + testAccCheckComputeInstanceLabel(&instance, "my_key", "my_value"), + testAccCheckComputeInstanceMetadata(&instance, "foo", "bar"), + testAccCheckComputeInstanceMetadata(&instance, "baz", "qux"), + testAccCheckComputeInstanceDisk(&instance, instanceName, true, true), + // by default, DeletionProtection is implicitly false. This should be false on any + // instance resource without an explicit deletion_protection = true declaration. + // Other tests check explicit true/false configs: TestAccComputeInstance_deletionProtectionExplicit[True | False] + testAccCheckComputeInstanceHasConfiguredDeletionProtection(&instance, false), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{"metadata.baz", "metadata.foo"}), + }, + }) +} + +func TestAccComputeInstance_basic2(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic2(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceTag(&instance, "foo"), + testAccCheckComputeInstanceMetadata(&instance, "foo", "bar"), + testAccCheckComputeInstanceDisk(&instance, instanceName, true, true), + ), + }, + }, + }) +} + +func TestAccComputeInstance_basic3(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic3(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceTag(&instance, "foo"), + testAccCheckComputeInstanceMetadata(&instance, "foo", "bar"), + testAccCheckComputeInstanceDisk(&instance, instanceName, true, true), + ), + }, + }, + }) +} + +func TestAccComputeInstance_basic4(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic4(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceTag(&instance, "foo"), + testAccCheckComputeInstanceMetadata(&instance, "foo", "bar"), + testAccCheckComputeInstanceDisk(&instance, instanceName, true, true), + ), + }, + }, + }) +} + +func TestAccComputeInstance_basic5(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic5(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceTag(&instance, "foo"), + testAccCheckComputeInstanceMetadata(&instance, "foo", "bar"), + testAccCheckComputeInstanceDisk(&instance, instanceName, true, true), + ), + }, + }, + }) +} + +func TestAccComputeInstance_IP(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var ipName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_ip(ipName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceAccessConfigHasNatIP(&instance), + ), + }, + }, + }) +} + +func TestAccComputeInstance_PTRRecord(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var ptrName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var ipName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_PTRRecord(ptrName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceAccessConfigHasPTR(&instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{"metadata.baz", "metadata.foo"}), + resource.TestStep{ + Config: testAccComputeInstance_ip(ipName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceAccessConfigHasNatIP(&instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{"metadata.baz", "metadata.foo"}), + }, + }) +} + +func TestAccComputeInstance_networkTier(t *testing.T) { + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_networkTier(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceAccessConfigHasNatIP(&instance), + testAccCheckComputeInstanceHasAssignedNatIP, + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_diskEncryption(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + bootEncryptionKey := "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" + bootEncryptionKeyHash := "esTuF7d4eatX4cnc4JsiEiaI+Rff78JgPhA/v1zxX9E=" + diskNameToEncryptionKey := map[string]*compute.CustomerEncryptionKey{ + fmt.Sprintf("instance-testd-%s", acctest.RandString(10)): { + RawKey: "Ym9vdDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=", + Sha256: "awJ7p57H+uVZ9axhJjl1D3lfC2MgA/wnt/z88Ltfvss=", + }, + fmt.Sprintf("instance-testd-%s", acctest.RandString(10)): { + RawKey: "c2Vjb25kNzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=", + Sha256: "7TpIwUdtCOJpq2m+3nt8GFgppu6a2Xsj1t0Gexk13Yc=", + }, + fmt.Sprintf("instance-testd-%s", acctest.RandString(10)): { + RawKey: "dGhpcmQ2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=", + Sha256: "b3pvaS7BjDbCKeLPPTx7yXBuQtxyMobCHN1QJR43xeM=", + }, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_disks_encryption(bootEncryptionKey, diskNameToEncryptionKey, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDiskEncryptionKey("google_compute_instance.foobar", &instance, bootEncryptionKeyHash, diskNameToEncryptionKey), + ), + }, + }, + }) +} + +func TestAccComputeInstance_attachedDisk(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskName = fmt.Sprintf("instance-testd-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_attachedDisk(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDisk(&instance, diskName, false, false), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_attachedDisk_sourceUrl(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskName = fmt.Sprintf("instance-testd-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_attachedDisk_sourceUrl(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDisk(&instance, diskName, false, false), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_attachedDisk_modeRo(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskName = fmt.Sprintf("instance-testd-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_attachedDisk_modeRo(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDisk(&instance, diskName, false, false), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_attachedDiskUpdate(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskName = fmt.Sprintf("instance-testd-%s", acctest.RandString(10)) + var diskName2 = fmt.Sprintf("instance-testd-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_attachedDisk(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDisk(&instance, diskName, false, false), + ), + }, + // check attaching + resource.TestStep{ + Config: testAccComputeInstance_addAttachedDisk(diskName, diskName2, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDisk(&instance, diskName, false, false), + testAccCheckComputeInstanceDisk(&instance, diskName2, false, false), + ), + }, + // check detaching + resource.TestStep{ + Config: testAccComputeInstance_detachDisk(diskName, diskName2, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDisk(&instance, diskName, false, false), + ), + }, + // check updating + resource.TestStep{ + Config: testAccComputeInstance_updateAttachedDiskEncryptionKey(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDisk(&instance, diskName, false, false), + ), + }, + }, + }) +} + +func TestAccComputeInstance_bootDisk_source(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_bootDisk_source(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceBootDisk(&instance, diskName), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_bootDisk_sourceUrl(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_bootDisk_sourceUrl(diskName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceBootDisk(&instance, diskName), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_bootDisk_type(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskType = "pd-ssd" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_bootDisk_type(instanceName, diskType), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceBootDiskType(instanceName, diskType), + ), + }, + }, + }) +} + +func TestAccComputeInstance_scratchDisk(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_scratchDisk(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.scratch", &instance), + testAccCheckComputeInstanceScratchDisk(&instance, []string{"NVME", "SCSI"}), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_forceNewAndChangeMetadata(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + ), + }, + resource.TestStep{ + Config: testAccComputeInstance_forceNewAndChangeMetadata(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceMetadata( + &instance, "qux", "true"), + ), + }, + }, + }) +} + +func TestAccComputeInstance_update(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + ), + }, + resource.TestStep{ + Config: testAccComputeInstance_update(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceMetadata( + &instance, "bar", "baz"), + testAccCheckComputeInstanceLabel(&instance, "only_me", "nothing_else"), + testAccCheckComputeInstanceTag(&instance, "baz"), + testAccCheckComputeInstanceAccessConfig(&instance), + ), + }, + }, + }) +} + +func TestAccComputeInstance_stopInstanceToUpdate(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + // Set fields that require stopping the instance + resource.TestStep{ + Config: testAccComputeInstance_stopInstanceToUpdate(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{"allow_stopping_for_update"}), + // Check that updating them works + resource.TestStep{ + Config: testAccComputeInstance_stopInstanceToUpdate2(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{"allow_stopping_for_update"}), + // Check that removing them works + resource.TestStep{ + Config: testAccComputeInstance_stopInstanceToUpdate3(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{"allow_stopping_for_update"}), + }, + }) +} + +func TestAccComputeInstance_service_account(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_service_account(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceServiceAccount(&instance, + "https://www.googleapis.com/auth/compute.readonly"), + testAccCheckComputeInstanceServiceAccount(&instance, + "https://www.googleapis.com/auth/devstorage.read_only"), + testAccCheckComputeInstanceServiceAccount(&instance, + "https://www.googleapis.com/auth/userinfo.email"), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_scheduling(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_scheduling(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_subnet_auto(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_subnet_auto(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasSubnet(&instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_subnet_custom(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_subnet_custom(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasSubnet(&instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_subnet_xpn(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + projectName := fmt.Sprintf("tf-xpntest-%d", time.Now().Unix()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_subnet_xpn(org, billingId, projectName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExistsInProject( + "google_compute_instance.foobar", fmt.Sprintf("%s-service", projectName), + &instance), + testAccCheckComputeInstanceHasSubnet(&instance), + ), + }, + }, + }) +} + +func TestAccComputeInstance_networkIPAuto(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_networkIPAuto(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasAnyNetworkIP(&instance), + ), + }, + }, + }) +} + +func TestAccComputeInstance_network_ip_custom(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var ipAddress = "10.0.200.200" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_network_ip_custom(instanceName, ipAddress), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasNetworkIP(&instance, ipAddress), + ), + }, + }, + }) +} + +func TestAccComputeInstance_private_image_family(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + var diskName = fmt.Sprintf("instance-testd-%s", acctest.RandString(10)) + var familyName = fmt.Sprintf("instance-testf-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_private_image_family(diskName, familyName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + ), + }, + }, + }) +} + +func TestAccComputeInstance_forceChangeMachineTypeManually(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceUpdateMachineType("google_compute_instance.foobar"), + ), + ExpectNonEmptyPlan: true, + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{"metadata.baz", "metadata.foo"}), + }, + }) +} + +func TestAccComputeInstance_multiNic(t *testing.T) { + t.Parallel() + + var instance compute.Instance + instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + networkName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + subnetworkName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_multiNic(instanceName, networkName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasMultiNic(&instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_guestAccelerator(t *testing.T) { + t.Parallel() + + var instance compute.Instance + instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_guestAccelerator(instanceName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasGuestAccelerator(&instance, "nvidia-tesla-k80", 1), + ), + }, + computeInstanceImportStep("us-east1-d", instanceName, []string{"metadata.baz", "metadata.foo"}), + }, + }) + +} + +func TestAccComputeInstance_guestAcceleratorSkip(t *testing.T) { + t.Parallel() + + var instance compute.Instance + instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_guestAccelerator(instanceName, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceLacksGuestAccelerator(&instance), + ), + }, + }, + }) + +} + +func TestAccComputeInstance_minCpuPlatform(t *testing.T) { + t.Parallel() + + var instance compute.Instance + instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_minCpuPlatform(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasMinCpuPlatform(&instance, "Intel Haswell"), + ), + }, + computeInstanceImportStep("us-east1-d", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_deletionProtectionExplicitFalse(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic_deletionProtectionFalse(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasConfiguredDeletionProtection(&instance, false), + ), + }, + }, + }) +} + +func TestAccComputeInstance_deletionProtectionExplicitTrueAndUpdateFalse(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_basic_deletionProtectionTrue(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasConfiguredDeletionProtection(&instance, true), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{"metadata.foo"}), + // Update deletion_protection to false, otherwise the test harness can't delete the instance + resource.TestStep{ + Config: testAccComputeInstance_basic_deletionProtectionFalse(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasConfiguredDeletionProtection(&instance, false), + ), + }, + }, + }) +} + +func TestAccComputeInstance_primaryAliasIpRange(t *testing.T) { + t.Parallel() + + var instance compute.Instance + instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_primaryAliasIpRange(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasAliasIpRange(&instance, "", "/24"), + ), + }, + computeInstanceImportStep("us-east1-d", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_secondaryAliasIpRange(t *testing.T) { + t.Parallel() + + var instance compute.Instance + instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + networkName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + subnetName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeInstance_secondaryAliasIpRange(networkName, subnetName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasAliasIpRange(&instance, "inst-test-secondary", "172.16.0.0/24"), + ), + }, + computeInstanceImportStep("us-east1-d", instanceName, []string{}), + resource.TestStep{ + Config: testAccComputeInstance_secondaryAliasIpRangeUpdate(networkName, subnetName, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceHasAliasIpRange(&instance, "", "10.0.1.0/24"), + ), + }, + computeInstanceImportStep("us-east1-d", instanceName, []string{}), + }, + }) +} + +func testAccCheckComputeInstanceUpdateMachineType(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) + + op, err := config.clientCompute.Instances.Stop(config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err != nil { + return fmt.Errorf("Could not stop instance: %s", err) + } + err = computeOperationWait(config.clientCompute, op, config.Project, "Waiting on stop") + if err != nil { + return fmt.Errorf("Could not stop instance: %s", err) + } + + machineType := compute.InstancesSetMachineTypeRequest{ + MachineType: "zones/us-central1-a/machineTypes/f1-micro", + } + + op, err = config.clientCompute.Instances.SetMachineType( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID, &machineType).Do() + if err != nil { + return fmt.Errorf("Could not change machine type: %s", err) + } + err = computeOperationWait(config.clientCompute, op, config.Project, "Waiting machine type change") + if err != nil { + return fmt.Errorf("Could not change machine type: %s", err) + } + return nil + } +} + +func testAccCheckComputeInstanceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_instance" { + continue + } + + _, err := config.clientCompute.Instances.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Instance still exists") + } + } + + return nil +} + +func testAccCheckComputeInstanceExists(n string, instance *compute.Instance) resource.TestCheckFunc { + return testAccCheckComputeInstanceExistsInProject(n, getTestProjectFromEnv(), instance) +} + +func testAccCheckComputeInstanceExistsInProject(n, p string, instance *compute.Instance) 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.Instances.Get( + p, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Instance not found") + } + + *instance = *found + + return nil + } +} + +func testAccCheckComputeInstanceMetadata( + instance *compute.Instance, + k string, v string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Metadata == nil { + return fmt.Errorf("no metadata") + } + + for _, item := range instance.Metadata.Items { + if k != item.Key { + continue + } + + if item.Value != nil && v == *item.Value { + return nil + } + + return fmt.Errorf("bad value for %s: %s", k, *item.Value) + } + + return fmt.Errorf("metadata not found: %s", k) + } +} + +func testAccCheckComputeInstanceAccessConfig(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instance.NetworkInterfaces { + if len(i.AccessConfigs) == 0 { + return fmt.Errorf("no access_config") + } + } + + return nil + } +} + +func testAccCheckComputeInstanceAccessConfigHasNatIP(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instance.NetworkInterfaces { + for _, c := range i.AccessConfigs { + if c.NatIP == "" { + return fmt.Errorf("no NAT IP") + } + } + } + + return nil + } +} + +func testAccCheckComputeInstanceAccessConfigHasPTR(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instance.NetworkInterfaces { + for _, c := range i.AccessConfigs { + if c.PublicPtrDomainName == "" { + return fmt.Errorf("no PTR Record") + } + } + } + + return nil + } +} + +func testAccCheckComputeInstanceDisk(instance *compute.Instance, source string, delete bool, boot bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Disks == nil { + return fmt.Errorf("no disks") + } + + for _, disk := range instance.Disks { + if strings.HasSuffix(disk.Source, "/"+source) && disk.AutoDelete == delete && disk.Boot == boot { + return nil + } + } + + return fmt.Errorf("Disk not found: %s", source) + } +} + +func testAccCheckComputeInstanceHasInstanceId(instance *compute.Instance, 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) + } + + remote := fmt.Sprintf("%d", instance.Id) + local := rs.Primary.Attributes["instance_id"] + + if remote != local { + return fmt.Errorf("Instance id stored does not match: remote has %#v but local has %#v", remote, + local) + } + + return nil + } +} + +func testAccCheckComputeInstanceBootDisk(instance *compute.Instance, source string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Disks == nil { + return fmt.Errorf("no disks") + } + + for _, disk := range instance.Disks { + if disk.Boot == true { + if strings.HasSuffix(disk.Source, source) { + return nil + } + } + } + + return fmt.Errorf("Boot disk not found with source %q", source) + } +} + +func testAccCheckComputeInstanceBootDiskType(instanceName string, diskType string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + // boot disk is named the same as the Instance + disk, err := config.clientCompute.Disks.Get(config.Project, "us-central1-a", instanceName).Do() + if err != nil { + return err + } + if strings.Contains(disk.Type, diskType) { + return nil + } + + return fmt.Errorf("Boot disk not found with type %q", diskType) + } +} + +func testAccCheckComputeInstanceScratchDisk(instance *compute.Instance, interfaces []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Disks == nil { + return fmt.Errorf("no disks") + } + + i := 0 + for _, disk := range instance.Disks { + if disk.Type == "SCRATCH" { + if i >= len(interfaces) { + return fmt.Errorf("Expected %d scratch disks, found more", len(interfaces)) + } + if disk.Interface != interfaces[i] { + return fmt.Errorf("Mismatched interface on scratch disk #%d, expected: %q, found: %q", + i, interfaces[i], disk.Interface) + } + i++ + } + } + + if i != len(interfaces) { + return fmt.Errorf("Expected %d scratch disks, found %d", len(interfaces), i) + } + + return nil + } +} + +func testAccCheckComputeInstanceDiskEncryptionKey(n string, instance *compute.Instance, bootDiskEncryptionKey string, diskNameToEncryptionKey map[string]*compute.CustomerEncryptionKey) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + for i, disk := range instance.Disks { + if disk.Boot { + attr := rs.Primary.Attributes["boot_disk.0.disk_encryption_key_sha256"] + if attr != bootDiskEncryptionKey { + return fmt.Errorf("Boot disk has wrong encryption key in state.\nExpected: %s\nActual: %s", bootDiskEncryptionKey, attr) + } + if disk.DiskEncryptionKey == nil && attr != "" { + return fmt.Errorf("Disk %d has mismatched encryption key.\nTF State: %+v\nGCP State: ", i, attr) + } + if disk.DiskEncryptionKey != nil && attr != disk.DiskEncryptionKey.Sha256 { + return fmt.Errorf("Disk %d has mismatched encryption key.\nTF State: %+v\nGCP State: %+v", + i, attr, disk.DiskEncryptionKey.Sha256) + } + } else { + if disk.DiskEncryptionKey != nil { + expectedKey := diskNameToEncryptionKey[GetResourceNameFromSelfLink(disk.Source)].Sha256 + if disk.DiskEncryptionKey.Sha256 != expectedKey { + return fmt.Errorf("Disk %d has unexpected encryption key in GCP.\nExpected: %s\nActual: %s", i, expectedKey, disk.DiskEncryptionKey.Sha256) + } + } + } + } + + numAttachedDisks, err := strconv.Atoi(rs.Primary.Attributes["attached_disk.#"]) + if err != nil { + return fmt.Errorf("Error converting value of attached_disk.#") + } + for i := 0; i < numAttachedDisks; i++ { + diskName := GetResourceNameFromSelfLink(rs.Primary.Attributes[fmt.Sprintf("attached_disk.%d.source", i)]) + encryptionKey := rs.Primary.Attributes[fmt.Sprintf("attached_disk.%d.disk_encryption_key_sha256", i)] + if key, ok := diskNameToEncryptionKey[diskName]; ok { + expectedEncryptionKey := key.Sha256 + if encryptionKey != expectedEncryptionKey { + return fmt.Errorf("Attached disk %d has unexpected encryption key in state.\nExpected: %s\nActual: %s", i, expectedEncryptionKey, encryptionKey) + } + } + } + return nil + } +} + +func testAccCheckComputeInstanceTag(instance *compute.Instance, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Tags == nil { + return fmt.Errorf("no tags") + } + + for _, k := range instance.Tags.Items { + if k == n { + return nil + } + } + + return fmt.Errorf("tag not found: %s", n) + } +} + +func testAccCheckComputeInstanceLabel(instance *compute.Instance, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Labels == nil { + return fmt.Errorf("no labels found on instance %s", instance.Name) + } + + v, ok := instance.Labels[key] + if !ok { + return fmt.Errorf("No label found with key %s on instance %s", key, instance.Name) + } + if v != value { + return fmt.Errorf("Expected value '%s' but found value '%s' for label '%s' on instance %s", value, v, key, instance.Name) + } + + return nil + } +} + +func testAccCheckComputeInstanceServiceAccount(instance *compute.Instance, scope string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if count := len(instance.ServiceAccounts); count != 1 { + return fmt.Errorf("Wrong number of ServiceAccounts: expected 1, got %d", count) + } + + for _, val := range instance.ServiceAccounts[0].Scopes { + if val == scope { + return nil + } + } + + return fmt.Errorf("Scope not found: %s", scope) + } +} + +func testAccCheckComputeInstanceHasSubnet(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instance.NetworkInterfaces { + if i.Subnetwork == "" { + return fmt.Errorf("no subnet") + } + } + + return nil + } +} + +func testAccCheckComputeInstanceHasAnyNetworkIP(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instance.NetworkInterfaces { + if i.NetworkIP == "" { + return fmt.Errorf("no network_ip") + } + } + + return nil + } +} + +func testAccCheckComputeInstanceHasNetworkIP(instance *compute.Instance, networkIP string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, i := range instance.NetworkInterfaces { + if i.NetworkIP != networkIP { + return fmt.Errorf("Wrong network_ip found: expected %v, got %v", networkIP, i.NetworkIP) + } + } + + return nil + } +} + +func testAccCheckComputeInstanceHasMultiNic(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(instance.NetworkInterfaces) < 2 { + return fmt.Errorf("only saw %d nics", len(instance.NetworkInterfaces)) + } + + return nil + } +} + +func testAccCheckComputeInstanceHasGuestAccelerator(instance *compute.Instance, acceleratorType string, acceleratorCount int64) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(instance.GuestAccelerators) != 1 { + return fmt.Errorf("Expected only one guest accelerator") + } + + if !strings.HasSuffix(instance.GuestAccelerators[0].AcceleratorType, acceleratorType) { + return fmt.Errorf("Wrong accelerator type: expected %v, got %v", acceleratorType, instance.GuestAccelerators[0].AcceleratorType) + } + + if instance.GuestAccelerators[0].AcceleratorCount != acceleratorCount { + return fmt.Errorf("Wrong accelerator acceleratorCount: expected %d, got %d", acceleratorCount, instance.GuestAccelerators[0].AcceleratorCount) + } + + return nil + } +} + +func testAccCheckComputeInstanceLacksGuestAccelerator(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(instance.GuestAccelerators) > 0 { + return fmt.Errorf("Expected no guest accelerators") + } + + return nil + } +} + +func testAccCheckComputeInstanceHasMinCpuPlatform(instance *compute.Instance, minCpuPlatform string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.MinCpuPlatform != minCpuPlatform { + return fmt.Errorf("Wrong minimum CPU platform: expected %s, got %s", minCpuPlatform, instance.MinCpuPlatform) + } + + return nil + } +} + +func testAccCheckComputeInstanceHasAliasIpRange(instance *compute.Instance, subnetworkRangeName, iPCidrRange string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, networkInterface := range instance.NetworkInterfaces { + for _, aliasIpRange := range networkInterface.AliasIpRanges { + if aliasIpRange.SubnetworkRangeName == subnetworkRangeName && (aliasIpRange.IpCidrRange == iPCidrRange || ipCidrRangeDiffSuppress("ip_cidr_range", aliasIpRange.IpCidrRange, iPCidrRange, nil)) { + return nil + } + } + } + + return fmt.Errorf("Alias ip range with name %s and cidr %s not present", subnetworkRangeName, iPCidrRange) + } +} + +func testAccCheckComputeInstanceHasAssignedNatIP(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_instance" { + continue + } + ip := rs.Primary.Attributes["network_interface.0.access_config.0.nat_ip"] + if ip == "" { + return fmt.Errorf("No assigned NatIP for instance %s", rs.Primary.Attributes["name"]) + } + } + return nil +} + +func testAccCheckComputeInstanceHasConfiguredDeletionProtection(instance *compute.Instance, configuredDeletionProtection bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.DeletionProtection != configuredDeletionProtection { + return fmt.Errorf("Wrong deletion protection flag: expected %t, got %t", configuredDeletionProtection, instance.DeletionProtection) + } + + return nil + } +} + +func testAccComputeInstance_basic(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + //deletion_protection = false is implicit in this config due to default value + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + baz = "qux" + } + + metadata { + startup-script = "echo Hello" + } + + labels { + my_key = "my_value" + my_other_key = "my_other_value" + } +} +`, instance) +} + +func testAccComputeInstance_basic2(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +} +`, instance) +} + +func testAccComputeInstance_basic3(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +} +`, instance) +} + +func testAccComputeInstance_basic4(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + + metadata { + foo = "bar" + } +} +`, instance) +} + +func testAccComputeInstance_basic5(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +} +`, instance) +} + +func testAccComputeInstance_basic_deletionProtectionFalse(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + deletion_protection = false + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } +} +`, instance) +} + +func testAccComputeInstance_basic_deletionProtectionTrue(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + deletion_protection = true + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } +} +`, instance) +} + +// Update zone to ForceNew, and change metadata k/v entirely +// Generates diff mismatch +func testAccComputeInstance_forceNewAndChangeMetadata(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-b" + tags = ["baz"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + access_config { } + } + + metadata { + qux = "true" + } +} +`, instance) +} + +// Update metadata, tags, and network_interface +func testAccComputeInstance_update(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["baz"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + access_config { } + } + + metadata { + bar = "baz" + startup-script = "echo Hello" + } + + labels { + only_me = "nothing_else" + } +} +`, instance) +} + +func testAccComputeInstance_ip(ip, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_address" "foo" { + name = "%s" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + access_config { + nat_ip = "${google_compute_address.foo.address}" + } + } + + metadata { + foo = "bar" + } +} +`, ip, instance) +} + +func testAccComputeInstance_PTRRecord(record, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + access_config { + public_ptr_domain_name = "test-record.%s.hashicorptest.com." + } + } + + metadata { + foo = "bar" + } +} +`, instance, record) +} + +func testAccComputeInstance_networkTier(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + access_config { + network_tier = "STANDARD" + } + } +} +`, instance) +} + +func testAccComputeInstance_disks_encryption(bootEncryptionKey string, diskNameToEncryptionKey map[string]*compute.CustomerEncryptionKey, instance string) string { + diskNames := []string{} + for k, _ := range diskNameToEncryptionKey { + diskNames = append(diskNames, k) + } + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + + disk_encryption_key { + raw_key = "%s" + } +} + +resource "google_compute_disk" "foobar2" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + + disk_encryption_key { + raw_key = "%s" + } +} + +resource "google_compute_disk" "foobar3" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + + disk_encryption_key { + raw_key = "%s" + } +} + +resource "google_compute_disk" "foobar4" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + disk_encryption_key_raw = "%s" + } + + attached_disk { + source = "${google_compute_disk.foobar.self_link}" + disk_encryption_key_raw = "%s" + } + + attached_disk { + source = "${google_compute_disk.foobar2.self_link}" + disk_encryption_key_raw = "%s" + } + + attached_disk { + source = "${google_compute_disk.foobar4.self_link}" + } + + attached_disk { + source = "${google_compute_disk.foobar3.self_link}" + disk_encryption_key_raw = "%s" + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +} +`, diskNames[0], diskNameToEncryptionKey[diskNames[0]].RawKey, + diskNames[1], diskNameToEncryptionKey[diskNames[1]].RawKey, + diskNames[2], diskNameToEncryptionKey[diskNames[2]].RawKey, + "instance-testd-"+acctest.RandString(10), + instance, bootEncryptionKey, + diskNameToEncryptionKey[diskNames[0]].RawKey, diskNameToEncryptionKey[diskNames[1]].RawKey, diskNameToEncryptionKey[diskNames[2]].RawKey) +} + +func testAccComputeInstance_attachedDisk(disk, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + attached_disk { + source = "${google_compute_disk.foobar.name}" + } + + network_interface { + network = "default" + } +} +`, disk, instance) +} + +func testAccComputeInstance_attachedDisk_sourceUrl(disk, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + attached_disk { + source = "${google_compute_disk.foobar.self_link}" + } + + network_interface { + network = "default" + } +} +`, disk, instance) +} + +func testAccComputeInstance_attachedDisk_modeRo(disk, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + attached_disk { + source = "${google_compute_disk.foobar.self_link}" + mode = "READ_ONLY" + } + + network_interface { + network = "default" + } +} +`, disk, instance) +} + +func testAccComputeInstance_addAttachedDisk(disk, disk2, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_disk" "foobar2" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + attached_disk { + source = "${google_compute_disk.foobar.name}" + } + + attached_disk { + source = "${google_compute_disk.foobar2.self_link}" + } + + network_interface { + network = "default" + } +} +`, disk, disk2, instance) +} + +func testAccComputeInstance_detachDisk(disk, disk2, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_disk" "foobar2" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + attached_disk { + source = "${google_compute_disk.foobar.name}" + } + + network_interface { + network = "default" + } +} +`, disk, disk2, instance) +} + +func testAccComputeInstance_updateAttachedDiskEncryptionKey(disk, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + disk_encryption_key { + raw_key = "c2Vjb25kNzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI" + } +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + attached_disk { + source = "${google_compute_disk.foobar.name}" + disk_encryption_key_raw = "c2Vjb25kNzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI" + } + + network_interface { + network = "default" + } +} +`, disk, instance) +} + +func testAccComputeInstance_bootDisk_source(disk, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + zone = "us-central1-a" + image = "${data.google_compute_image.my_image.self_link}" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + source = "${google_compute_disk.foobar.name}" + } + + network_interface { + network = "default" + } +} +`, disk, instance) +} + +func testAccComputeInstance_bootDisk_sourceUrl(disk, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + zone = "us-central1-a" + image = "${data.google_compute_image.my_image.self_link}" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + source = "${google_compute_disk.foobar.self_link}" + } + + network_interface { + network = "default" + } +} +`, disk, instance) +} + +func testAccComputeInstance_bootDisk_type(instance string, diskType string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + type = "%s" + } + } + + network_interface { + network = "default" + } +} +`, instance, diskType) +} + +func testAccComputeInstance_scratchDisk(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "scratch" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + scratch_disk { + interface = "NVME" + } + + scratch_disk { + interface = "SCSI" + } + + network_interface { + network = "default" + } + +} +`, instance) +} + +func testAccComputeInstance_service_account(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + service_account { + scopes = [ + "userinfo-email", + "compute-ro", + "storage-ro", + ] + } +} +`, instance) +} + +func testAccComputeInstance_scheduling(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + scheduling { + automatic_restart = false + } +} +`, instance) +} + +func testAccComputeInstance_subnet_auto(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_network" "inst-test-network" { + name = "inst-test-network-%s" + + auto_create_subnetworks = true +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "${google_compute_network.inst-test-network.name}" + access_config { } + } + +} +`, acctest.RandString(10), instance) +} + +func testAccComputeInstance_subnet_custom(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_network" "inst-test-network" { + name = "inst-test-network-%s" + + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "inst-test-subnetwork-%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + network = "${google_compute_network.inst-test-network.self_link}" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.self_link}" + access_config { } + } + +} +`, acctest.RandString(10), acctest.RandString(10), instance) +} + +func testAccComputeInstance_subnet_xpn(org, billingId, projectName, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_project" "host_project" { + name = "Test Project XPN Host" + project_id = "%s-host" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "host_project" { + project = "${google_project.host_project.project_id}" + service = "compute.googleapis.com" +} + +resource "google_compute_shared_vpc_host_project" "host_project" { + project = "${google_project_service.host_project.project}" +} + +resource "google_project" "service_project" { + name = "Test Project XPN Service" + project_id = "%s-service" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "service_project" { + project = "${google_project.service_project.project_id}" + service = "compute.googleapis.com" +} + +resource "google_compute_shared_vpc_service_project" "service_project" { + host_project = "${google_compute_shared_vpc_host_project.host_project.project}" + service_project = "${google_project_service.service_project.project}" +} + + +resource "google_compute_network" "inst-test-network" { + name = "inst-test-network-%s" + project = "${google_compute_shared_vpc_host_project.host_project.project}" + + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "inst-test-subnetwork-%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + network = "${google_compute_network.inst-test-network.self_link}" + project = "${google_compute_shared_vpc_host_project.host_project.project}" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + project = "${google_compute_shared_vpc_service_project.service_project.service_project}" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.name}" + subnetwork_project = "${google_compute_subnetwork.inst-test-subnetwork.project}" + access_config { } + } + +} +`, projectName, org, billingId, projectName, org, billingId, acctest.RandString(10), acctest.RandString(10), instance) +} + +func testAccComputeInstance_networkIPAuto(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_network" "inst-test-network" { + name = "inst-test-network-%s" +} +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "inst-test-subnetwork-%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + network = "${google_compute_network.inst-test-network.self_link}" +} +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.name}" + access_config { } + } + +} +`, acctest.RandString(10), acctest.RandString(10), instance) +} + +func testAccComputeInstance_network_ip_custom(instance, ipAddress string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_network" "inst-test-network" { + name = "inst-test-network-%s" +} +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "inst-test-subnetwork-%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + network = "${google_compute_network.inst-test-network.self_link}" +} +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.name}" + network_ip = "%s" + access_config { } + } + +} +`, acctest.RandString(10), acctest.RandString(10), instance, ipAddress) +} + +func testAccComputeInstance_private_image_family(disk, family, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + zone = "us-central1-a" + image = "${data.google_compute_image.my_image.self_link}" +} + +resource "google_compute_image" "foobar" { + name = "%s-1" + source_disk = "${google_compute_disk.foobar.self_link}" + family = "%s" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${google_compute_image.foobar.family}" + } + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } +} +`, disk, family, family, instance) +} + +func testAccComputeInstance_multiNic(instance, network, subnetwork string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.name}" + access_config { } + } + + network_interface { + network = "default" + } +} + +resource "google_compute_network" "inst-test-network" { + name = "%s" +} +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + network = "${google_compute_network.inst-test-network.self_link}" +} +`, instance, network, subnetwork) +} + +func testAccComputeInstance_guestAccelerator(instance string, count uint8) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-east1-d" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + scheduling { + # Instances with guest accelerators do not support live migration. + on_host_maintenance = "TERMINATE" + } + + guest_accelerator { + count = %d + type = "nvidia-tesla-k80" + } +}`, instance, count) +} + +func testAccComputeInstance_minCpuPlatform(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-east1-d" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + min_cpu_platform = "Intel Haswell" +}`, instance) +} + +func testAccComputeInstance_primaryAliasIpRange(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-east1-d" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + + alias_ip_range { + ip_cidr_range = "/24" + } + } +}`, instance) +} + +func testAccComputeInstance_secondaryAliasIpRange(network, subnet, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_network" "inst-test-network" { + name = "%s" +} +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-east1" + network = "${google_compute_network.inst-test-network.self_link}" + secondary_ip_range { + range_name = "inst-test-secondary" + ip_cidr_range = "172.16.0.0/20" + } + secondary_ip_range { + range_name = "inst-test-tertiary" + ip_cidr_range = "10.1.0.0/16" + } +} +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-east1-d" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.self_link}" + + alias_ip_range = [ + { + subnetwork_range_name = "${google_compute_subnetwork.inst-test-subnetwork.secondary_ip_range.0.range_name}" + ip_cidr_range = "172.16.0.0/24" + }, + { + subnetwork_range_name = "${google_compute_subnetwork.inst-test-subnetwork.secondary_ip_range.1.range_name}" + ip_cidr_range = "10.1.0.0/20" + } + ] + + } +}`, network, subnet, instance) +} + +func testAccComputeInstance_secondaryAliasIpRangeUpdate(network, subnet, instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_network" "inst-test-network" { + name = "%s" +} +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-east1" + network = "${google_compute_network.inst-test-network.self_link}" + secondary_ip_range { + range_name = "inst-test-secondary" + ip_cidr_range = "172.16.0.0/20" + } + secondary_ip_range { + range_name = "inst-test-tertiary" + ip_cidr_range = "10.1.0.0/16" + } +} +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-east1-d" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + subnetwork = "${google_compute_subnetwork.inst-test-subnetwork.self_link}" + alias_ip_range { + ip_cidr_range = "10.0.1.0/24" + } + } +}`, network, subnet, instance) +} + +// Set fields that require stopping the instance: machine_type, min_cpu_platform, and service_account +func testAccComputeInstance_stopInstanceToUpdate(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + min_cpu_platform = "Intel Broadwell" + service_account { + scopes = [ + "userinfo-email", + "compute-ro", + "storage-ro", + ] + } + + allow_stopping_for_update = true +} +`, instance) +} + +// Update fields that require stopping the instance: machine_type, min_cpu_platform, and service_account +func testAccComputeInstance_stopInstanceToUpdate2(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-2" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + min_cpu_platform = "Intel Skylake" + service_account { + scopes = [ + "userinfo-email", + "compute-ro", + ] + } + + allow_stopping_for_update = true +} +`, instance) +} + +// Remove fields that require stopping the instance: min_cpu_platform and service_account (machine_type is Required) +func testAccComputeInstance_stopInstanceToUpdate3(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-2" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + allow_stopping_for_update = true +} +`, instance) +} diff --git a/provider/terraform/tests/resource_compute_network_peering_test.go b/provider/terraform/tests/resource_compute_network_peering_test.go new file mode 100644 index 000000000000..14accde2baea --- /dev/null +++ b/provider/terraform/tests/resource_compute_network_peering_test.go @@ -0,0 +1,125 @@ +package google + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" + "strings" + "testing" +) + +func TestAccComputeNetworkPeering_basic(t *testing.T) { + t.Parallel() + + var peering compute.NetworkPeering + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccComputeNetworkPeeringDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeNetworkPeering_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkPeeringExist("google_compute_network_peering.foo", &peering), + testAccCheckComputeNetworkPeeringAutoCreateRoutes(true, &peering), + testAccCheckComputeNetworkPeeringExist("google_compute_network_peering.bar", &peering), + testAccCheckComputeNetworkPeeringAutoCreateRoutes(true, &peering), + ), + }, + }, + }) + +} + +func testAccComputeNetworkPeeringDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_network_peering" { + continue + } + + _, err := config.clientCompute.Networks.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Network peering still exists") + } + } + + return nil +} + +func testAccCheckComputeNetworkPeeringExist(n string, peering *compute.NetworkPeering) 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) + + parts := strings.Split(rs.Primary.ID, "/") + if len(parts) != 2 { + return fmt.Errorf("Invalid network peering identifier: %s", rs.Primary.ID) + } + + networkName, peeringName := parts[0], parts[1] + + network, err := config.clientCompute.Networks.Get(config.Project, networkName).Do() + if err != nil { + return err + } + + found := findPeeringFromNetwork(network, peeringName) + if found == nil { + return fmt.Errorf("Network peering '%s' not found in network '%s'", peeringName, network.Name) + } + *peering = *found + + return nil + } +} + +func testAccCheckComputeNetworkPeeringAutoCreateRoutes(v bool, peering *compute.NetworkPeering) resource.TestCheckFunc { + return func(s *terraform.State) error { + if peering.AutoCreateRoutes != v { + return fmt.Errorf("should AutoCreateRoutes set to %t", v) + } + + return nil + } +} + +func testAccComputeNetworkPeering_basic() string { + return fmt.Sprintf(` +resource "google_compute_network" "network1" { + name = "network-test-1-%s" + auto_create_subnetworks = false +} + +resource "google_compute_network" "network2" { + name = "network-test-2-%s" + auto_create_subnetworks = false +} + +resource "google_compute_network_peering" "foo" { + name = "peering-test-1-%s" + network = "${google_compute_network.network1.self_link}" + peer_network = "${google_compute_network.network2.self_link}" +} + +resource "google_compute_network_peering" "bar" { + name = "peering-test-2-%s" + auto_create_routes = true + network = "${google_compute_network.network2.self_link}" + peer_network = "${google_compute_network.network1.self_link}" +} +`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) +} diff --git a/provider/terraform/tests/resource_compute_network_test.go b/provider/terraform/tests/resource_compute_network_test.go new file mode 100644 index 000000000000..56ae6320c6c6 --- /dev/null +++ b/provider/terraform/tests/resource_compute_network_test.go @@ -0,0 +1,302 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeNetwork_basic(t *testing.T) { + t.Parallel() + + var network compute.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeNetwork_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkExists( + "google_compute_network.foobar", &network), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_network.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeNetwork_auto_subnet(t *testing.T) { + t.Parallel() + + var network compute.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeNetwork_auto_subnet(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkExists( + "google_compute_network.bar", &network), + testAccCheckComputeNetworkIsAutoSubnet( + "google_compute_network.bar", &network), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_network.bar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeNetwork_custom_subnet(t *testing.T) { + t.Parallel() + + var network compute.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeNetwork_custom_subnet(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkExists( + "google_compute_network.baz", &network), + testAccCheckComputeNetworkIsCustomSubnet( + "google_compute_network.baz", &network), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_network.baz", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeNetwork_routing_mode(t *testing.T) { + t.Parallel() + + var network compute.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeNetwork_routing_mode("GLOBAL"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkExists( + "google_compute_network.acc_network_routing_mode", &network), + testAccCheckComputeNetworkHasRoutingMode( + "google_compute_network.acc_network_routing_mode", &network, "GLOBAL"), + ), + }, + // Test updating the routing field (only updateable field). + resource.TestStep{ + Config: testAccComputeNetwork_routing_mode("REGIONAL"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkExists( + "google_compute_network.acc_network_routing_mode", &network), + testAccCheckComputeNetworkHasRoutingMode( + "google_compute_network.acc_network_routing_mode", &network, "REGIONAL"), + ), + }, + }, + }) +} + +func TestAccComputeNetwork_default_routing_mode(t *testing.T) { + t.Parallel() + + var network compute.Network + + expectedRoutingMode := "REGIONAL" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeNetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeNetwork_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeNetworkExists( + "google_compute_network.foobar", &network), + testAccCheckComputeNetworkHasRoutingMode( + "google_compute_network.foobar", &network, expectedRoutingMode), + ), + }, + }, + }) +} + +func testAccCheckComputeNetworkDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_network" { + continue + } + + _, err := config.clientCompute.Networks.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Network still exists") + } + } + + return nil +} + +func testAccCheckComputeNetworkExists(n string, network *compute.Network) 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.Networks.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Network not found") + } + + *network = *found + + return nil + } +} + +func testAccCheckComputeNetworkIsAutoSubnet(n string, network *compute.Network) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.Networks.Get( + config.Project, network.Name).Do() + if err != nil { + return err + } + + if !found.AutoCreateSubnetworks { + return fmt.Errorf("should have AutoCreateSubnetworks = true") + } + + if found.IPv4Range != "" { + return fmt.Errorf("should not have IPv4Range") + } + + return nil + } +} + +func testAccCheckComputeNetworkIsCustomSubnet(n string, network *compute.Network) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.Networks.Get( + config.Project, network.Name).Do() + if err != nil { + return err + } + + if found.AutoCreateSubnetworks { + return fmt.Errorf("should have AutoCreateSubnetworks = false") + } + + if found.IPv4Range != "" { + return fmt.Errorf("should not have IPv4Range") + } + + return nil + } +} + +func testAccCheckComputeNetworkHasRoutingMode(n string, network *compute.Network, routingMode string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.Attributes["routing_mode"] == "" { + return fmt.Errorf("Routing mode not found on resource") + } + + found, err := config.clientCompute.Networks.Get( + config.Project, network.Name).Do() + if err != nil { + return err + } + + foundRoutingMode := found.RoutingConfig.RoutingMode + + if routingMode != foundRoutingMode { + return fmt.Errorf("Expected routing mode %s to match actual routing mode %s", routingMode, foundRoutingMode) + } + + return nil + } +} + +func testAccComputeNetwork_basic() string { + return fmt.Sprintf(` +resource "google_compute_network" "foobar" { + name = "network-test-%s" +}`, acctest.RandString(10)) +} + +func testAccComputeNetwork_auto_subnet() string { + return fmt.Sprintf(` +resource "google_compute_network" "bar" { + name = "network-test-%s" + auto_create_subnetworks = true +}`, acctest.RandString(10)) +} + +func testAccComputeNetwork_custom_subnet() string { + return fmt.Sprintf(` +resource "google_compute_network" "baz" { + name = "network-test-%s" + auto_create_subnetworks = false +}`, acctest.RandString(10)) +} + +func testAccComputeNetwork_routing_mode(routingMode string) string { + return fmt.Sprintf(` +resource "google_compute_network" "acc_network_routing_mode" { + name = "network-test-%s" + routing_mode = "%s" +}`, acctest.RandString(10), routingMode) +} diff --git a/provider/terraform/tests/resource_compute_project_metadata_item_test.go b/provider/terraform/tests/resource_compute_project_metadata_item_test.go new file mode 100644 index 000000000000..a4334959ddad --- /dev/null +++ b/provider/terraform/tests/resource_compute_project_metadata_item_test.go @@ -0,0 +1,174 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeProjectMetadataItem_basic(t *testing.T) { + t.Parallel( + // Key must be unique to avoid concurrent tests interfering with each other + ) + + key := "myKey" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckProjectMetadataItemDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProjectMetadataItem_basicWithResourceName("foobar", key, "myValue"), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectMetadataItem_hasMetadata(key, "myValue"), + ), + }, + { + ResourceName: "google_compute_project_metadata_item.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeProjectMetadataItem_basicMultiple(t *testing.T) { + t.Parallel( + // Generate a config of two config keys + ) + + config := testAccProjectMetadataItem_basic("myKey", "myValue") + + testAccProjectMetadataItem_basic("myOtherKey", "myOtherValue") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckProjectMetadataItemDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectMetadataItem_hasMetadata("myKey", "myValue"), + testAccCheckProjectMetadataItem_hasMetadata("myOtherKey", "myOtherValue"), + ), + }, + }, + }) +} + +func TestAccComputeProjectMetadataItem_basicWithEmptyVal(t *testing.T) { + t.Parallel( + // Key must be unique to avoid concurrent tests interfering with each other + ) + + key := "myKey" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckProjectMetadataItemDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProjectMetadataItem_basicWithResourceName("foobar", key, ""), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectMetadataItem_hasMetadata(key, ""), + ), + }, + { + ResourceName: "google_compute_project_metadata_item.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeProjectMetadataItem_basicUpdate(t *testing.T) { + t.Parallel( + // Key must be unique to avoid concurrent tests interfering with each other + ) + + key := "myKey" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckProjectMetadataItemDestroy, + Steps: []resource.TestStep{ + { + Config: testAccProjectMetadataItem_basicWithResourceName("foobar", key, "myValue"), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectMetadataItem_hasMetadata(key, "myValue"), + ), + }, + { + Config: testAccProjectMetadataItem_basicWithResourceName("foobar", key, "myUpdatedValue"), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectMetadataItem_hasMetadata(key, "myUpdatedValue"), + ), + }, + }, + }) +} + +func testAccCheckProjectMetadataItem_hasMetadata(key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + project, err := config.clientCompute.Projects.Get(config.Project).Do() + if err != nil { + return err + } + + metadata := flattenMetadata(project.CommonInstanceMetadata) + + val, ok := metadata[key] + if !ok { + return fmt.Errorf("Unable to find a value for key '%s'", key) + } + if val != value { + return fmt.Errorf("Value for key '%s' does not match. Expected '%s' but found '%s'", key, value, val) + } + return nil + } +} + +func testAccCheckProjectMetadataItemDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + project, err := config.clientCompute.Projects.Get(config.Project).Do() + if err != nil { + return err + } + + metadata := flattenMetadata(project.CommonInstanceMetadata) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_project_metadata_item" { + continue + } + + _, ok := metadata[rs.Primary.ID] + if ok { + return fmt.Errorf("Metadata key/value '%s': '%s' still exist", rs.Primary.Attributes["key"], rs.Primary.Attributes["value"]) + } + } + + return nil +} + +func testAccProjectMetadataItem_basic(key, val string) string { + return testAccProjectMetadataItem_basicWithResourceName(acctest.RandString(10), key, val) +} + +func testAccProjectMetadataItem_basicWithResourceName(resourceName, key, val string) string { + return fmt.Sprintf(` +resource "google_compute_project_metadata_item" "%s" { + key = "%s" + value = "%s" +} +`, resourceName, key, val) +} diff --git a/provider/terraform/tests/resource_compute_project_metadata_test.go b/provider/terraform/tests/resource_compute_project_metadata_test.go new file mode 100644 index 000000000000..0ab371893131 --- /dev/null +++ b/provider/terraform/tests/resource_compute_project_metadata_test.go @@ -0,0 +1,302 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +// Add two key value pairs +func TestAccComputeProjectMetadata_basic(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + var project compute.Project + projectID := "terrafom-test-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeProjectMetadataDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeProject_basic0_metadata(projectID, pname, org, billingId), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeProjectExists( + "google_compute_project_metadata.fizzbuzz", projectID, &project), + testAccCheckComputeProjectMetadataContains(projectID, "banana", "orange"), + testAccCheckComputeProjectMetadataContains(projectID, "sofa", "darwinism"), + testAccCheckComputeProjectMetadataSize(projectID, 2), + ), + }, + }, + }) +} + +// Add three key value pairs, then replace one and modify a second +func TestAccComputeProjectMetadata_modify_1(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + var project compute.Project + projectID := "terrafom-test-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeProjectMetadataDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeProject_modify0_metadata(projectID, pname, org, billingId), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeProjectExists( + "google_compute_project_metadata.fizzbuzz", projectID, &project), + testAccCheckComputeProjectMetadataContains(projectID, "paper", "pen"), + testAccCheckComputeProjectMetadataContains(projectID, "genghis_khan", "french bread"), + testAccCheckComputeProjectMetadataContains(projectID, "happy", "smiling"), + testAccCheckComputeProjectMetadataSize(projectID, 3), + ), + }, + + resource.TestStep{ + Config: testAccComputeProject_modify1_metadata(projectID, pname, org, billingId), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeProjectExists( + "google_compute_project_metadata.fizzbuzz", projectID, &project), + testAccCheckComputeProjectMetadataContains(projectID, "paper", "pen"), + testAccCheckComputeProjectMetadataContains(projectID, "paris", "french bread"), + testAccCheckComputeProjectMetadataContains(projectID, "happy", "laughing"), + testAccCheckComputeProjectMetadataSize(projectID, 3), + ), + }, + }, + }) +} + +// Add two key value pairs, and replace both +func TestAccComputeProjectMetadata_modify_2(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + var project compute.Project + projectID := "terraform-test-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeProjectMetadataDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeProject_basic0_metadata(projectID, pname, org, billingId), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeProjectExists( + "google_compute_project_metadata.fizzbuzz", projectID, &project), + testAccCheckComputeProjectMetadataContains(projectID, "banana", "orange"), + testAccCheckComputeProjectMetadataContains(projectID, "sofa", "darwinism"), + testAccCheckComputeProjectMetadataSize(projectID, 2), + ), + }, + + resource.TestStep{ + Config: testAccComputeProject_basic1_metadata(projectID, pname, org, billingId), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeProjectExists( + "google_compute_project_metadata.fizzbuzz", projectID, &project), + testAccCheckComputeProjectMetadataContains(projectID, "kiwi", "papaya"), + testAccCheckComputeProjectMetadataContains(projectID, "finches", "darwinism"), + testAccCheckComputeProjectMetadataSize(projectID, 2), + ), + }, + }, + }) +} + +func testAccCheckComputeProjectMetadataDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_project_metadata" { + continue + } + + project, err := config.clientCompute.Projects.Get(rs.Primary.ID).Do() + if err == nil && len(project.CommonInstanceMetadata.Items) > 0 { + return fmt.Errorf("Error, metadata items still exist in %s", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckComputeProjectExists(n, projectID string, project *compute.Project) 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.Projects.Get(projectID).Do() + if err != nil { + return err + } + + if "common_metadata" != rs.Primary.ID { + return fmt.Errorf("Common metadata not found, found %s", rs.Primary.ID) + } + + *project = *found + + return nil + } +} + +func testAccCheckComputeProjectMetadataContains(projectID, key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + project, err := config.clientCompute.Projects.Get(projectID).Do() + if err != nil { + return fmt.Errorf("Error, failed to load project service for %s: %s", config.Project, err) + } + + for _, kv := range project.CommonInstanceMetadata.Items { + if kv.Key == key { + if kv.Value != nil && *kv.Value == value { + return nil + } else { + return fmt.Errorf("Error, key value mismatch, wanted (%s, %s), got (%s, %s)", + key, value, kv.Key, *kv.Value) + } + } + } + + return fmt.Errorf("Error, key %s not present in %s", key, project.SelfLink) + } +} + +func testAccCheckComputeProjectMetadataSize(projectID string, size int) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + project, err := config.clientCompute.Projects.Get(projectID).Do() + if err != nil { + return fmt.Errorf("Error, failed to load project service for %s: %s", config.Project, err) + } + + if size > len(project.CommonInstanceMetadata.Items) { + return fmt.Errorf("Error, expected at least %d metadata items, got %d", size, + len(project.CommonInstanceMetadata.Items)) + } + + return nil + } +} + +func testAccComputeProject_basic0_metadata(projectID, name, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "project" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "compute" { + project = "${google_project.project.project_id}" + service = "compute.googleapis.com" +} + +resource "google_compute_project_metadata" "fizzbuzz" { + project = "${google_project.project.project_id}" + metadata { + banana = "orange" + sofa = "darwinism" + } + depends_on = ["google_project_service.compute"] +}`, projectID, name, org, billing) +} + +func testAccComputeProject_basic1_metadata(projectID, name, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "project" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "compute" { + project = "${google_project.project.project_id}" + service = "compute.googleapis.com" +} + +resource "google_compute_project_metadata" "fizzbuzz" { + project = "${google_project.project.project_id}" + metadata { + kiwi = "papaya" + finches = "darwinism" + } + depends_on = ["google_project_service.compute"] +}`, projectID, name, org, billing) +} + +func testAccComputeProject_modify0_metadata(projectID, name, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "project" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "compute" { + project = "${google_project.project.project_id}" + service = "compute.googleapis.com" +} + +resource "google_compute_project_metadata" "fizzbuzz" { + project = "${google_project.project.project_id}" + metadata { + paper = "pen" + genghis_khan = "french bread" + happy = "smiling" + } + depends_on = ["google_project_service.compute"] +}`, projectID, name, org, billing) +} + +func testAccComputeProject_modify1_metadata(projectID, name, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "project" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "compute" { + project = "${google_project.project.project_id}" + service = "compute.googleapis.com" +} + +resource "google_compute_project_metadata" "fizzbuzz" { + project = "${google_project.project.project_id}" + metadata { + paper = "pen" + paris = "french bread" + happy = "laughing" + } + depends_on = ["google_project_service.compute"] +}`, projectID, name, org, billing) +} diff --git a/provider/terraform/tests/resource_compute_region_backend_service_test.go b/provider/terraform/tests/resource_compute_region_backend_service_test.go new file mode 100644 index 000000000000..bde3198c4995 --- /dev/null +++ b/provider/terraform/tests/resource_compute_region_backend_service_test.go @@ -0,0 +1,405 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeRegionBackendService_basic(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + extraCheckName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_basic(serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeRegionBackendService_basicModified( + serviceName, checkName, extraCheckName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + }, + }) +} + +func TestAccComputeRegionBackendService_withBackend(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withBackend( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.lipsum", &svc), + ), + }, + }, + }) + + if svc.TimeoutSec != 10 { + t.Errorf("Expected TimeoutSec == 10, got %d", svc.TimeoutSec) + } + if svc.Protocol != "TCP" { + t.Errorf("Expected Protocol to be TCP, got %q", svc.Protocol) + } + if len(svc.Backends) != 1 { + t.Errorf("Expected 1 backend, got %d", len(svc.Backends)) + } +} + +func TestAccComputeRegionBackendService_withBackendAndUpdate(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withBackend( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeRegionBackendService_withBackend( + serviceName, igName, itName, checkName, 20), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.lipsum", &svc), + ), + }, + }, + }) + + if svc.TimeoutSec != 20 { + t.Errorf("Expected TimeoutSec == 20, got %d", svc.TimeoutSec) + } + if svc.Protocol != "TCP" { + t.Errorf("Expected Protocol to be TCP, got %q", svc.Protocol) + } + if len(svc.Backends) != 1 { + t.Errorf("Expected 1 backend, got %d", len(svc.Backends)) + } +} + +func TestAccComputeRegionBackendService_withConnectionDraining(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withConnectionDraining(serviceName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.ConnectionDraining.DrainingTimeoutSec != 10 { + t.Errorf("Expected ConnectionDraining.DrainingTimeoutSec == 10, got %d", svc.ConnectionDraining.DrainingTimeoutSec) + } +} + +func TestAccComputeRegionBackendService_withConnectionDrainingAndUpdate(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withConnectionDraining(serviceName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeRegionBackendService_basic(serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.ConnectionDraining.DrainingTimeoutSec != 0 { + t.Errorf("Expected ConnectionDraining.DrainingTimeoutSec == 0, got %d", svc.ConnectionDraining.DrainingTimeoutSec) + } +} + +func TestAccComputeRegionBackendService_withSessionAffinity(t *testing.T) { + t.Parallel() + + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRegionBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRegionBackendService_withSessionAffinity( + serviceName, checkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRegionBackendServiceExists( + "google_compute_region_backend_service.foobar", &svc), + ), + }, + }, + }) + + if svc.SessionAffinity != "CLIENT_IP" { + t.Errorf("Expected Protocol to be CLIENT_IP, got %q", svc.SessionAffinity) + } +} + +func testAccCheckComputeRegionBackendServiceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_region_backend_service" { + continue + } + + _, err := config.clientCompute.RegionBackendServices.Get( + config.Project, config.Region, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Backend service still exists") + } + } + + return nil +} + +func testAccCheckComputeRegionBackendServiceExists(n string, svc *compute.BackendService) 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.RegionBackendServices.Get( + config.Project, config.Region, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Backend service not found") + } + + *svc = *found + + return nil + } +} + +func testAccComputeRegionBackendService_basic(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_health_check.zero.self_link}"] + region = "us-central1" +} + +resource "google_compute_health_check" "zero" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + port = "80" + } +} +`, serviceName, checkName) +} + +func testAccComputeRegionBackendService_basicModified(serviceName, checkOne, checkTwo string) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_health_check.one.self_link}"] + region = "us-central1" +} + +resource "google_compute_health_check" "zero" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + } +} + +resource "google_compute_health_check" "one" { + name = "%s" + check_interval_sec = 30 + timeout_sec = 30 + + tcp_health_check { + } +} +`, serviceName, checkOne, checkTwo) +} + +func testAccComputeRegionBackendService_withBackend( + serviceName, igName, itName, checkName string, timeout int64) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_region_backend_service" "lipsum" { + name = "%s" + description = "Hello World 1234" + protocol = "TCP" + region = "us-central1" + timeout_sec = %v + + backend { + group = "${google_compute_instance_group_manager.foobar.instance_group}" + } + + health_checks = ["${google_compute_health_check.default.self_link}"] +} + +resource "google_compute_instance_group_manager" "foobar" { + name = "%s" + instance_template = "${google_compute_instance_template.foobar.self_link}" + base_instance_name = "foobar" + zone = "us-central1-f" + target_size = 1 +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + + network_interface { + network = "default" + } + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } +} + +resource "google_compute_health_check" "default" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + + } +} +`, serviceName, timeout, igName, itName, checkName) +} + +func testAccComputeRegionBackendService_withSessionAffinity(serviceName, checkName string) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_health_check.zero.self_link}"] + region = "us-central1" + session_affinity = "CLIENT_IP" + +} + +resource "google_compute_health_check" "zero" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + port = "80" + } +} +`, serviceName, checkName) +} + +func testAccComputeRegionBackendService_withConnectionDraining(serviceName, checkName string, drainingTimeout int64) string { + return fmt.Sprintf(` +resource "google_compute_region_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_health_check.zero.self_link}"] + region = "us-central1" + connection_draining_timeout_sec = %v +} + +resource "google_compute_health_check" "zero" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + + tcp_health_check { + port = "80" + } +} +`, serviceName, drainingTimeout, checkName) +} 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 new file mode 100644 index 000000000000..1d51a9171400 --- /dev/null +++ b/provider/terraform/tests/resource_compute_region_instance_group_manager_test.go @@ -0,0 +1,1319 @@ +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" +) + +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)) + igm2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + ), + }, + }, + }) +} + +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)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + ), + }, + }, + }) + + 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)) + template2 := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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{ + 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"}, + }, + }, + }) +} + +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)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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{ + 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}), + ), + }, + }, + }) +} + +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{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + 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"), + ), + }, + }, + }) +} + +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{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + 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"), + ), + }, + 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)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + ), + }, + }, + }) +} + +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)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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, + }, + }, + }) +} + +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)) + hck := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + ), + }, + }, + }) +} + +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"} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + ), + }, + }, + }) +} + +func testAccCheckRegionInstanceGroupManagerDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + 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() + 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 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 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]) + } + } + + 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 + } +} + +func testAccRegionInstanceGroupManager_basic(template, target, igm1, igm2 string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" + } + + resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + region = "us-central1" + target_size = 2 + } + + resource "google_compute_region_instance_group_manager" "igm-no-tp" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-no-tp" + region = "us-central1" + target_size = 2 + } + `, template, target, igm1, igm2) +} + +func testAccRegionInstanceGroupManager_targetSizeZero(template, igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-basic" + region = "us-central1" + } + `, template, igm) +} + +func testAccRegionInstanceGroupManager_update(template, target, igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-update" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_target_pool" "igm-update" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" + } + + resource "google_compute_region_instance_group_manager" "igm-update" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update.self_link}" + target_pools = ["${google_compute_target_pool.igm-update.self_link}"] + base_instance_name = "igm-update" + region = "us-central1" + target_size = 2 + named_port { + name = "customhttp" + port = 8080 + } + }`, template, target, igm) +} + +// Change IGM's instance template and target size +func testAccRegionInstanceGroupManager_update2(template1, target1, target2, template2, igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-update" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_target_pool" "igm-update" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" + } + + resource "google_compute_target_pool" "igm-update2" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" + } + + resource "google_compute_instance_template" "igm-update2" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_region_instance_group_manager" "igm-update" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update2.self_link}" + target_pools = [ + "${google_compute_target_pool.igm-update.self_link}", + "${google_compute_target_pool.igm-update2.self_link}", + ] + base_instance_name = "igm-update" + region = "us-central1" + target_size = 3 + named_port { + name = "customhttp" + port = 8080 + } + named_port { + name = "customhttps" + port = 8443 + } + }`, template1, target1, target2, template2, igm) +} + +func testAccRegionInstanceGroupManager_updateLifecycle(tag, igm string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-update" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["%s"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } + } + + resource "google_compute_region_instance_group_manager" "igm-update" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update.self_link}" + base_instance_name = "igm-update" + region = "us-central1" + target_size = 2 + named_port { + name = "customhttp" + port = 8080 + } + }`, tag, igm) +} + +func testAccRegionInstanceGroupManager_separateRegions(igm1, igm2 string) string { + return fmt.Sprintf(` + data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" + } + + resource "google_compute_instance_template" "igm-basic" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + metadata { + foo = "bar" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + } + + resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-basic" + region = "us-central1" + target_size = 2 + } + + resource "google_compute_region_instance_group_manager" "igm-basic-2" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-basic-2" + region = "us-west1" + target_size = 2 + } + `, igm1, igm2) +} + +func testAccRegionInstanceGroupManager_autoHealingPolicies(template, target, igm, hck string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} + +resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + region = "us-central1" + target_size = 2 + auto_healing_policies { + health_check = "${google_compute_http_health_check.zero.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + `, template, target, igm, hck) +} +func testAccRegionInstanceGroupManager_versions(primaryTemplate string, canaryTemplate string, igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-primary" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_instance_template" "igm-canary" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test region instance group manager" + name = "%s" + base_instance_name = "igm-basic" + region = "us-central1" + target_size = 2 + + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-primary.self_link}" + } + + version { + name = "canary" + instance_template = "${google_compute_instance_template.igm-canary.self_link}" + target_size { + fixed = 1 + } + } +} + `, primaryTemplate, canaryTemplate, igm) +} + +func testAccRegionInstanceGroupManager_distributionPolicy(template, igm string, zones []string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + metadata { + foo = "bar" + } +} + +resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + base_instance_name = "igm-basic" + region = "us-central1" + target_size = 2 + distribution_policy_zones = ["%s"] +} + `, template, igm, strings.Join(zones, "\",\"")) +} + +func testAccRegionInstanceGroupManager_updateStrategy(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-update-strategy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-update-strategy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update-strategy.self_link}" + base_instance_name = "rigm-update-strategy" + region = "us-central1" + target_size = 2 + update_strategy = "NONE" + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccRegionInstanceGroupManager_rollingUpdatePolicy(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + base_instance_name = "igm-rolling-update-policy" + region = "us-central1" + target_size = 4 + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + update_strategy = "ROLLING_UPDATE" + + rolling_update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 2 + min_ready_sec = 20 + } + + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccRegionInstanceGroupManager_rollingUpdatePolicy2(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + base_instance_name = "igm-rolling-update-policy" + region = "us-central1" + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + target_size = 3 + update_strategy = "ROLLING_UPDATE" + + rolling_update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 0 + min_ready_sec = 10 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} diff --git a/provider/terraform/tests/resource_compute_route_test.go b/provider/terraform/tests/resource_compute_route_test.go new file mode 100644 index 000000000000..8cd1ce216110 --- /dev/null +++ b/provider/terraform/tests/resource_compute_route_test.go @@ -0,0 +1,204 @@ +package google + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +func TestAccComputeRoute_basic(t *testing.T) { + t.Parallel() + + var route compute.Route + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRoute_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRouteExists( + "google_compute_route.foobar", &route), + resource.TestMatchResourceAttr( + "google_compute_route.foobar", "description", regexp.MustCompile("This is a route")), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_route.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeRoute_defaultInternetGateway(t *testing.T) { + t.Parallel() + + var route compute.Route + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRoute_defaultInternetGateway(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRouteExists( + "google_compute_route.foobar", &route), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_route.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeRoute_hopInstance(t *testing.T) { + var route compute.Route + + instanceName := "tf" + acctest.RandString(10) + zone := "us-central1-b" + instanceNameRegexp := regexp.MustCompile(fmt.Sprintf("projects/(.+)/zones/%s/instances/%s$", zone, instanceName)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRoute_hopInstance(instanceName, zone), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeRouteExists( + "google_compute_route.foobar", &route), + resource.TestMatchResourceAttr("google_compute_route.foobar", "next_hop_instance", instanceNameRegexp), + resource.TestMatchResourceAttr("google_compute_route.foobar", "next_hop_instance", instanceNameRegexp), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_route.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckComputeRouteDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_route" { + continue + } + + _, err := config.clientCompute.Routes.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Route still exists") + } + } + + return nil +} + +func testAccCheckComputeRouteExists(n string, route *compute.Route) 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.Routes.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Route not found") + } + + *route = *found + + return nil + } +} + +func testAccComputeRoute_basic() string { + return fmt.Sprintf(` +resource "google_compute_network" "foobar" { + name = "route-test-%s" + auto_create_subnetworks = false + ipv4_range = "10.0.0.0/16" +} + +resource "google_compute_route" "foobar" { + name = "route-test-%s" + description = "This is a route" + dest_range = "15.0.0.0/24" + network = "${google_compute_network.foobar.name}" + next_hop_ip = "10.0.1.5" +}`, acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeRoute_defaultInternetGateway() string { + return fmt.Sprintf(` +resource "google_compute_route" "foobar" { + name = "route-test-%s" + dest_range = "0.0.0.0/0" + network = "default" + next_hop_gateway = "default-internet-gateway" + priority = 100 +}`, acctest.RandString(10)) +} + +func testAccComputeRoute_hopInstance(instanceName, zone string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foo" { + name = "%s" + machine_type = "n1-standard-1" + zone = "%s" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } +} + +resource "google_compute_route" "foobar" { + name = "route-test-%s" + dest_range = "0.0.0.0/0" + network = "default" + next_hop_instance = "${google_compute_instance.foo.name}" + next_hop_instance_zone = "${google_compute_instance.foo.zone}" + priority = 100 +}`, instanceName, zone, acctest.RandString(10)) +} diff --git a/provider/terraform/tests/resource_compute_router_interface_test.go b/provider/terraform/tests/resource_compute_router_interface_test.go new file mode 100644 index 000000000000..ca383b213de2 --- /dev/null +++ b/provider/terraform/tests/resource_compute_router_interface_test.go @@ -0,0 +1,289 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeRouterInterface_basic(t *testing.T) { + t.Parallel() + + testId := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRouterInterfaceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRouterInterfaceBasic(testId), + Check: testAccCheckComputeRouterInterfaceExists( + "google_compute_router_interface.foobar"), + }, + resource.TestStep{ + ResourceName: "google_compute_router_interface.foobar", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeRouterInterfaceKeepRouter(testId), + Check: testAccCheckComputeRouterInterfaceDelete( + "google_compute_router_interface.foobar"), + }, + }, + }) +} + +func testAccCheckComputeRouterInterfaceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + routersService := config.clientCompute.Routers + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_router" { + continue + } + + project, err := getTestProject(rs.Primary, config) + if err != nil { + return err + } + + region, err := getTestRegion(rs.Primary, config) + if err != nil { + return err + } + + routerName := rs.Primary.Attributes["router"] + + _, err = routersService.Get(project, region, routerName).Do() + + if err == nil { + return fmt.Errorf("Error, Router %s in region %s still exists", + routerName, region) + } + } + + return nil +} + +func testAccCheckComputeRouterInterfaceDelete(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + routersService := config.clientCompute.Routers + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_router_interface" { + continue + } + + project, err := getTestProject(rs.Primary, config) + if err != nil { + return err + } + + region, err := getTestRegion(rs.Primary, config) + if err != nil { + return err + } + + name := rs.Primary.Attributes["name"] + routerName := rs.Primary.Attributes["router"] + + router, err := routersService.Get(project, region, routerName).Do() + + if err != nil { + return fmt.Errorf("Error Reading Router %s: %s", routerName, err) + } + + ifaces := router.Interfaces + for _, iface := range ifaces { + + if iface.Name == name { + return fmt.Errorf("Interface %s still exists on router %s/%s", name, region, router.Name) + } + } + } + + return nil + } +} + +func testAccCheckComputeRouterInterfaceExists(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) + + project, err := getTestProject(rs.Primary, config) + if err != nil { + return err + } + + region, err := getTestRegion(rs.Primary, config) + if err != nil { + return err + } + + name := rs.Primary.Attributes["name"] + routerName := rs.Primary.Attributes["router"] + + routersService := config.clientCompute.Routers + router, err := routersService.Get(project, region, routerName).Do() + + if err != nil { + return fmt.Errorf("Error Reading Router %s: %s", routerName, err) + } + + for _, iface := range router.Interfaces { + + if iface.Name == name { + return nil + } + } + + return fmt.Errorf("Interface %s not found for router %s", name, router.Name) + } +} + +func testAccComputeRouterInterfaceBasic(testId string) string { + return fmt.Sprintf(` + resource "google_compute_network" "foobar" { + name = "router-interface-test-%s" + } + resource "google_compute_subnetwork" "foobar" { + name = "router-interface-test-subnetwork-%s" + network = "${google_compute_network.foobar.self_link}" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + } + resource "google_compute_address" "foobar" { + name = "router-interface-test-%s" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_vpn_gateway" "foobar" { + name = "router-interface-test-%s" + network = "${google_compute_network.foobar.self_link}" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_forwarding_rule" "foobar_esp" { + name = "router-interface-test-%s-1" + region = "${google_compute_vpn_gateway.foobar.region}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp500" { + name = "router-interface-test-%s-2" + region = "${google_compute_forwarding_rule.foobar_esp.region}" + ip_protocol = "UDP" + port_range = "500-500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp4500" { + name = "router-interface-test-%s-3" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + ip_protocol = "UDP" + port_range = "4500-4500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_router" "foobar"{ + name = "router-interface-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + network = "${google_compute_network.foobar.self_link}" + bgp { + asn = 64514 + } + } + resource "google_compute_vpn_tunnel" "foobar" { + name = "router-interface-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp4500.region}" + target_vpn_gateway = "${google_compute_vpn_gateway.foobar.self_link}" + shared_secret = "unguessable" + peer_ip = "8.8.8.8" + router = "${google_compute_router.foobar.name}" + } + resource "google_compute_router_interface" "foobar" { + name = "router-interface-test-%s" + router = "${google_compute_router.foobar.name}" + region = "${google_compute_router.foobar.region}" + ip_range = "169.254.3.1/30" + vpn_tunnel = "${google_compute_vpn_tunnel.foobar.name}" + } + `, testId, testId, testId, testId, testId, testId, testId, testId, testId, testId) +} + +func testAccComputeRouterInterfaceKeepRouter(testId string) string { + return fmt.Sprintf(` + resource "google_compute_network" "foobar" { + name = "router-interface-test-%s" + } + resource "google_compute_subnetwork" "foobar" { + name = "router-interface-test-subnetwork-%s" + network = "${google_compute_network.foobar.self_link}" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + } + resource "google_compute_address" "foobar" { + name = "router-interface-test-%s" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_vpn_gateway" "foobar" { + name = "router-interface-test-%s" + network = "${google_compute_network.foobar.self_link}" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_forwarding_rule" "foobar_esp" { + name = "router-interface-test-%s-1" + region = "${google_compute_vpn_gateway.foobar.region}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp500" { + name = "router-interface-test-%s-2" + region = "${google_compute_forwarding_rule.foobar_esp.region}" + ip_protocol = "UDP" + port_range = "500-500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp4500" { + name = "router-interface-test-%s-3" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + ip_protocol = "UDP" + port_range = "4500-4500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_router" "foobar"{ + name = "router-interface-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + network = "${google_compute_network.foobar.self_link}" + bgp { + asn = 64514 + } + } + resource "google_compute_vpn_tunnel" "foobar" { + name = "router-interface-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp4500.region}" + target_vpn_gateway = "${google_compute_vpn_gateway.foobar.self_link}" + shared_secret = "unguessable" + peer_ip = "8.8.8.8" + router = "${google_compute_router.foobar.name}" + } + `, testId, testId, testId, testId, testId, testId, testId, testId, testId) +} diff --git a/provider/terraform/tests/resource_compute_router_peer_test.go b/provider/terraform/tests/resource_compute_router_peer_test.go new file mode 100644 index 000000000000..04c89f6da8ec --- /dev/null +++ b/provider/terraform/tests/resource_compute_router_peer_test.go @@ -0,0 +1,305 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeRouterPeer_basic(t *testing.T) { + t.Parallel() + + testId := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeRouterPeerDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeRouterPeerBasic(testId), + Check: testAccCheckComputeRouterPeerExists( + "google_compute_router_peer.foobar"), + }, + resource.TestStep{ + ResourceName: "google_compute_router_peer.foobar", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeRouterPeerKeepRouter(testId), + Check: testAccCheckComputeRouterPeerDelete( + "google_compute_router_peer.foobar"), + }, + }, + }) +} + +func testAccCheckComputeRouterPeerDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + routersService := config.clientCompute.Routers + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_router" { + continue + } + + project, err := getTestProject(rs.Primary, config) + if err != nil { + return err + } + + region, err := getTestRegion(rs.Primary, config) + if err != nil { + return err + } + + routerName := rs.Primary.Attributes["router"] + + _, err = routersService.Get(project, region, routerName).Do() + + if err == nil { + return fmt.Errorf("Error, Router %s in region %s still exists", + routerName, region) + } + } + + return nil +} + +func testAccCheckComputeRouterPeerDelete(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + routersService := config.clientCompute.Routers + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_router_peer" { + continue + } + + project, err := getTestProject(rs.Primary, config) + if err != nil { + return err + } + + region, err := getTestRegion(rs.Primary, config) + if err != nil { + return err + } + + name := rs.Primary.Attributes["name"] + routerName := rs.Primary.Attributes["router"] + + router, err := routersService.Get(project, region, routerName).Do() + + if err != nil { + return fmt.Errorf("Error Reading Router %s: %s", routerName, err) + } + + peers := router.BgpPeers + for _, peer := range peers { + + if peer.Name == name { + return fmt.Errorf("Peer %s still exists on router %s/%s", name, region, router.Name) + } + } + } + + return nil + } +} + +func testAccCheckComputeRouterPeerExists(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) + + project, err := getTestProject(rs.Primary, config) + if err != nil { + return err + } + + region, err := getTestRegion(rs.Primary, config) + if err != nil { + return err + } + + name := rs.Primary.Attributes["name"] + routerName := rs.Primary.Attributes["router"] + + routersService := config.clientCompute.Routers + router, err := routersService.Get(project, region, routerName).Do() + + if err != nil { + return fmt.Errorf("Error Reading Router %s: %s", routerName, err) + } + + for _, peer := range router.BgpPeers { + + if peer.Name == name { + return nil + } + } + + return fmt.Errorf("Peer %s not found for router %s", name, router.Name) + } +} + +func testAccComputeRouterPeerBasic(testId string) string { + return fmt.Sprintf(` + resource "google_compute_network" "foobar" { + name = "router-peer-test-%s" + } + resource "google_compute_subnetwork" "foobar" { + name = "router-peer-test-subnetwork-%s" + network = "${google_compute_network.foobar.self_link}" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + } + resource "google_compute_address" "foobar" { + name = "router-peer-test-%s" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_vpn_gateway" "foobar" { + name = "router-peer-test-%s" + network = "${google_compute_network.foobar.self_link}" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_forwarding_rule" "foobar_esp" { + name = "router-peer-test-%s-1" + region = "${google_compute_vpn_gateway.foobar.region}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp500" { + name = "router-peer-test-%s-2" + region = "${google_compute_forwarding_rule.foobar_esp.region}" + ip_protocol = "UDP" + port_range = "500-500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp4500" { + name = "router-peer-test-%s-3" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + ip_protocol = "UDP" + port_range = "4500-4500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_router" "foobar"{ + name = "router-peer-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + network = "${google_compute_network.foobar.self_link}" + bgp { + asn = 64514 + } + } + resource "google_compute_vpn_tunnel" "foobar" { + name = "router-peer-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp4500.region}" + target_vpn_gateway = "${google_compute_vpn_gateway.foobar.self_link}" + shared_secret = "unguessable" + peer_ip = "8.8.8.8" + router = "${google_compute_router.foobar.name}" + } + resource "google_compute_router_interface" "foobar" { + name = "router-peer-test-%s" + router = "${google_compute_router.foobar.name}" + region = "${google_compute_router.foobar.region}" + ip_range = "169.254.3.1/30" + vpn_tunnel = "${google_compute_vpn_tunnel.foobar.name}" + } + resource "google_compute_router_peer" "foobar" { + name = "router-peer-test-%s" + router = "${google_compute_router.foobar.name}" + region = "${google_compute_router.foobar.region}" + peer_ip_address = "169.254.3.2" + peer_asn = 65515 + advertised_route_priority = 100 + interface = "${google_compute_router_interface.foobar.name}" + } + `, testId, testId, testId, testId, testId, testId, testId, testId, testId, testId, testId) +} + +func testAccComputeRouterPeerKeepRouter(testId string) string { + return fmt.Sprintf(` + resource "google_compute_network" "foobar" { + name = "router-peer-test-%s" + } + resource "google_compute_subnetwork" "foobar" { + name = "router-peer-test-subnetwork-%s" + network = "${google_compute_network.foobar.self_link}" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + } + resource "google_compute_address" "foobar" { + name = "router-peer-test-%s" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_vpn_gateway" "foobar" { + name = "router-peer-test-%s" + network = "${google_compute_network.foobar.self_link}" + region = "${google_compute_subnetwork.foobar.region}" + } + resource "google_compute_forwarding_rule" "foobar_esp" { + name = "router-peer-test-%s-1" + region = "${google_compute_vpn_gateway.foobar.region}" + ip_protocol = "ESP" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp500" { + name = "router-peer-test-%s-2" + region = "${google_compute_forwarding_rule.foobar_esp.region}" + ip_protocol = "UDP" + port_range = "500-500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_forwarding_rule" "foobar_udp4500" { + name = "router-peer-test-%s-3" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + ip_protocol = "UDP" + port_range = "4500-4500" + ip_address = "${google_compute_address.foobar.address}" + target = "${google_compute_vpn_gateway.foobar.self_link}" + } + resource "google_compute_router" "foobar"{ + name = "router-peer-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp500.region}" + network = "${google_compute_network.foobar.self_link}" + bgp { + asn = 64514 + } + } + resource "google_compute_vpn_tunnel" "foobar" { + name = "router-peer-test-%s" + region = "${google_compute_forwarding_rule.foobar_udp4500.region}" + target_vpn_gateway = "${google_compute_vpn_gateway.foobar.self_link}" + shared_secret = "unguessable" + peer_ip = "8.8.8.8" + router = "${google_compute_router.foobar.name}" + } + resource "google_compute_router_interface" "foobar" { + name = "router-peer-test-%s" + router = "${google_compute_router.foobar.name}" + region = "${google_compute_router.foobar.region}" + ip_range = "169.254.3.1/30" + vpn_tunnel = "${google_compute_vpn_tunnel.foobar.name}" + } + `, testId, testId, testId, testId, testId, testId, testId, testId, testId, testId) +} diff --git a/provider/terraform/tests/resource_compute_security_policy_test.go b/provider/terraform/tests/resource_compute_security_policy_test.go new file mode 100644 index 000000000000..e1d6834e2ba3 --- /dev/null +++ b/provider/terraform/tests/resource_compute_security_policy_test.go @@ -0,0 +1,202 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeSecurityPolicy_basic(t *testing.T) { + t.Parallel() + + spName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSecurityPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSecurityPolicy_basic(spName), + }, + resource.TestStep{ + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSecurityPolicy_withRule(t *testing.T) { + t.Parallel() + + spName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSecurityPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSecurityPolicy_withRule(spName), + }, + resource.TestStep{ + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSecurityPolicy_update(t *testing.T) { + t.Parallel() + + spName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSecurityPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSecurityPolicy_withRule(spName), + }, + resource.TestStep{ + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + + resource.TestStep{ + Config: testAccComputeSecurityPolicy_update(spName), + }, + resource.TestStep{ + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + + resource.TestStep{ + Config: testAccComputeSecurityPolicy_withRule(spName), + }, + resource.TestStep{ + ResourceName: "google_compute_security_policy.policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckComputeSecurityPolicyDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_security_policy" { + continue + } + + pol := rs.Primary.ID + + _, err := config.clientComputeBeta.SecurityPolicies.Get(config.Project, pol).Do() + if err == nil { + return fmt.Errorf("Security policy %q still exists", pol) + } + } + + return nil +} + +func testAccComputeSecurityPolicy_basic(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "basic security policy" +} +`, spName) +} + +func testAccComputeSecurityPolicy_withRule(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + + rule { + action = "allow" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule" + } + + rule { + action = "allow" + priority = "2000" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["10.0.0.0/24"] + } + } + preview = true + } +} +`, spName) +} + +func testAccComputeSecurityPolicy_update(spName string) string { + return fmt.Sprintf(` +resource "google_compute_security_policy" "policy" { + name = "%s" + description = "updated description" + + // keep this + rule { + action = "allow" + priority = "2147483647" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["*"] + } + } + description = "default rule" + } + + // add this + rule { + action = "deny(403)" + priority = "1000" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["10.0.1.0/24"] + } + } + } + + // update this + rule { + action = "allow" + priority = "2000" + match { + versioned_expr = "SRC_IPS_V1" + config { + src_ip_ranges = ["10.0.0.0/24"] + } + } + description = "updated description" + preview = false + } +} +`, spName) +} diff --git a/provider/terraform/tests/resource_compute_shared_vpc_test.go b/provider/terraform/tests/resource_compute_shared_vpc_test.go new file mode 100644 index 000000000000..6c31dcdd6943 --- /dev/null +++ b/provider/terraform/tests/resource_compute_shared_vpc_test.go @@ -0,0 +1,160 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeSharedVpc_basic(t *testing.T) { + org := getTestOrgFromEnv(t) + billingId := getTestBillingAccountFromEnv(t) + + hostProject := "xpn-host-" + acctest.RandString(10) + serviceProject := "xpn-service-" + acctest.RandString(10) + + hostProjectResourceName := "google_compute_shared_vpc_host_project.host" + serviceProjectResourceName := "google_compute_shared_vpc_service_project.service" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSharedVpc_basic(hostProject, serviceProject, org, billingId), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSharedVpcHostProject(hostProject, true), + testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject, true), + ), + }, + // Test import. + resource.TestStep{ + ResourceName: hostProjectResourceName, + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: serviceProjectResourceName, + ImportState: true, + ImportStateVerify: true, + }, + // Use a separate TestStep rather than a CheckDestroy because we need the project to still exist. + resource.TestStep{ + Config: testAccComputeSharedVpc_disabled(hostProject, serviceProject, org, billingId), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSharedVpcHostProject(hostProject, false), + testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject, false), + ), + }, + }, + }) +} + +func testAccCheckComputeSharedVpcHostProject(hostProject string, enabled bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.Projects.Get(hostProject).Do() + if err != nil { + return fmt.Errorf("Error reading project %s: %s", hostProject, err) + } + + if found.Name != hostProject { + return fmt.Errorf("Project %s not found", hostProject) + } + + if enabled != (found.XpnProjectStatus == "HOST") { + return fmt.Errorf("Project %q shared VPC status was not expected, got %q", hostProject, found.XpnProjectStatus) + } + + return nil + } +} + +func testAccCheckComputeSharedVpcServiceProject(hostProject, serviceProject string, enabled bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + serviceHostProject, err := config.clientCompute.Projects.GetXpnHost(serviceProject).Do() + if err != nil { + if enabled { + return fmt.Errorf("Expected service project to be enabled.") + } + return nil + } + + if enabled != (serviceHostProject.Name == hostProject) { + return fmt.Errorf("Wrong host project for the given service project. Expected '%s', got '%s'", hostProject, serviceHostProject.Name) + } + + return nil + } +} + +func testAccComputeSharedVpc_basic(hostProject, serviceProject, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "host" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project" "service" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "host" { + project = "${google_project.host.project_id}" + service = "compute.googleapis.com" +} + +resource "google_project_service" "service" { + project = "${google_project.service.project_id}" + service = "compute.googleapis.com" +} + +resource "google_compute_shared_vpc_host_project" "host" { + project = "${google_project.host.project_id}" + depends_on = ["google_project_service.host"] +} + +resource "google_compute_shared_vpc_service_project" "service" { + host_project = "${google_project.host.project_id}" + service_project = "${google_project.service.project_id}" + depends_on = ["google_compute_shared_vpc_host_project.host", "google_project_service.service"] +}`, hostProject, hostProject, org, billing, serviceProject, serviceProject, org, billing) +} + +func testAccComputeSharedVpc_disabled(hostProject, serviceProject, org, billing string) string { + return fmt.Sprintf(` +resource "google_project" "host" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project" "service" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "host" { + project = "${google_project.host.project_id}" + service = "compute.googleapis.com" +} + +resource "google_project_service" "service" { + project = "${google_project.service.project_id}" + service = "compute.googleapis.com" +} +`, hostProject, hostProject, org, billing, serviceProject, serviceProject, org, billing) +} diff --git a/provider/terraform/tests/resource_compute_snapshot_test.go b/provider/terraform/tests/resource_compute_snapshot_test.go new file mode 100644 index 000000000000..9b4d4c89a716 --- /dev/null +++ b/provider/terraform/tests/resource_compute_snapshot_test.go @@ -0,0 +1,260 @@ +package google + +import ( + "fmt" + "testing" + + "reflect" + "strings" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" +) + +func TestAccComputeSnapshot_basic(t *testing.T) { + t.Parallel() + + snapshotName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var snapshot compute.Snapshot + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSnapshotDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSnapshot_basic(snapshotName, diskName, "my-value"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSnapshotExists( + "google_compute_snapshot.foobar", &snapshot), + ), + }, + }, + }) +} + +func TestAccComputeSnapshot_update(t *testing.T) { + t.Parallel() + + snapshotName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var snapshot compute.Snapshot + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSnapshotDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSnapshot_basic(snapshotName, diskName, "my-value"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSnapshotExists( + "google_compute_snapshot.foobar", &snapshot), + ), + }, + resource.TestStep{ + Config: testAccComputeSnapshot_basic(snapshotName, diskName, "my-updated-value"), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSnapshotExists( + "google_compute_snapshot.foobar", &snapshot), + ), + }, + }, + }) +} + +func TestAccComputeSnapshot_encryption(t *testing.T) { + t.Parallel() + + snapshotName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var snapshot compute.Snapshot + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSnapshotDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSnapshot_encryption(snapshotName, diskName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSnapshotExists( + "google_compute_snapshot.foobar", &snapshot), + ), + }, + }, + }) +} + +func testAccCheckComputeSnapshotDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_snapshot" { + continue + } + + _, err := config.clientCompute.Snapshots.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { + return nil + } else if ok { + return fmt.Errorf("Error while requesting Google Cloud Plateform: http code error : %d, http message error: %s", gerr.Code, gerr.Message) + } + return fmt.Errorf("Error while requesting Google Cloud Plateform") + } + return fmt.Errorf("Snapshot still exists") + } + + return nil +} + +func testAccCheckComputeSnapshotExists(n string, snapshot *compute.Snapshot) 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.Snapshots.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Snapshot %s not found", n) + } + + attr := rs.Primary.Attributes["snapshot_encryption_key_sha256"] + if found.SnapshotEncryptionKey != nil && found.SnapshotEncryptionKey.Sha256 != attr { + return fmt.Errorf("Snapshot %s has mismatched encryption key (Sha256).\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SnapshotEncryptionKey.Sha256) + } else if found.SnapshotEncryptionKey == nil && attr != "" { + return fmt.Errorf("Snapshot %s has mismatched encryption key.\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SnapshotEncryptionKey) + } + + attr = rs.Primary.Attributes["source_disk_encryption_key_sha256"] + if found.SourceDiskEncryptionKey != nil && found.SourceDiskEncryptionKey.Sha256 != attr { + return fmt.Errorf("Snapshot %s has mismatched source disk encryption key (Sha256).\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SourceDiskEncryptionKey.Sha256) + } else if found.SourceDiskEncryptionKey == nil && attr != "" { + return fmt.Errorf("Snapshot %s has mismatched source disk encryption key.\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SourceDiskEncryptionKey) + } + + attr = rs.Primary.Attributes["source_disk_link"] + if found.SourceDisk != attr { + return fmt.Errorf("Snapshot %s has mismatched source disk link.\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SourceDisk) + } + + foundDisk, errDisk := config.clientCompute.Disks.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.Attributes["source_disk"]).Do() + if errDisk != nil { + return errDisk + } + if foundDisk.SelfLink != attr { + return fmt.Errorf("Snapshot %s has mismatched source disk\nTF State: %+v.\nGCP State: %+v", + n, attr, foundDisk.SelfLink) + } + + attr = rs.Primary.Attributes["self_link"] + if found.SelfLink != attr { + return fmt.Errorf("Snapshot %s has mismatched self link.\nTF State: %+v.\nGCP State: %+v", + n, attr, found.SelfLink) + } + + // We should have a map + attr, ok = rs.Primary.Attributes["labels.%"] + if !ok { + return fmt.Errorf("Snapshot %s has no labels map in attributes", n) + } + // Parse out our map + attrMap := make(map[string]string) + for k, v := range rs.Primary.Attributes { + if !strings.HasPrefix(k, "labels.") || k == "labels.%" { + continue + } + key := k[len("labels."):] + attrMap[key] = v + } + if (len(attrMap) != 0 || len(found.Labels) != 0) && !reflect.DeepEqual(attrMap, found.Labels) { + return fmt.Errorf("Snapshot %s has mismatched labels.\nTF State: %+v\nGCP State: %+v", + n, attrMap, found.Labels) + } + + attr = rs.Primary.Attributes["label_fingerprint"] + if found.LabelFingerprint != attr { + return fmt.Errorf("Snapshot %s has mismatched label fingerprint\nTF State: %+v.\nGCP State: %+v", + n, attr, found.LabelFingerprint) + } + + *snapshot = *found + + return nil + } +} + +func testAccComputeSnapshot_basic(snapshotName, diskName, labelValue string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + +resource "google_compute_snapshot" "foobar" { + name = "%s" + source_disk = "${google_compute_disk.foobar.name}" + zone = "us-central1-a" + labels = { + my_label = "%s" + } +}`, diskName, snapshotName, labelValue) +} + +func testAccComputeSnapshot_encryption(snapshotName string, diskName string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + image = "${data.google_compute_image.my_image.self_link}" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + disk_encryption_key { + raw_key = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" + } +} +resource "google_compute_snapshot" "foobar" { + name = "%s" + source_disk = "${google_compute_disk.foobar.name}" + zone = "us-central1-a" + source_disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" + snapshot_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" +}`, diskName, snapshotName) +} diff --git a/provider/terraform/tests/resource_compute_ssl_policy_test.go b/provider/terraform/tests/resource_compute_ssl_policy_test.go new file mode 100644 index 000000000000..66843746c92f --- /dev/null +++ b/provider/terraform/tests/resource_compute_ssl_policy_test.go @@ -0,0 +1,402 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + compute "google.golang.org/api/compute/v1" +) + +func TestAccComputeSslPolicy_basic(t *testing.T) { + t.Parallel() + + var sslPolicy compute.SslPolicy + sslPolicyName := fmt.Sprintf("test-ssl-policy-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSslPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSslPolicyBasic(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.basic", &sslPolicy), + // test attribute default values + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.basic", "profile", "COMPATIBLE"), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.basic", "min_tls_version", "TLS_1_0"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSslPolicy_profile(t *testing.T) { + t.Parallel() + + var sslPolicy compute.SslPolicy + sslPolicyName := fmt.Sprintf("test-ssl-policy-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSslPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSslPolicyProfile(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.profile", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.profile", "profile", "MODERN"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.profile", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSslPolicy_update(t *testing.T) { + t.Parallel() + + var sslPolicy compute.SslPolicy + sslPolicyName := fmt.Sprintf("test-ssl-policy-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSslPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSslUpdate1(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.update", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "profile", "MODERN"), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "min_tls_version", "TLS_1_0"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.update", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeSslUpdate2(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.update", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "profile", "RESTRICTED"), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "min_tls_version", "TLS_1_2"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.update", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSslPolicy_tls_version(t *testing.T) { + t.Parallel() + + var sslPolicy compute.SslPolicy + sslPolicyName := fmt.Sprintf("test-ssl-policy-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSslPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSslPolicyTlsVersion(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.tlsversion", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.tlsversion", "min_tls_version", "TLS_1_2"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.tlsversion", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSslPolicy_custom(t *testing.T) { + t.Parallel() + + var sslPolicy compute.SslPolicy + sslPolicyName := fmt.Sprintf("test-ssl-policy-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSslPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSslPolicyCustom(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.custom", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.custom", "min_tls_version", "TLS_1_2"), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.custom", "profile", "CUSTOM"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.custom", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSslPolicy_update_to_custom(t *testing.T) { + t.Parallel() + + var sslPolicy compute.SslPolicy + sslPolicyName := fmt.Sprintf("test-ssl-policy-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSslPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSslUpdate1(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.update", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "profile", "MODERN"), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "min_tls_version", "TLS_1_0"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.update", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeSslUpdate3(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.update", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "profile", "CUSTOM"), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "min_tls_version", "TLS_1_1"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.update", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSslPolicy_update_from_custom(t *testing.T) { + t.Parallel() + + var sslPolicy compute.SslPolicy + sslPolicyName := fmt.Sprintf("test-ssl-policy-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSslPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSslUpdate3(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.update", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "profile", "CUSTOM"), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "min_tls_version", "TLS_1_1"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.update", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + Config: testAccComputeSslUpdate1(sslPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSslPolicyExists( + "google_compute_ssl_policy.update", &sslPolicy), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "profile", "MODERN"), + resource.TestCheckResourceAttr( + "google_compute_ssl_policy.update", "min_tls_version", "TLS_1_0"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_ssl_policy.update", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckComputeSslPolicyExists(n string, sslPolicy *compute.SslPolicy) 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) + + project, err := getTestProject(rs.Primary, config) + if err != nil { + return err + } + + name := rs.Primary.Attributes["name"] + + found, err := config.clientCompute.SslPolicies.Get( + project, name).Do() + if err != nil { + return fmt.Errorf("Error Reading SSL Policy %s: %s", name, err) + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("SSL Policy not found") + } + + *sslPolicy = *found + + return nil + } +} + +func testAccCheckComputeSslPolicyDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_ssl_policy" { + continue + } + + _, err := config.clientCompute.SslPolicies.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("SSL Policy still exists") + } + } + + return nil +} + +func testAccComputeSslPolicyBasic(resourceName string) string { + return fmt.Sprintf(` +resource "google_compute_ssl_policy" "basic" { + name = "%s" + description = "Generated by TF provider acceptance test" +} +`, resourceName) +} + +func testAccComputeSslPolicyProfile(resourceName string) string { + return fmt.Sprintf(` +resource "google_compute_ssl_policy" "profile" { + name = "%s" + description = "Generated by TF provider acceptance test" + profile = "MODERN" +} +`, resourceName) +} + +func testAccComputeSslUpdate1(resourceName string) string { + return fmt.Sprintf(` +resource "google_compute_ssl_policy" "update" { + name = "%s" + description = "Generated by TF provider acceptance test" + min_tls_version = "TLS_1_0" + profile = "MODERN" +} +`, resourceName) +} + +func testAccComputeSslUpdate2(resourceName string) string { + return fmt.Sprintf(` +resource "google_compute_ssl_policy" "update" { + name = "%s" + description = "Generated by TF provider acceptance test" + min_tls_version = "TLS_1_2" + profile = "RESTRICTED" +} +`, resourceName) +} + +func testAccComputeSslUpdate3(resourceName string) string { + return fmt.Sprintf(` +resource "google_compute_ssl_policy" "update" { + name = "%s" + description = "Generated by TF provider acceptance test" + min_tls_version = "TLS_1_1" + profile = "CUSTOM" + custom_features = ["TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] +} +`, resourceName) +} + +func testAccComputeSslPolicyTlsVersion(resourceName string) string { + return fmt.Sprintf(` +resource "google_compute_ssl_policy" "tlsversion" { + name = "%s" + description = "Generated by TF provider acceptance test" + min_tls_version = "TLS_1_2" +} +`, resourceName) +} + +func testAccComputeSslPolicyCustom(resourceName string) string { + return fmt.Sprintf(` +resource "google_compute_ssl_policy" "custom" { + name = "%s" + description = "Generated by TF provider acceptance test" + min_tls_version = "TLS_1_2" + profile = "CUSTOM" + custom_features = ["TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] +} +`, resourceName) +} diff --git a/provider/terraform/tests/resource_compute_subnetwork_iam_test.go b/provider/terraform/tests/resource_compute_subnetwork_iam_test.go new file mode 100644 index 000000000000..1c6cf705598f --- /dev/null +++ b/provider/terraform/tests/resource_compute_subnetwork_iam_test.go @@ -0,0 +1,247 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccComputeSubnetworkIamBinding(t *testing.T) { + t.Parallel() + + account := acctest.RandomWithPrefix("tf-test") + role := "roles/compute.networkUser" + region := getTestRegionFromEnv() + subnetwork := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccComputeSubnetworkIamBinding_basic(account, region, subnetwork, role), + }, + resource.TestStep{ + ResourceName: "google_compute_subnetwork_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", region, subnetwork, role), + ImportState: true, + ImportStateVerify: true, + }, + { + // Test Iam Binding update + Config: testAccComputeSubnetworkIamBinding_update(account, region, subnetwork, role), + }, + resource.TestStep{ + ResourceName: "google_compute_subnetwork_iam_binding.foo", + ImportStateId: fmt.Sprintf("%s/%s %s", region, subnetwork, role), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSubnetworkIamMember(t *testing.T) { + t.Parallel() + + project := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + role := "roles/compute.networkUser" + region := getTestRegionFromEnv() + subnetwork := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAccComputeSubnetworkIamMember_basic(account, region, subnetwork, role), + }, + resource.TestStep{ + ResourceName: "google_compute_subnetwork_iam_member.foo", + ImportStateId: fmt.Sprintf("%s/%s %s serviceAccount:%s@%s.iam.gserviceaccount.com", region, subnetwork, role, account, project), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSubnetworkIamPolicy(t *testing.T) { + t.Parallel() + + project := getTestProjectFromEnv() + account := acctest.RandomWithPrefix("tf-test") + role := "roles/compute.networkUser" + region := getTestRegionFromEnv() + subnetwork := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccComputeSubnetworkIamPolicy_basic(account, region, subnetwork, role), + }, + // Test a few import formats + resource.TestStep{ + ResourceName: "google_compute_subnetwork_iam_policy.foo", + ImportStateId: fmt.Sprintf("projects/%s/regions/%s/subnetworks/%s", project, region, subnetwork), + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_compute_subnetwork_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s/%s/%s", project, region, subnetwork), + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_compute_subnetwork_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s/%s", region, subnetwork), + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_compute_subnetwork_iam_policy.foo", + ImportStateId: fmt.Sprintf("%s", subnetwork), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccComputeSubnetworkIamBinding_basic(account, region, subnetworkName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_compute_network" "network" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "%s" + region = "%s" + ip_cidr_range = "10.1.0.0/16" + network = "${google_compute_network.network.name}" +} + +resource "google_compute_subnetwork_iam_binding" "foo" { + project = "${google_compute_subnetwork.subnetwork.project}" + region = "${google_compute_subnetwork.subnetwork.region}" + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + role = "%s" + members = ["serviceAccount:${google_service_account.test_account.email}"] +} +`, account, subnetworkName, subnetworkName, region, roleId) +} + +func testAccComputeSubnetworkIamBinding_update(account, region, subnetworkName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_service_account" "test_account_2" { + account_id = "%s-2" + display_name = "Iam Testing Account" +} + +resource "google_compute_network" "network" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "%s" + region = "%s" + ip_cidr_range = "10.1.0.0/16" + network = "${google_compute_network.network.name}" +} + +resource "google_compute_subnetwork_iam_binding" "foo" { + project = "${google_compute_subnetwork.subnetwork.project}" + region = "${google_compute_subnetwork.subnetwork.region}" + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + role = "%s" + members = [ + "serviceAccount:${google_service_account.test_account.email}", + "serviceAccount:${google_service_account.test_account_2.email}" + ] +} +`, account, account, subnetworkName, subnetworkName, region, roleId) +} + +func testAccComputeSubnetworkIamMember_basic(account, region, subnetworkName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_compute_network" "network" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "%s" + region = "%s" + ip_cidr_range = "10.1.0.0/16" + network = "${google_compute_network.network.name}" +} + +resource "google_compute_subnetwork_iam_member" "foo" { + project = "${google_compute_subnetwork.subnetwork.project}" + region = "${google_compute_subnetwork.subnetwork.region}" + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + role = "%s" + member = "serviceAccount:${google_service_account.test_account.email}" +} +`, account, subnetworkName, subnetworkName, region, roleId) +} + +func testAccComputeSubnetworkIamPolicy_basic(account, region, subnetworkName, roleId string) string { + return fmt.Sprintf(` +resource "google_service_account" "test_account" { + account_id = "%s" + display_name = "Iam Testing Account" +} + +resource "google_compute_network" "network" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "subnetwork" { + name = "%s" + region = "%s" + ip_cidr_range = "10.1.0.0/16" + network = "${google_compute_network.network.name}" +} + +data "google_iam_policy" "foo" { + binding { + role = "%s" + + members = ["serviceAccount:${google_service_account.test_account.email}"] + } +} + +resource "google_compute_subnetwork_iam_policy" "foo" { + project = "${google_compute_subnetwork.subnetwork.project}" + region = "${google_compute_subnetwork.subnetwork.region}" + subnetwork = "${google_compute_subnetwork.subnetwork.name}" + policy_data = "${data.google_iam_policy.foo.policy_data}" +} +`, account, subnetworkName, subnetworkName, region, roleId) +} diff --git a/provider/terraform/tests/resource_compute_subnetwork_test.go b/provider/terraform/tests/resource_compute_subnetwork_test.go new file mode 100644 index 000000000000..a5ab1dde0429 --- /dev/null +++ b/provider/terraform/tests/resource_compute_subnetwork_test.go @@ -0,0 +1,421 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" +) + +// Unit tests + +func TestIsShrinkageIpCidr(t *testing.T) { + cases := map[string]struct { + Old, New string + Shrinkage bool + }{ + "Expansion same network ip": { + Old: "10.0.0.0/24", + New: "10.0.0.0/16", + Shrinkage: false, + }, + "Expansion different network ip": { + Old: "10.0.1.0/24", + New: "10.0.0.0/16", + Shrinkage: false, + }, + "Shrinkage same network ip": { + Old: "10.0.0.0/16", + New: "10.0.0.0/24", + Shrinkage: true, + }, + "Shrinkage different network ip": { + Old: "10.0.0.0/16", + New: "10.1.0.0/16", + Shrinkage: true, + }, + } + + for tn, tc := range cases { + if isShrinkageIpCidr(tc.Old, tc.New, nil) != tc.Shrinkage { + t.Errorf("%s failed: Shrinkage should be %t", tn, tc.Shrinkage) + } + } +} + +// Acceptance tests + +func TestAccComputeSubnetwork_basic(t *testing.T) { + t.Parallel() + + var subnetwork1 compute.Subnetwork + var subnetwork2 compute.Subnetwork + + cnName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + subnetwork1Name := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + subnetwork2Name := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + subnetwork3Name := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSubnetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSubnetwork_basic(cnName, subnetwork1Name, subnetwork2Name, subnetwork3Name), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists( + "google_compute_subnetwork.network-ref-by-url", &subnetwork1), + testAccCheckComputeSubnetworkExists( + "google_compute_subnetwork.network-ref-by-name", &subnetwork2), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_subnetwork.network-ref-by-url", + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_compute_subnetwork.network-with-private-google-access", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeSubnetwork_update(t *testing.T) { + t.Parallel() + + var subnetwork compute.Subnetwork + + cnName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + subnetworkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSubnetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSubnetwork_update1(cnName, "10.2.0.0/24", subnetworkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists( + "google_compute_subnetwork.network-with-private-google-access", &subnetwork), + ), + }, + resource.TestStep{ + // Expand IP CIDR range and update private_ip_google_access + Config: testAccComputeSubnetwork_update2(cnName, "10.2.0.0/16", subnetworkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists( + "google_compute_subnetwork.network-with-private-google-access", &subnetwork), + ), + }, + resource.TestStep{ + // Shrink IP CIDR range and update private_ip_google_access + Config: testAccComputeSubnetwork_update2(cnName, "10.2.0.0/24", subnetworkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists( + "google_compute_subnetwork.network-with-private-google-access", &subnetwork), + ), + }, + }, + }) + + if subnetwork.PrivateIpGoogleAccess { + t.Errorf("Expected PrivateIpGoogleAccess to be false, got %v", subnetwork.PrivateIpGoogleAccess) + } +} + +func TestAccComputeSubnetwork_secondaryIpRanges(t *testing.T) { + t.Parallel() + + var subnetwork compute.Subnetwork + + cnName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + subnetworkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSubnetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeSubnetwork_secondaryIpRanges_update1(cnName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists("google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork), + testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"), + ), + }, + resource.TestStep{ + Config: testAccComputeSubnetwork_secondaryIpRanges_update2(cnName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists("google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork), + testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"), + testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update2", "192.168.11.0/24"), + ), + }, + resource.TestStep{ + Config: testAccComputeSubnetwork_secondaryIpRanges_update1(cnName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists("google_compute_subnetwork.network-with-private-secondary-ip-ranges", &subnetwork), + testAccCheckComputeSubnetworkHasSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update1", "192.168.10.0/24"), + testAccCheckComputeSubnetworkHasNotSecondaryIpRange(&subnetwork, "tf-test-secondary-range-update2", "192.168.11.0/24"), + ), + }, + }, + }) +} + +func TestAccComputeSubnetwork_flowLogs(t *testing.T) { + t.Parallel() + + var subnetwork compute.Subnetwork + + cnName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + subnetworkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSubnetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeSubnetwork_flowLogs(cnName, subnetworkName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists( + "google_compute_subnetwork.network-with-flow-logs", &subnetwork), + resource.TestCheckResourceAttr("google_compute_subnetwork.network-with-flow-logs", + "enable_flow_logs", "true"), + ), + }, + { + ResourceName: "google_compute_subnetwork.network-with-flow-logs", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeSubnetwork_flowLogs(cnName, subnetworkName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeSubnetworkExists( + "google_compute_subnetwork.network-with-flow-logs", &subnetwork), + resource.TestCheckResourceAttr("google_compute_subnetwork.network-with-flow-logs", + "enable_flow_logs", "false"), + ), + }, + { + ResourceName: "google_compute_subnetwork.network-with-flow-logs", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckComputeSubnetworkDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_subnetwork" { + continue + } + + region, subnet_name := splitSubnetID(rs.Primary.ID) + _, err := config.clientCompute.Subnetworks.Get( + config.Project, region, subnet_name).Do() + if err == nil { + return fmt.Errorf("Network still exists") + } + } + + return nil +} + +func testAccCheckComputeSubnetworkExists(n string, subnetwork *compute.Subnetwork) 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) + + region, subnet_name := splitSubnetID(rs.Primary.ID) + found, err := config.clientCompute.Subnetworks.Get( + config.Project, region, subnet_name).Do() + if err != nil { + return err + } + + if found.Name != subnet_name { + return fmt.Errorf("Subnetwork not found") + } + + *subnetwork = *found + + return nil + } +} + +func testAccCheckComputeSubnetworkHasSecondaryIpRange(subnetwork *compute.Subnetwork, rangeName, ipCidrRange string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, secondaryRange := range subnetwork.SecondaryIpRanges { + if secondaryRange.RangeName == rangeName { + if secondaryRange.IpCidrRange == ipCidrRange { + return nil + } + return fmt.Errorf("Secondary range %s has the wrong ip_cidr_range. Expected %s, got %s", rangeName, ipCidrRange, secondaryRange.IpCidrRange) + } + } + + return fmt.Errorf("Secondary range %s not found", rangeName) + } +} + +func testAccCheckComputeSubnetworkHasNotSecondaryIpRange(subnetwork *compute.Subnetwork, rangeName, ipCidrRange string) resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, secondaryRange := range subnetwork.SecondaryIpRanges { + if secondaryRange.RangeName == rangeName { + if secondaryRange.IpCidrRange == ipCidrRange { + return fmt.Errorf("Secondary range %s has the wrong ip_cidr_range. Expected %s, got %s", rangeName, ipCidrRange, secondaryRange.IpCidrRange) + } + } + } + + return nil + } +} + +func testAccComputeSubnetwork_basic(cnName, subnetwork1Name, subnetwork2Name, subnetwork3Name string) string { + return fmt.Sprintf(` +resource "google_compute_network" "custom-test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "network-ref-by-url" { + name = "%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + network = "${google_compute_network.custom-test.self_link}" +} + + +resource "google_compute_subnetwork" "network-ref-by-name" { + name = "%s" + ip_cidr_range = "10.1.0.0/16" + region = "us-central1" + network = "${google_compute_network.custom-test.name}" +} + +resource "google_compute_subnetwork" "network-with-private-google-access" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = "${google_compute_network.custom-test.self_link}" + private_ip_google_access = true +} +`, cnName, subnetwork1Name, subnetwork2Name, subnetwork3Name) +} + +func testAccComputeSubnetwork_update1(cnName, cidrRange, subnetworkName string) string { + return fmt.Sprintf(` +resource "google_compute_network" "custom-test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "network-with-private-google-access" { + name = "%s" + ip_cidr_range = "%s" + region = "us-central1" + network = "${google_compute_network.custom-test.self_link}" + private_ip_google_access = true +} +`, cnName, subnetworkName, cidrRange) +} + +func testAccComputeSubnetwork_update2(cnName, cidrRange, subnetworkName string) string { + return fmt.Sprintf(` +resource "google_compute_network" "custom-test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "network-with-private-google-access" { + name = "%s" + ip_cidr_range = "%s" + region = "us-central1" + network = "${google_compute_network.custom-test.self_link}" +} +`, cnName, subnetworkName, cidrRange) +} + +func testAccComputeSubnetwork_secondaryIpRanges_update1(cnName, subnetworkName string) string { + return fmt.Sprintf(` +resource "google_compute_network" "custom-test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = "${google_compute_network.custom-test.self_link}" + secondary_ip_range { + range_name = "tf-test-secondary-range-update1" + ip_cidr_range = "192.168.10.0/24" + } +} +`, cnName, subnetworkName) +} + +func testAccComputeSubnetwork_secondaryIpRanges_update2(cnName, subnetworkName string) string { + return fmt.Sprintf(` +resource "google_compute_network" "custom-test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "network-with-private-secondary-ip-ranges" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = "${google_compute_network.custom-test.self_link}" + secondary_ip_range { + range_name = "tf-test-secondary-range-update1" + ip_cidr_range = "192.168.10.0/24" + } + secondary_ip_range { + range_name = "tf-test-secondary-range-update2" + ip_cidr_range = "192.168.11.0/24" + }, +} +`, cnName, subnetworkName) +} + +func testAccComputeSubnetwork_flowLogs(cnName, subnetworkName string, enableLogs bool) string { + return fmt.Sprintf(` +resource "google_compute_network" "custom-test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "network-with-flow-logs" { + name = "%s" + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" + network = "${google_compute_network.custom-test.self_link}" + enable_flow_logs = %v +} +`, cnName, subnetworkName, enableLogs) +} diff --git a/provider/terraform/tests/resource_compute_target_http_proxy_test.go b/provider/terraform/tests/resource_compute_target_http_proxy_test.go new file mode 100644 index 000000000000..cd77c5982781 --- /dev/null +++ b/provider/terraform/tests/resource_compute_target_http_proxy_test.go @@ -0,0 +1,250 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeTargetHttpProxy_basic(t *testing.T) { + t.Parallel() + + target := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + backend := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + hc := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + urlmap1 := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + urlmap2 := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeTargetHttpProxyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeTargetHttpProxy_basic1(target, backend, hc, urlmap1, urlmap2), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeTargetHttpProxyExists( + "google_compute_target_http_proxy.foobar"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_target_http_proxy.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeTargetHttpProxy_update(t *testing.T) { + t.Parallel() + + target := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + backend := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + hc := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + urlmap1 := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + urlmap2 := fmt.Sprintf("thttp-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeTargetHttpProxyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeTargetHttpProxy_basic1(target, backend, hc, urlmap1, urlmap2), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeTargetHttpProxyExists( + "google_compute_target_http_proxy.foobar"), + ), + }, + + resource.TestStep{ + Config: testAccComputeTargetHttpProxy_basic2(target, backend, hc, urlmap1, urlmap2), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeTargetHttpProxyExists( + "google_compute_target_http_proxy.foobar"), + ), + }, + }, + }) +} + +func testAccCheckComputeTargetHttpProxyDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_target_http_proxy" { + continue + } + + _, err := config.clientCompute.TargetHttpProxies.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("TargetHttpProxy still exists") + } + } + + return nil +} + +func testAccCheckComputeTargetHttpProxyExists(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) + + found, err := config.clientCompute.TargetHttpProxies.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("TargetHttpProxy not found") + } + + return nil + } +} + +func testAccComputeTargetHttpProxy_basic1(target, backend, hc, urlmap1, urlmap2 string) string { + return fmt.Sprintf(` + resource "google_compute_target_http_proxy" "foobar" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar1.self_link}" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + } + + resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 + } + + resource "google_compute_url_map" "foobar1" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + } + + resource "google_compute_url_map" "foobar2" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + } + `, target, backend, hc, urlmap1, urlmap2) +} + +func testAccComputeTargetHttpProxy_basic2(target, backend, hc, urlmap1, urlmap2 string) string { + return fmt.Sprintf(` + resource "google_compute_target_http_proxy" "foobar" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + url_map = "${google_compute_url_map.foobar2.self_link}" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] + } + + resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 + } + + resource "google_compute_url_map" "foobar1" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + } + + resource "google_compute_url_map" "foobar2" { + name = "%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } + } + `, target, backend, hc, urlmap1, urlmap2) +} diff --git a/provider/terraform/tests/resource_compute_target_pool_test.go b/provider/terraform/tests/resource_compute_target_pool_test.go new file mode 100644 index 000000000000..793f3171abd2 --- /dev/null +++ b/provider/terraform/tests/resource_compute_target_pool_test.go @@ -0,0 +1,151 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeTargetPool_basic(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeTargetPoolDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeTargetPool_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeTargetPoolExists( + "google_compute_target_pool.foo"), + testAccCheckComputeTargetPoolHealthCheck("google_compute_target_pool.foo", "google_compute_http_health_check.foobar"), + testAccCheckComputeTargetPoolExists( + "google_compute_target_pool.bar"), + testAccCheckComputeTargetPoolHealthCheck("google_compute_target_pool.bar", "google_compute_http_health_check.foobar"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_target_pool.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckComputeTargetPoolDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_target_pool" { + continue + } + + _, err := config.clientCompute.TargetPools.Get( + config.Project, config.Region, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("TargetPool still exists") + } + } + + return nil +} + +func testAccCheckComputeTargetPoolExists(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) + + found, err := config.clientCompute.TargetPools.Get( + config.Project, config.Region, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("TargetPool not found") + } + + return nil + } +} + +func testAccCheckComputeTargetPoolHealthCheck(targetPool, healthCheck string) resource.TestCheckFunc { + return func(s *terraform.State) error { + targetPoolRes, ok := s.RootModule().Resources[targetPool] + if !ok { + return fmt.Errorf("Not found: %s", targetPool) + } + + healthCheckRes, ok := s.RootModule().Resources[healthCheck] + if !ok { + return fmt.Errorf("Not found: %s", healthCheck) + } + + hcLink := healthCheckRes.Primary.Attributes["self_link"] + if targetPoolRes.Primary.Attributes["health_checks.0"] != hcLink { + return fmt.Errorf("Health check not set up. Expected %q", hcLink) + } + + return nil + } +} + +func testAccComputeTargetPool_basic() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_http_health_check" "foobar" { + name = "healthcheck-test-%s" + host = "example.com" +} + +resource "google_compute_instance" "foobar" { + name = "inst-tp-test-%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } +} + +resource "google_compute_target_pool" "foo" { + description = "Resource created for Terraform acceptance testing" + instances = ["${google_compute_instance.foobar.self_link}", "us-central1-b/bar"] + name = "tpool-test-%s" + session_affinity = "CLIENT_IP_PROTO" + health_checks = [ + "${google_compute_http_health_check.foobar.name}" + ] +} + +resource "google_compute_target_pool" "bar" { + description = "Resource created for Terraform acceptance testing" + name = "tpool-test-%s" + health_checks = [ + "${google_compute_http_health_check.foobar.self_link}" + ] +}`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) +} diff --git a/provider/terraform/tests/resource_compute_target_tcp_proxy_test.go b/provider/terraform/tests/resource_compute_target_tcp_proxy_test.go new file mode 100644 index 000000000000..dfbbf7dd07a8 --- /dev/null +++ b/provider/terraform/tests/resource_compute_target_tcp_proxy_test.go @@ -0,0 +1,165 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeTargetTcpProxy_basic(t *testing.T) { + t.Parallel() + + target := fmt.Sprintf("ttcp-test-%s", acctest.RandString(10)) + backend := fmt.Sprintf("ttcp-test-%s", acctest.RandString(10)) + hc := fmt.Sprintf("ttcp-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeTargetTcpProxyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeTargetTcpProxy_basic1(target, backend, hc), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeTargetTcpProxyExists( + "google_compute_target_tcp_proxy.foobar"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_target_tcp_proxy.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeTargetTcpProxy_update(t *testing.T) { + t.Parallel() + + target := fmt.Sprintf("ttcp-test-%s", acctest.RandString(10)) + backend := fmt.Sprintf("ttcp-test-%s", acctest.RandString(10)) + hc := fmt.Sprintf("ttcp-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeTargetTcpProxyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeTargetTcpProxy_basic1(target, backend, hc), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeTargetTcpProxyExists( + "google_compute_target_tcp_proxy.foobar"), + ), + }, + resource.TestStep{ + Config: testAccComputeTargetTcpProxy_basic2(target, backend, hc), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeTargetTcpProxyExists( + "google_compute_target_tcp_proxy.foobar"), + ), + }, + }, + }) +} + +func testAccCheckComputeTargetTcpProxyDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_target_tcp_proxy" { + continue + } + + _, err := config.clientCompute.TargetTcpProxies.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("TargetTcpProxy still exists") + } + } + + return nil +} + +func testAccCheckComputeTargetTcpProxyExists(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) + + found, err := config.clientCompute.TargetTcpProxies.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("TargetTcpProxy not found") + } + + return nil + } +} + +func testAccComputeTargetTcpProxy_basic1(target, backend, hc string) string { + return fmt.Sprintf(` + resource "google_compute_target_tcp_proxy" "foobar" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + backend_service = "${google_compute_backend_service.foobar.self_link}" + proxy_header = "NONE" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + protocol = "TCP" + health_checks = ["${google_compute_health_check.zero.self_link}"] + } + + resource "google_compute_health_check" "zero" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + tcp_health_check { + port = "443" + } + } + `, target, backend, hc) +} + +func testAccComputeTargetTcpProxy_basic2(target, backend, hc string) string { + return fmt.Sprintf(` + resource "google_compute_target_tcp_proxy" "foobar" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + backend_service = "${google_compute_backend_service.foobar.self_link}" + proxy_header = "PROXY_V1" + } + + resource "google_compute_backend_service" "foobar" { + name = "%s" + protocol = "TCP" + health_checks = ["${google_compute_health_check.zero.self_link}"] + } + + resource "google_compute_health_check" "zero" { + name = "%s" + check_interval_sec = 1 + timeout_sec = 1 + tcp_health_check { + port = "443" + } + } + `, target, backend, hc) +} diff --git a/provider/terraform/tests/resource_compute_url_map_test.go b/provider/terraform/tests/resource_compute_url_map_test.go new file mode 100644 index 000000000000..ba10f19646b1 --- /dev/null +++ b/provider/terraform/tests/resource_compute_url_map_test.go @@ -0,0 +1,411 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccComputeUrlMap_basic(t *testing.T) { + t.Parallel() + + bsName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + hcName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + umName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeUrlMapDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeUrlMap_basic1(bsName, hcName, umName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeUrlMapExists( + "google_compute_url_map.foobar"), + ), + }, + resource.TestStep{ + ResourceName: "google_compute_url_map.foobar", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"host_rule", "path_matcher", "test"}, + }, + }, + }) +} + +func TestAccComputeUrlMap_update_path_matcher(t *testing.T) { + t.Parallel() + + bsName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + hcName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + umName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeUrlMapDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeUrlMap_basic1(bsName, hcName, umName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeUrlMapExists( + "google_compute_url_map.foobar"), + ), + }, + + resource.TestStep{ + Config: testAccComputeUrlMap_basic2(bsName, hcName, umName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeUrlMapExists( + "google_compute_url_map.foobar"), + ), + }, + }, + }) +} + +func TestAccComputeUrlMap_advanced(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeUrlMapDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeUrlMap_advanced1(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeUrlMapExists( + "google_compute_url_map.foobar"), + ), + }, + + resource.TestStep{ + Config: testAccComputeUrlMap_advanced2(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeUrlMapExists( + "google_compute_url_map.foobar"), + ), + }, + }, + }) +} + +func TestAccComputeUrlMap_noPathRulesWithUpdate(t *testing.T) { + t.Parallel() + + bsName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + hcName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + umName := fmt.Sprintf("urlmap-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeUrlMapDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeUrlMap_noPathRules(bsName, hcName, umName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeUrlMapExists( + "google_compute_url_map.foobar"), + ), + }, + resource.TestStep{ + Config: testAccComputeUrlMap_basic1(bsName, hcName, umName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeUrlMapExists( + "google_compute_url_map.foobar"), + ), + }, + }, + }) +} + +func testAccCheckComputeUrlMapDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_url_map" { + continue + } + + _, err := config.clientCompute.UrlMaps.Get( + config.Project, rs.Primary.ID).Do() + if err == nil { + return fmt.Errorf("Url map still exists") + } + } + + return nil +} + +func testAccCheckComputeUrlMapExists(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) + + found, err := config.clientCompute.UrlMaps.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Url map not found") + } + return nil + } +} + +func testAccComputeUrlMap_basic1(bsName, hcName, umName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "urlmap-test-%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "urlmap-test-%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_url_map" "foobar" { + name = "urlmap-test-%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + + path_rule { + paths = ["/*"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } +} +`, bsName, hcName, umName) +} + +func testAccComputeUrlMap_basic2(bsName, hcName, umName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "urlmap-test-%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "urlmap-test-%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_url_map" "foobar" { + name = "urlmap-test-%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "blip" + } + + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "blip" + + path_rule { + paths = ["/*", "/home"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + + test { + host = "mysite.com" + path = "/test" + service = "${google_compute_backend_service.foobar.self_link}" + } +} +`, bsName, hcName, umName) +} + +func testAccComputeUrlMap_advanced1() string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "urlmap-test-%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "urlmap-test-%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_url_map" "foobar" { + name = "urlmap-test-%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "blop" + } + + host_rule { + hosts = ["myfavoritesite.com"] + path_matcher = "blip" + } + + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "blop" + + path_rule { + paths = ["/*", "/home"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "blip" + + path_rule { + paths = ["/*", "/home"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } +} +`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeUrlMap_advanced2() string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "urlmap-test-%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "urlmap-test-%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_url_map" "foobar" { + name = "urlmap-test-%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "blep" + } + + host_rule { + hosts = ["myfavoritesite.com"] + path_matcher = "blip" + } + + host_rule { + hosts = ["myleastfavoritesite.com"] + path_matcher = "blub" + } + + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "blep" + + path_rule { + paths = ["/home"] + service = "${google_compute_backend_service.foobar.self_link}" + } + + path_rule { + paths = ["/login"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "blub" + + path_rule { + paths = ["/*", "/blub"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } + + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "blip" + + path_rule { + paths = ["/*", "/home"] + service = "${google_compute_backend_service.foobar.self_link}" + } + } +} +`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) +} + +func testAccComputeUrlMap_noPathRules(bsName, hcName, umName string) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "foobar" { + name = "urlmap-test-%s" + health_checks = ["${google_compute_http_health_check.zero.self_link}"] +} + +resource "google_compute_http_health_check" "zero" { + name = "urlmap-test-%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + +resource "google_compute_url_map" "foobar" { + name = "urlmap-test-%s" + default_service = "${google_compute_backend_service.foobar.self_link}" + + host_rule { + hosts = ["mysite.com", "myothersite.com"] + path_matcher = "boop" + } + + path_matcher { + default_service = "${google_compute_backend_service.foobar.self_link}" + name = "boop" + } + + test { + host = "mysite.com" + path = "/*" + service = "${google_compute_backend_service.foobar.self_link}" + } +} +`, bsName, hcName, umName) +} diff --git a/provider/terraform/tests/resource_compute_vpn_gateway_test.go b/provider/terraform/tests/resource_compute_vpn_gateway_test.go new file mode 100644 index 000000000000..c922af914600 --- /dev/null +++ b/provider/terraform/tests/resource_compute_vpn_gateway_test.go @@ -0,0 +1,105 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "google.golang.org/api/compute/v1" +) + +func TestAccComputeVpnGateway_basic(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeVpnGatewayDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeVpnGateway_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeVpnGatewayExists( + "google_compute_vpn_gateway.foobar"), + testAccCheckComputeVpnGatewayExists( + "google_compute_vpn_gateway.baz"), + ), + }, + }, + }) +} + +func testAccCheckComputeVpnGatewayDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + project := config.Project + + vpnGatewaysService := compute.NewTargetVpnGatewaysService(config.clientCompute) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_network" { + continue + } + + region := rs.Primary.Attributes["region"] + name := rs.Primary.Attributes["name"] + + _, err := vpnGatewaysService.Get(project, region, name).Do() + + if err == nil { + return fmt.Errorf("Error, VPN Gateway %s in region %s still exists", + name, region) + } + } + + return nil +} + +func testAccCheckComputeVpnGatewayExists(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) + name := rs.Primary.Attributes["name"] + region := rs.Primary.Attributes["region"] + project := config.Project + + vpnGatewaysService := compute.NewTargetVpnGatewaysService(config.clientCompute) + _, err := vpnGatewaysService.Get(project, region, name).Do() + + if err != nil { + return fmt.Errorf("Error Reading VPN Gateway %s: %s", name, err) + } + + return nil + } +} + +func testAccComputeVpnGateway_basic() string { + return fmt.Sprintf(` +resource "google_compute_network" "foobar" { + name = "gateway-test-%s" + auto_create_subnetworks = false + ipv4_range = "10.0.0.0/16" +} + +resource "google_compute_vpn_gateway" "foobar" { + name = "gateway-test-%s" + network = "${google_compute_network.foobar.self_link}" + region = "us-central1" +} +resource "google_compute_vpn_gateway" "baz" { + name = "gateway-test-%s" + network = "${google_compute_network.foobar.name}" + region = "us-central1" +}`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) +} From 0a325ab3e8c5c8552e0bfe04b469d1ef26e58bfc Mon Sep 17 00:00:00 2001 From: emily Date: Tue, 16 Oct 2018 11:18:36 -0700 Subject: [PATCH 03/15] Add more import paths for Terraform google_service_account (#566) Merged PR #566. --- build/terraform | 2 +- provider/terraform/common~copy.yaml | 8 +- .../resource_google_service_account.go | 19 ++- .../resource_google_service_account_test.go | 128 ++++++++++++++++++ 4 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 provider/terraform/tests/resource_google_service_account_test.go diff --git a/build/terraform b/build/terraform index 05c0ec1b7f4a..80bd51e5c004 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 05c0ec1b7f4afa9c70e2f26d9486c1197aeef1c7 +Subproject commit 80bd51e5c0047894fdcf44cdb29e6eab7601a539 diff --git a/provider/terraform/common~copy.yaml b/provider/terraform/common~copy.yaml index 0e7cdeff4ae0..2dd94ffec987 100644 --- a/provider/terraform/common~copy.yaml +++ b/provider/terraform/common~copy.yaml @@ -17,26 +17,26 @@ -%> # Handwritten acceptance tests for autogenerated resources. # Adding them here allows updating the tests as part of a MM pull request. -<% Dir["provider/terraform/tests/*#{api.prefix[1..-1]}*"].each do |file_path| +<% Dir["provider/terraform/tests/*.go"].each do |file_path| fname = file_path.split('/')[-1] -%> '<%= dir -%>/<%= fname -%>': 'provider/terraform/tests/<%= fname -%>' <% end -%> # Copy all of the terraform resources that are still hand written <% - Dir["provider/terraform/resources/*#{api.prefix[1..-1]}*"].each do |file_path| + Dir["provider/terraform/resources/*.go"].each do |file_path| fname = file_path.split('/')[-1] -%> '<%= dir -%>/<%= fname -%>': 'provider/terraform/resources/<%= fname -%>' <% end -%> <% - Dir["provider/terraform/data_sources/*#{api.prefix[1..-1]}*"].each do |file_path| + Dir["provider/terraform/data_sources/*.go"].each do |file_path| fname = file_path.split('/')[-1] -%> '<%= dir -%>/<%= fname -%>': 'provider/terraform/data_sources/<%= fname -%>' <% end -%> <% - Dir["provider/terraform/utils/*"].each do |file_path| + Dir["provider/terraform/utils/*.go"].each do |file_path| fname = file_path.split('/')[-1] -%> '<%= dir -%>/<%= fname -%>': 'provider/terraform/utils/<%= fname -%>' diff --git a/provider/terraform/resources/resource_google_service_account.go b/provider/terraform/resources/resource_google_service_account.go index 14cb347a92cb..2c6672d1c23d 100644 --- a/provider/terraform/resources/resource_google_service_account.go +++ b/provider/terraform/resources/resource_google_service_account.go @@ -15,7 +15,7 @@ func resourceGoogleServiceAccount() *schema.Resource { Delete: resourceGoogleServiceAccountDelete, Update: resourceGoogleServiceAccountUpdate, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + State: resourceGoogleServiceAccountImport, }, Schema: map[string]*schema.Schema{ "email": &schema.Schema{ @@ -194,3 +194,20 @@ func saMergeBindings(bindings []*iam.Binding) []*iam.Binding { return rb } + +func resourceGoogleServiceAccountImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + parseImportId([]string{ + "projects/(?P[^/]+)/serviceAccounts/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)"}, d, config) + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/serviceAccounts/{{email}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} diff --git a/provider/terraform/tests/resource_google_service_account_test.go b/provider/terraform/tests/resource_google_service_account_test.go new file mode 100644 index 000000000000..251b261abc43 --- /dev/null +++ b/provider/terraform/tests/resource_google_service_account_test.go @@ -0,0 +1,128 @@ +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 resource can be created, updated, and destroyed +func TestAccServiceAccount_basic(t *testing.T) { + t.Parallel() + + accountId := "a" + acctest.RandString(10) + uniqueId := "" + displayName := "Terraform Test" + displayName2 := "Terraform Test Update" + project := getTestProjectFromEnv() + expectedEmail := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", accountId, project) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // The first step creates a basic service account + resource.TestStep{ + Config: testAccServiceAccountBasic(accountId, displayName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_service_account.acceptance", "project", project), + ), + }, + resource.TestStep{ + ResourceName: "google_service_account.acceptance", + ImportStateId: fmt.Sprintf("projects/%s/serviceAccounts/%s", project, expectedEmail), + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_service_account.acceptance", + ImportStateId: fmt.Sprintf("%s/%s", project, expectedEmail), + ImportState: true, + ImportStateVerify: true, + }, + resource.TestStep{ + ResourceName: "google_service_account.acceptance", + ImportStateId: expectedEmail, + ImportState: true, + ImportStateVerify: true, + }, + // The second step updates the service account + resource.TestStep{ + Config: testAccServiceAccountBasic(accountId, displayName2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_service_account.acceptance", "project", project), + testAccStoreServiceAccountUniqueId(&uniqueId), + ), + }, + resource.TestStep{ + ResourceName: "google_service_account.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + // The third step explicitely adds the same default project to the service account configuration + // and ensure the service account is not recreated by comparing the value of its unique_id with the one from the previous step + resource.TestStep{ + Config: testAccServiceAccountWithProject(project, accountId, displayName2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_service_account.acceptance", "project", project), + resource.TestCheckResourceAttrPtr( + "google_service_account.acceptance", "unique_id", &uniqueId), + ), + }, + resource.TestStep{ + ResourceName: "google_service_account.acceptance", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccStoreServiceAccountUniqueId(uniqueId *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + *uniqueId = s.RootModule().Resources["google_service_account.acceptance"].Primary.Attributes["unique_id"] + return nil + } +} + +func testAccServiceAccountBasic(account, name string) string { + return fmt.Sprintf(` +resource "google_service_account" "acceptance" { + account_id = "%v" + display_name = "%v" +} +`, account, name) +} + +func testAccServiceAccountWithProject(project, account, name string) string { + return fmt.Sprintf(` +resource "google_service_account" "acceptance" { + project = "%v" + account_id = "%v" + display_name = "%v" +} +`, project, account, name) +} + +func testAccServiceAccountPolicy(account, project string) string { + return fmt.Sprintf(` +resource "google_service_account" "acceptance" { + account_id = "%v" + display_name = "%v" +} + +data "google_iam_policy" "service_account" { + binding { + role = "roles/iam.serviceAccountActor" + members = [ + "serviceAccount:%v@%v.iam.gserviceaccount.com", + ] + } +} +`, account, account, account, project) +} From ab76f24f3d65aadb7b86715337f1690dbe184f5f Mon Sep 17 00:00:00 2001 From: emily Date: Tue, 16 Oct 2018 11:33:37 -0700 Subject: [PATCH 04/15] Update gitignore with some common extensions (#527) Merged PR #527. --- .gitignore | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.gitignore b/.gitignore index 52db29480255..ee23455f777a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Temp files +*~ +*# *.swp + coverage/* *.pyc @@ -21,3 +25,13 @@ coverage/* # Keep project coverage statistics under source control !coverage/.last_run.json + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + From 4717dc79d09e62f655170c8b0ebb31bbf89ac484 Mon Sep 17 00:00:00 2001 From: Alex Stephen Date: Tue, 16 Oct 2018 14:30:21 -0700 Subject: [PATCH 05/15] The Last of the Facts Modules (#565) Merged PR #565. --- build/ansible | 2 +- build/terraform | 2 +- products/compute/ansible.yaml | 1 + products/container/ansible.yaml | 14 ++++++++++++- .../container/examples/ansible/cluster.yaml | 11 +--------- .../container/examples/ansible/node_pool.yaml | 2 +- products/spanner/ansible.yaml | 14 +++++++++++++ .../spanner/examples/ansible/database.yaml | 10 ---------- .../spanner/examples/ansible/instance.yaml | 8 -------- products/sql/ansible.yaml | 20 ++++++++++++++++++- products/sql/examples/ansible/database.yaml | 8 -------- products/sql/examples/ansible/instance.yaml | 7 ------- provider/ansible.rb | 1 + provider/ansible/resource_override.rb | 3 +++ templates/ansible/verifiers/facts.yaml.erb | 4 ++++ 15 files changed, 59 insertions(+), 48 deletions(-) diff --git a/build/ansible b/build/ansible index 9720967ff1eb..f83c565380f4 160000 --- a/build/ansible +++ b/build/ansible @@ -1 +1 @@ -Subproject commit 9720967ff1eba8ffe782f1910315e9c385948407 +Subproject commit f83c565380f420564e797d8028fe493223b80f39 diff --git a/build/terraform b/build/terraform index 80bd51e5c004..1a9338f9bfbf 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 80bd51e5c0047894fdcf44cdb29e6eab7601a539 +Subproject commit 1a9338f9bfbf9636d84fe30eb750afc12221b441 diff --git a/products/compute/ansible.yaml b/products/compute/ansible.yaml index d9b204637eba..57a8a2c59958 100644 --- a/products/compute/ansible.yaml +++ b/products/compute/ansible.yaml @@ -269,6 +269,7 @@ overrides: !ruby/object:Provider::ResourceOverrides - 'products/compute/helpers/python/instance_metadata.py' InterconnectAttachment: !ruby/object:Provider::Ansible::ResourceOverride version_added: '2.8' + has_tests: false Network: !ruby/object:Provider::Ansible::ResourceOverride properties: routingConfig: !ruby/object:Provider::Ansible::PropertyOverride diff --git a/products/container/ansible.yaml b/products/container/ansible.yaml index 0efefc05c1f1..286a3da03fb5 100644 --- a/products/container/ansible.yaml +++ b/products/container/ansible.yaml @@ -23,7 +23,19 @@ manifest: !ruby/object:Provider::Ansible::Manifest - google-auth >= 1.3.0 version_added: '2.6' author: Google Inc. (@googlecloudplatform) -# This is where custom code would be defined eventually. +datasources: !ruby/object:Provider::ResourceOverrides + Cluster: !ruby/object:Provider::Ansible::ResourceOverride + version_added: '2.8' + facts: !ruby/object:Provider::Ansible::FactsOverride + has_filters: false + list_key: 'clusters' + NodePool: !ruby/object:Provider::Ansible::ResourceOverride + version_added: '2.8' + facts: !ruby/object:Provider::Ansible::FactsOverride + has_filters: false + list_key: 'nodePools' + KubeConfig: !ruby/object:Provider::Ansible::ResourceOverride + exclude: true overrides: !ruby/object:Provider::ResourceOverrides Cluster: !ruby/object:Provider::Ansible::ResourceOverride provider_helpers: diff --git a/products/container/examples/ansible/cluster.yaml b/products/container/examples/ansible/cluster.yaml index 320def4a086e..eae70e7adf9f 100644 --- a/products/container/examples/ansible/cluster.yaml +++ b/products/container/examples/ansible/cluster.yaml @@ -14,7 +14,7 @@ task: !ruby/object:Provider::Ansible::Task name: gcp_container_cluster code: - name: <%= ctx[:name] %> + name: 'my-cluster' initial_node_count: 2 master_auth: username: "cluster_admin" @@ -26,12 +26,3 @@ task: !ruby/object:Provider::Ansible::Task project: <%= ctx[:project] %> auth_kind: <%= ctx[:auth_kind] %> service_account_file: <%= ctx[:service_account_file] %> -verifier: !ruby/object:Provider::Ansible::Verifier - command: | - gcloud container clusters describe - --project="{{ gcp_project}}" - --zone=us-central1-a - "{{ resource_name }}" - failure: !ruby/object:Provider::Ansible::FailureCondition - error: | - No cluster named '{{ resource_name }}' in {{ gcp_project }}. diff --git a/products/container/examples/ansible/node_pool.yaml b/products/container/examples/ansible/node_pool.yaml index 5864761efd70..d1db89fb0e20 100644 --- a/products/container/examples/ansible/node_pool.yaml +++ b/products/container/examples/ansible/node_pool.yaml @@ -15,7 +15,7 @@ task: !ruby/object:Provider::Ansible::Task name: gcp_container_node_pool code: - name: <%= ctx[:name] %> + name: 'my-pool' initial_node_count: 4 cluster: "{{ cluster }}" zone: 'us-central1-a' diff --git a/products/spanner/ansible.yaml b/products/spanner/ansible.yaml index 9ecd5ecd264c..0aefd1d47f72 100644 --- a/products/spanner/ansible.yaml +++ b/products/spanner/ansible.yaml @@ -23,6 +23,20 @@ manifest: !ruby/object:Provider::Ansible::Manifest - google-auth >= 1.3.0 version_added: '2.7' author: Google Inc. (@googlecloudplatform) +datasources: !ruby/object:Provider::ResourceOverrides + Instance: !ruby/object:Provider::Ansible::ResourceOverride + version_added: '2.8' + facts: !ruby/object:Provider::Ansible::FactsOverride + has_filters: false + list_key: 'instances' + Database: !ruby/object:Provider::Ansible::ResourceOverride + version_added: '2.8' + facts: !ruby/object:Provider::Ansible::FactsOverride + has_filters: false + list_key: 'databases' + # Virtual => true objects that don't need dedicated resources. + InstanceConfig: !ruby/object:Provider::Ansible::ResourceOverride + exclude: true overrides: !ruby/object:Provider::ResourceOverrides Instance: !ruby/object:Provider::Ansible::ResourceOverride custom_create_resource: true diff --git a/products/spanner/examples/ansible/database.yaml b/products/spanner/examples/ansible/database.yaml index 200b433beb60..f00527e75aa1 100644 --- a/products/spanner/examples/ansible/database.yaml +++ b/products/spanner/examples/ansible/database.yaml @@ -33,13 +33,3 @@ task: !ruby/object:Provider::Ansible::Task project: <%= ctx[:project] %> auth_kind: <%= ctx[:auth_kind] %> service_account_file: <%= ctx[:service_account_file] %> -verifier: !ruby/object:Provider::Ansible::Verifier - command: | - gcloud spanner databases describe - --project="{{ gcp_project }}" - --instance="{{ instance.name }}" - "webstore" - failure: !ruby/object:Provider::Ansible::GrpcFailureCondition - single: Database - plural: instances/instance-database/databases - name: webstore diff --git a/products/spanner/examples/ansible/instance.yaml b/products/spanner/examples/ansible/instance.yaml index 96d8675232d2..fe2572da2e70 100644 --- a/products/spanner/examples/ansible/instance.yaml +++ b/products/spanner/examples/ansible/instance.yaml @@ -23,11 +23,3 @@ task: !ruby/object:Provider::Ansible::Task project: <%= ctx[:project] %> auth_kind: <%= ctx[:auth_kind] %> service_account_file: <%= ctx[:service_account_file] %> -verifier: !ruby/object:Provider::Ansible::Verifier - command: | - gcloud spanner instances describe - --project="{{ gcp_project }}" - "{{ resource_name }}" - failure: !ruby/object:Provider::Ansible::GrpcFailureCondition - single: Instance - plural: instances diff --git a/products/sql/ansible.yaml b/products/sql/ansible.yaml index 81acbc207d28..d85e89d91b2f 100644 --- a/products/sql/ansible.yaml +++ b/products/sql/ansible.yaml @@ -23,7 +23,25 @@ manifest: !ruby/object:Provider::Ansible::Manifest - google-auth >= 1.3.0 version_added: '2.7' author: Google Inc. (@googlecloudplatform) -# This is where custom code would be defined eventually. +datasources: !ruby/object:Provider::ResourceOverrides + Instance: !ruby/object:Provider::Ansible::ResourceOverride + version_added: '2.8' + facts: !ruby/object:Provider::Ansible::FactsOverride + has_filters: false + Database: !ruby/object:Provider::Ansible::ResourceOverride + version_added: '2.8' + facts: !ruby/object:Provider::Ansible::FactsOverride + has_filters: false + User: !ruby/object:Provider::Ansible::ResourceOverride + version_added: '2.8' + facts: !ruby/object:Provider::Ansible::FactsOverride + has_filters: false + Tier: !ruby/object:Provider::Ansible::ResourceOverride + exclude: true + SslCert: !ruby/object:Provider::Ansible::ResourceOverride + exclude: true + Flag: !ruby/object:Provider::Ansible::ResourceOverride + exclude: true overrides: !ruby/object:Provider::ResourceOverrides Instance: !ruby/object:Provider::Ansible::ResourceOverride access_api_results: true diff --git a/products/sql/examples/ansible/database.yaml b/products/sql/examples/ansible/database.yaml index 36cd4fb8658f..8c1806cb2d19 100644 --- a/products/sql/examples/ansible/database.yaml +++ b/products/sql/examples/ansible/database.yaml @@ -36,11 +36,3 @@ task: !ruby/object:Provider::Ansible::Task project: <%= ctx[:project] %> auth_kind: <%= ctx[:auth_kind] %> service_account_file: <%= ctx[:service_account_file] %> -verifier: !ruby/object:Provider::Ansible::Verifier - command: | - gcloud sql databases describe - --instance='instance-database' - --project="{{ gcp_project}}" - "{{ resource_name }}" - failure: !ruby/object:Provider::Ansible::FailureCondition - error: '(gcloud.sql.databases.describe) HTTPError 404: Not Found' diff --git a/products/sql/examples/ansible/instance.yaml b/products/sql/examples/ansible/instance.yaml index 6527465bb29d..bbe480341996 100644 --- a/products/sql/examples/ansible/instance.yaml +++ b/products/sql/examples/ansible/instance.yaml @@ -25,10 +25,3 @@ task: !ruby/object:Provider::Ansible::Task project: <%= ctx[:project] %> auth_kind: <%= ctx[:auth_kind] %> service_account_file: <%= ctx[:service_account_file] %> -verifier: !ruby/object:Provider::Ansible::Verifier - command: | - gcloud sql instances describe - --project="{{ gcp_project}}" - "{{ resource_name }}" - failure: !ruby/object:Provider::Ansible::FailureCondition - error: Cloud SQL instance does not exist diff --git a/provider/ansible.rb b/provider/ansible.rb index 144401fc7a8d..36db401043ca 100644 --- a/provider/ansible.rb +++ b/provider/ansible.rb @@ -244,6 +244,7 @@ def generate_resource_tests(data) path = ["products/#{data[:product_name]}", "examples/ansible/#{prod_name}.yaml"].join('/') + return unless data[:object].has_tests # Unlike other providers, all resources will not be built at once or # in close timing to each other (due to external PRs). # This means that examples might not be built out for every resource diff --git a/provider/ansible/resource_override.rb b/provider/ansible/resource_override.rb index 4f009163832b..05632989ef78 100644 --- a/provider/ansible/resource_override.rb +++ b/provider/ansible/resource_override.rb @@ -25,6 +25,7 @@ module OverrideProperties attr_reader :create attr_reader :delete attr_reader :editable + attr_reader :has_tests attr_reader :hidden attr_reader :imports attr_reader :post_create @@ -51,6 +52,7 @@ def validate default_value_property :custom_update_resource, false default_value_property :exclude, false default_value_property :editable, true + default_value_property :has_tests, true default_value_property :imports, [] default_value_property :provider_helpers, [] default_value_property :unwrap_resource, false @@ -62,6 +64,7 @@ def validate check_optional_property :create, ::String check_optional_property :delete, ::String check_property :editable, :boolean + check_property :has_tests, :boolean check_optional_property :hidden, ::Array check_property :imports, ::Array check_optional_property :post_create, ::String diff --git a/templates/ansible/verifiers/facts.yaml.erb b/templates/ansible/verifiers/facts.yaml.erb index bb66494acc27..3da8e0778ecc 100644 --- a/templates/ansible/verifiers/facts.yaml.erb +++ b/templates/ansible/verifiers/facts.yaml.erb @@ -36,5 +36,9 @@ <% if object.facts.test -%> - <%= lines(object.facts.test) -%> <% else # object.facts.test -%> +<% if object.facts.has_filters -%> - results['items'] | length == <%= _state == 'present' ? 1 : 0 %> +<% else # object.has_filters -%> + - results['items'] | length <%= _state == 'present' ? '>= 1' : '== 0' %> +<% end # object.has_filters -%> <% end # object.facts.test -%> From d8335c6a3b712bb9634b4a1e9ee6c146cf8d5f67 Mon Sep 17 00:00:00 2001 From: Nathan McKinley Date: Tue, 16 Oct 2018 14:45:26 -0700 Subject: [PATCH 06/15] If a downstream PR is closed, count that as if it's merged. (#567) Merged PR #567. --- .ci/ci.yml.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/ci.yml.tmpl b/.ci/ci.yml.tmpl index bd2ceb69ec9c..e91635af26a0 100644 --- a/.ci/ci.yml.tmpl +++ b/.ci/ci.yml.tmpl @@ -13,7 +13,7 @@ resource_types: type: docker-image source: repository: nmckinley/concourse-github-pr-resource - tag: v0.1.11 + tag: v0.1.12 resources: - name: magic-modules From 078067a62c8822852f5911cabf2a6c166dbf1beb Mon Sep 17 00:00:00 2001 From: Sam Levenick Date: Tue, 16 Oct 2018 15:42:34 -0700 Subject: [PATCH 07/15] Ground up (#557) * Add basic provider * Remove build * Remove extra variables * Add CI for inspec * Reference master branch for creating PRs * Remove bad comments * Add inspec to pr template * yml not yaml * Rubocop * Rubocopppp * Add submodule for inspec * Ci changes for inspec * Chmod inspec * master instead of devel * Add method to prevent auto generated tests for inspec * Add bash headers * Add rm for current repo * Revert "Add rm for current repo" This reverts commit de0dd27b7382c0c783aebe5c227a66aaa6ec3345. * Add inspec to rakefile --- .ci/ci.yml.tmpl | 49 ++++++++++++++++ .ci/magic-modules/create-pr.sh | 19 ++++++ .ci/magic-modules/create-pr.yml | 1 + .ci/magic-modules/generate-inspec.sh | 40 +++++++++++++ .ci/magic-modules/generate-inspec.yml | 21 +++++++ .ci/magic-modules/point-to-submodules.sh | 8 +++ .ci/magic-modules/point-to-submodules.yml | 1 + .ci/unit-tests/inspec.sh | 6 ++ .ci/unit-tests/inspec.yml | 14 +++++ .ci/vars.tmpl | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 1 + .gitmodules | 3 + Rakefile | 3 +- build/inspec | 1 + compiler.rb | 1 + products/compute/inspec.yaml | 29 +++++++++ provider/inspec.rb | 71 +++++++++++++++++++++++ provider/inspec/manifest.rb | 25 ++++++++ provider/inspec/property_override.rb | 36 ++++++++++++ provider/inspec/resource_override.rb | 53 +++++++++++++++++ templates/inspec/plural_resource.erb | 17 ++++++ templates/inspec/singular_resource.erb | 17 ++++++ 22 files changed, 416 insertions(+), 2 deletions(-) create mode 100755 .ci/magic-modules/generate-inspec.sh create mode 100644 .ci/magic-modules/generate-inspec.yml create mode 100755 .ci/unit-tests/inspec.sh create mode 100644 .ci/unit-tests/inspec.yml create mode 160000 build/inspec create mode 100644 products/compute/inspec.yaml create mode 100644 provider/inspec.rb create mode 100644 provider/inspec/manifest.rb create mode 100644 provider/inspec/property_override.rb create mode 100644 provider/inspec/resource_override.rb create mode 100644 templates/inspec/plural_resource.erb create mode 100644 templates/inspec/singular_resource.erb diff --git a/.ci/ci.yml.tmpl b/.ci/ci.yml.tmpl index e91635af26a0..d87f661869f9 100644 --- a/.ci/ci.yml.tmpl +++ b/.ci/ci.yml.tmpl @@ -56,6 +56,12 @@ resources: uri: git@github.com:((github-account.username))/ansible.git private_key: ((repo-key.private_key)) + - name: inspec-intermediate + type: git-branch + source: + uri: git@github.com:((github-account.username))/inspec-gcp.git + private_key: ((repo-key.private_key)) + {% for module in vars.puppet_modules %} - name: puppet-{{module}}-intermediate type: git-branch @@ -154,6 +160,18 @@ jobs: branch_file: magic-modules-branched/branchname only_if_diff: true force: true + - do: + # consumes: magic-modules-branched + # produces: inspec-generated + - task: generate-inspec + file: magic-modules-branched/.ci/magic-modules/generate-inspec.yml + # Puts 'inspec-generated' into the robot's fork. + - put: inspec-intermediate + params: + repository: inspec-generated + branch_file: magic-modules-branched/branchname + only_if_diff: true + force: true {% if vars.puppet_modules %} - do: # consumes: magic-modules-branched @@ -210,6 +228,7 @@ jobs: CHEF_MODULES: {{','.join(vars.chef_modules)}} TERRAFORM_ENABLED: true ANSIBLE_ENABLED: true + INSPEC_ENABLED: true # Push the magic modules branch that contains the updated submodules. - put: magic-modules @@ -260,6 +279,27 @@ jobs: context: ansible-tests path: magic-modules-new-prs + - name: inspec-test + plan: + - get: magic-modules + version: every + trigger: true + params: + submodules: [build/inspec] + passed: [mm-generate] + - task: test + file: magic-modules/.ci/unit-tests/inspec.yml + timeout: 30m + on_failure: + do: + - get: magic-modules-new-prs + passed: [mm-generate] + - put: magic-modules-new-prs + params: + status: failure + context: inspec-tests + path: magic-modules-new-prs + - name: puppet-test plan: - get: magic-modules @@ -341,6 +381,7 @@ jobs: {%- endif %} - terraform-test - ansible-test + - inspec-test - get: mm-initial-pr resource: magic-modules-new-prs passed: [mm-generate] @@ -357,6 +398,7 @@ jobs: # is what you change if you want to test this in a non-live environment. TERRAFORM_REPO_USER: terraform-providers ANSIBLE_REPO_USER: modular-magician + INSPEC_REPO_USER: modular-magician {%- if vars.puppet_modules %} PUPPET_REPO_USER: GoogleCloudPlatform PUPPET_MODULES: {{','.join(vars.puppet_modules)}} @@ -397,6 +439,13 @@ jobs: # See comment on terraform-intermediate only_if_diff: true force: true + - put: inspec-intermediate + params: + repository: magic-modules-with-comment/build/inspec + branch_file: magic-modules-with-comment/original_pr_branch_name + # See comment on terraform-intermediate + only_if_diff: true + force: true {% for module in vars.puppet_modules %} - put: puppet-{{module}}-intermediate params: diff --git a/.ci/magic-modules/create-pr.sh b/.ci/magic-modules/create-pr.sh index 5365ffce205f..041f98dcb2a2 100755 --- a/.ci/magic-modules/create-pr.sh +++ b/.ci/magic-modules/create-pr.sh @@ -71,6 +71,25 @@ if [ "$BRANCH_NAME" = "$ORIGINAL_PR_BRANCH" ]; then popd fi + if [ -n "$INSPEC_REPO_USER" ]; then + pushd build/inspec + + git log -1 --pretty=%B > ./downstream_body + echo "" >> ./downstream_body + echo "" >> ./downstream_body + if [ -n "$ORIGINAL_PR_USER" ]; then + echo "/cc @$ORIGINAL_PR_USER" >> ./downstream_body + fi + + git checkout -b "$BRANCH_NAME" + if INSPEC_PR=$(hub pull-request -b "$INSPEC_REPO_USER/inspec:master" -F ./downstream_body); then + DEPENDENCIES="${DEPENDENCIES}depends: $INSPEC_PR ${NEWLINE}" + else + echo "InSpec - did not generate a PR." + fi + popd + fi + for PRD in "${PUPPET_PRODUCTS[@]}"; do pushd "build/puppet/$PRD" diff --git a/.ci/magic-modules/create-pr.yml b/.ci/magic-modules/create-pr.yml index 859d1a049a91..735318704f06 100644 --- a/.ci/magic-modules/create-pr.yml +++ b/.ci/magic-modules/create-pr.yml @@ -24,6 +24,7 @@ params: GITHUB_TOKEN: "" TERRAFORM_REPO_USER: "" ANSIBLE_REPO_USER: "" + INSPEC_REPO_USER: "" PUPPET_REPO_USER: "" PUPPET_MODULES: "" CHEF_REPO_USER: "" diff --git a/.ci/magic-modules/generate-inspec.sh b/.ci/magic-modules/generate-inspec.sh new file mode 100755 index 000000000000..0896aadb1557 --- /dev/null +++ b/.ci/magic-modules/generate-inspec.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# This script takes in 'magic-modules-branched', a git repo tracking the head of a PR against magic-modules. +# It outputs "inspec-generated", a non-submodule git repo containing the generated inspec code. + +set -x +set -e +source "$(dirname "$0")/helpers.sh" +PATCH_DIR="$(pwd)/patches" +pushd magic-modules-branched +LAST_COMMIT_AUTHOR="$(git log --pretty="%an <%ae>" -n1 HEAD)" +bundle install +for i in $(find products/ -name 'inspec.yaml' -printf '%h\n'); +do + bundle exec compiler -p $i -e inspec -o "build/inspec/" +done + +# This command can crash - if that happens, the script should not fail. +set +e +INSPEC_COMMIT_MSG="$(python .ci/magic-modules/extract_from_pr_description.py --tag inspec < .git/body)" +set -e +if [ -z "$INSPEC_COMMIT_MSG" ]; then + INSPEC_COMMIT_MSG="Magic Modules changes." +fi + +pushd "build/inspec" +# These config entries will set the "committer". +git config --global user.email "magic-modules@google.com" +git config --global user.name "Modular Magician" + +git add -A +# Set the "author" to the commit's real author. +git commit -m "$INSPEC_COMMIT_MSG" --author="$LAST_COMMIT_AUTHOR" || true # don't crash if no changes +git checkout -B "$(cat ../../branchname)" + +apply_patches "$PATCH_DIR/modular-magician/inspec-gcp" "$INSPEC_COMMIT_MSG" "$LAST_COMMIT_AUTHOR" "master" +popd +popd + +git clone magic-modules-branched/build/inspec ./inspec-generated diff --git a/.ci/magic-modules/generate-inspec.yml b/.ci/magic-modules/generate-inspec.yml new file mode 100644 index 000000000000..575cd28f6d2f --- /dev/null +++ b/.ci/magic-modules/generate-inspec.yml @@ -0,0 +1,21 @@ +--- +# This file takes two inputs: magic-modules-branched in detached-HEAD state, and the patches. +# It spits out "inspec-generated", an inspec repo on a new branch (named after the +# HEAD commit on the PR), with the new generated code in it. +platform: linux + +image_resource: + type: docker-image + source: + repository: nmckinley/go-ruby-python + tag: '1.11-2.5-2.7' + +inputs: + - name: magic-modules-branched + - name: patches + +outputs: + - name: inspec-generated + +run: + path: magic-modules-branched/.ci/magic-modules/generate-inspec.sh diff --git a/.ci/magic-modules/point-to-submodules.sh b/.ci/magic-modules/point-to-submodules.sh index e852cdb96506..39ba92110781 100755 --- a/.ci/magic-modules/point-to-submodules.sh +++ b/.ci/magic-modules/point-to-submodules.sh @@ -59,6 +59,14 @@ if [ "$ANSIBLE_ENABLED" = "true" ]; then git add build/ansible fi +if [ "$INSPEC_ENABLED" = "true" ]; then + git config -f .gitmodules submodule.build/inspec.branch "$BRANCH" + git config -f .gitmodules submodule.build/inspec.url "git@github.com:$GH_USERNAME/inspec-gcp.git" + git submodule sync build/inspec + ssh-agent bash -c "ssh-add ~/github_private_key; git submodule update --remote --init build/inspec" + git add build/inspec +fi + # Commit those changes so that they can be tested in the next phase. git add .gitmodules git config --global user.email "magic-modules@google.com" diff --git a/.ci/magic-modules/point-to-submodules.yml b/.ci/magic-modules/point-to-submodules.yml index 82f5c5653674..f91ff55283b8 100644 --- a/.ci/magic-modules/point-to-submodules.yml +++ b/.ci/magic-modules/point-to-submodules.yml @@ -24,6 +24,7 @@ params: CREDS: "" TERRAFORM_ENABLED: false ANSIBLE_ENABLED: false + INSPEC_ENABLED: false PUPPET_MODULES: "" CHEF_MODULES: "" diff --git a/.ci/unit-tests/inspec.sh b/.ci/unit-tests/inspec.sh new file mode 100755 index 000000000000..eb1427683a1f --- /dev/null +++ b/.ci/unit-tests/inspec.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e +set -x + +echo 'TODO slevenick write tests' \ No newline at end of file diff --git a/.ci/unit-tests/inspec.yml b/.ci/unit-tests/inspec.yml new file mode 100644 index 000000000000..d189d153caa6 --- /dev/null +++ b/.ci/unit-tests/inspec.yml @@ -0,0 +1,14 @@ +platform: linux +inputs: + - name: magic-modules +image_resource: + type: docker-image + source: + repository: ruby + tag: '2.5' +run: + path: magic-modules/.ci/unit-tests/inspec.sh +params: + PRODUCT: "" + PROVIDER: inspec + EXCLUDE_PATTERN: "" diff --git a/.ci/vars.tmpl b/.ci/vars.tmpl index aac853d7cdd3..f5aaa68e4548 100644 --- a/.ci/vars.tmpl +++ b/.ci/vars.tmpl @@ -10,7 +10,7 @@ build/{{repo}}/{{name}} {% set chef_submodules = names_as_list('chef', chef_modules).split() %} {% set all_submodules = puppet_submodules + chef_submodules + - (['build/terraform'] + ['build/ansible']) + (['build/terraform'] + ['build/ansible'] + ['build/inspec']) %} {% set all_submodules_yaml_format = '[' + ','.join(all_submodules) + ']' %} {% set chef_test_excludes = { diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index be8c9d4a27dc..bfab3ccbe1b1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -40,3 +40,4 @@ this PR's changes from the commit messages for downstream commits. ### [chef-sql] ### [chef-storage] ## [ansible] +## [inspec] diff --git a/.gitmodules b/.gitmodules index 641426918029..a0c2eed20449 100644 --- a/.gitmodules +++ b/.gitmodules @@ -81,3 +81,6 @@ [submodule "build/chef/iam"] path = build/chef/iam url = git@github.com:GoogleCloudPlatform/chef-google-iam.git +[submodule "build/inspec"] + path = build/inspec + url = git@github.com:modular-magician/inspec-gcp.git diff --git a/Rakefile b/Rakefile index da3fce97ec10..4625a1e33a43 100644 --- a/Rakefile +++ b/Rakefile @@ -19,7 +19,8 @@ PROVIDER_FOLDERS = { ansible: 'build/ansible', puppet: 'build/puppet/%s', chef: 'build/chef/%s', - terraform: 'build/terraform' + terraform: 'build/terraform', + inspec: 'build/inspec' }.freeze # Requires diff --git a/build/inspec b/build/inspec new file mode 160000 index 000000000000..faf47ee4071d --- /dev/null +++ b/build/inspec @@ -0,0 +1 @@ +Subproject commit faf47ee4071d8c4c4bc7ca62cb904ffa0fbd1271 diff --git a/compiler.rb b/compiler.rb index 97dddcbf738a..68249439e524 100755 --- a/compiler.rb +++ b/compiler.rb @@ -31,6 +31,7 @@ require 'provider/chef' require 'provider/chef/bundle' require 'provider/example' +require 'provider/inspec' require 'provider/puppet' require 'provider/puppet/bundle' require 'provider/terraform' diff --git a/products/compute/inspec.yaml b/products/compute/inspec.yaml new file mode 100644 index 000000000000..6523b93ef4ab --- /dev/null +++ b/products/compute/inspec.yaml @@ -0,0 +1,29 @@ +# 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::Inspec::Config +manifest: !ruby/object:Provider::Inspec::Manifest + version: '0.1.0' + source: 'FIXME' + issues: 'FIXME' + summary: 'InSpec resources for verifying GCP infrastructure' + description: | + InSpec resources for verifying GCP infrastructure +files: !ruby/object:Provider::Config::Files +style: +functions: +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-10-04T10:00:00-0700 + general: 'Initial release' diff --git a/provider/inspec.rb b/provider/inspec.rb new file mode 100644 index 000000000000..7f66da907f30 --- /dev/null +++ b/provider/inspec.rb @@ -0,0 +1,71 @@ +# 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. + +require 'provider/config' +require 'provider/core' +require 'provider/inspec/manifest' +require 'provider/inspec/resource_override' +require 'provider/inspec/property_override' + +module Provider + # Code generator for Example Cookbooks that manage Google Cloud Platform + # resources. + class Inspec < Provider::Core + # Settings for the provider + class Config < Provider::Config + attr_reader :manifest + def provider + Provider::Inspec + end + + def resource_override + Provider::Inspec::ResourceOverride + end + + def property_override + Provider::Inspec::PropertyOverride + end + end + + # This function uses the resource templates to create singular and plural + # resources that can be used by InSpec + def generate_resource(data) + target_folder = File.join(data[:output_folder], 'inspec') + FileUtils.mkpath target_folder + name = data[:object].name.underscore + generate_resource_file data.clone.merge( + default_template: 'templates/inspec/singular_resource.erb', + out_file: File.join(target_folder, "google_#{data[:product_name]}_#{name}.rb") + ) + generate_resource_file data.clone.merge( + default_template: 'templates/inspec/plural_resource.erb', + out_file: File.join(target_folder, "google_#{data[:product_name]}_#{name}s.rb") + ) + end + + # TODO? + def generate_resource_tests(data) end + + def generate_base_property(data) end + + def generate_simple_property(type, data) end + + def generate_typed_array(data, prop) end + + def emit_resourceref_object(data) end + + def emit_nested_object(data) end + + def generate_network_datas(data, object) end + end +end diff --git a/provider/inspec/manifest.rb b/provider/inspec/manifest.rb new file mode 100644 index 000000000000..e2502f5adf76 --- /dev/null +++ b/provider/inspec/manifest.rb @@ -0,0 +1,25 @@ +# 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. + +require 'provider/core' + +module Provider + class Inspec < Provider::Core + # Metadata for manifest.json + class Manifest < Api::Object + def validate + super + end + end + end +end diff --git a/provider/inspec/property_override.rb b/provider/inspec/property_override.rb new file mode 100644 index 000000000000..67d3deabf746 --- /dev/null +++ b/provider/inspec/property_override.rb @@ -0,0 +1,36 @@ +# 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. + +require 'api/object' +require 'provider/abstract_core' +require 'provider/property_override' + +module Provider + class Inspec < Provider::Core + # Collection of fields allowed in the PropertyOverride section for + # inspec. All fields should be `attr_reader :` + module OverrideFields + end + + # inspec-specific overrides to api.yaml. + class PropertyOverride < Provider::PropertyOverride + include OverrideFields + + private + + def overriden + Provider::Inspec::OverrideFields + end + end + end +end diff --git a/provider/inspec/resource_override.rb b/provider/inspec/resource_override.rb new file mode 100644 index 000000000000..b7771b20d53b --- /dev/null +++ b/provider/inspec/resource_override.rb @@ -0,0 +1,53 @@ +# 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. + +require 'provider/core' +require 'provider/resource_override' + +module Provider + class Inspec < Provider::Core + # inspec specific properties to be added to Api::Resource + module OverrideProperties + attr_reader :manual + end + + # Custom inspec code to handle type convergence operations + class Handlers < Api::Object + def validate + super + end + end + + # Product specific overriden properties for inspec + class ResourceOverride < Provider::ResourceOverride + include OverrideProperties + + def validate + assign_defaults + + super + check_property :manual, :boolean + end + + private + + def assign_defaults + default_value_property :manual, false + end + + def overriden + Provider::Inspec::OverrideProperties + end + end + end +end diff --git a/templates/inspec/plural_resource.erb b/templates/inspec/plural_resource.erb new file mode 100644 index 000000000000..3628efec92c1 --- /dev/null +++ b/templates/inspec/plural_resource.erb @@ -0,0 +1,17 @@ +<%# The license inside this block applies to this file. +# 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. +-%> +<%= compile 'templates/license.erb' -%> + +<%= lines(autogen_notice :ruby) -%> \ No newline at end of file diff --git a/templates/inspec/singular_resource.erb b/templates/inspec/singular_resource.erb new file mode 100644 index 000000000000..3628efec92c1 --- /dev/null +++ b/templates/inspec/singular_resource.erb @@ -0,0 +1,17 @@ +<%# The license inside this block applies to this file. +# 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. +-%> +<%= compile 'templates/license.erb' -%> + +<%= lines(autogen_notice :ruby) -%> \ No newline at end of file From 06d12b3ebb5ef0e127c2830d34f36f8ad3f0e65f Mon Sep 17 00:00:00 2001 From: Dana Hoffman Date: Tue, 16 Oct 2018 17:04:09 -0700 Subject: [PATCH 08/15] 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 72481eb03e2d5b504977c4c6dfba41f9223b98a6 Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Wed, 17 Oct 2018 11:11:33 -0700 Subject: [PATCH 09/15] Rename file, change copyright, spacing, consistency. --- products/storage/object_access_control.yaml | 17 +++++++++-------- products/storage/terraform.yaml | 2 +- ...google_storage_object_access_control_test.go | 8 ++++---- ...ring.go.erb => resourceref_as_string.go.erb} | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) rename templates/terraform/custom_expand/{set_resourceref_as_string.go.erb => resourceref_as_string.go.erb} (96%) diff --git a/products/storage/object_access_control.yaml b/products/storage/object_access_control.yaml index 41d7e17d8052..289d9f6d69f6 100644 --- a/products/storage/object_access_control.yaml +++ b/products/storage/object_access_control.yaml @@ -30,14 +30,15 @@ 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 + * 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 diff --git a/products/storage/terraform.yaml b/products/storage/terraform.yaml index d3e3f369b2ab..1eee3f47f5d6 100644 --- a/products/storage/terraform.yaml +++ b/products/storage/terraform.yaml @@ -32,7 +32,7 @@ overrides: !ruby/object:Provider::ResourceOverrides id: !ruby/object:Provider::Terraform::PropertyOverride exclude: true bucket: !ruby/object:Provider::Terraform::PropertyOverride - custom_expand: 'templates/terraform/custom_expand/set_resourceref_as_string.go.erb' + 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 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 index 1906b6329b50..cb2985f8118e 100644 --- a/provider/terraform/tests/resource_google_storage_object_access_control_test.go +++ b/provider/terraform/tests/resource_google_storage_object_access_control_test.go @@ -26,7 +26,7 @@ func TestAccStorageObjectAccessControl_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccStorageObjectAccessControlDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testGoogleStorageObjectAccessControlBasic(bucketName, objectName, "READER", "allUsers"), }, { @@ -55,7 +55,7 @@ func TestAccStorageObjectAccessControl_update(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccStorageObjectAccessControlDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testGoogleStorageObjectAccessControlBasic(bucketName, objectName, "READER", "allUsers"), }, { @@ -63,7 +63,7 @@ func TestAccStorageObjectAccessControl_update(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - resource.TestStep{ + { Config: testGoogleStorageObjectAccessControlBasic(bucketName, objectName, "OWNER", "allUsers"), }, { @@ -119,7 +119,7 @@ resource "google_storage_object_access_control" "default" { object = "${google_storage_bucket_object.object.name}" bucket = "${google_storage_bucket.bucket.name}" role = "%s" - entity = "%s" + entity = "%s" } `, bucketName, objectName, tfObjectAcl.Name(), role, entity) } diff --git a/templates/terraform/custom_expand/set_resourceref_as_string.go.erb b/templates/terraform/custom_expand/resourceref_as_string.go.erb similarity index 96% rename from templates/terraform/custom_expand/set_resourceref_as_string.go.erb rename to templates/terraform/custom_expand/resourceref_as_string.go.erb index cf008609db00..34e8b9099530 100644 --- a/templates/terraform/custom_expand/set_resourceref_as_string.go.erb +++ b/templates/terraform/custom_expand/resourceref_as_string.go.erb @@ -1,5 +1,5 @@ <%# The license inside this block applies to this file. - # Copyright 2017 Google Inc. + # 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 From fcd777704629022309bd3b55c08a25efb1e21e10 Mon Sep 17 00:00:00 2001 From: Sam Levenick Date: Wed, 17 Oct 2018 13:50:13 -0700 Subject: [PATCH 10/15] 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 11/15] 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 12/15] 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 13/15] 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 b2df83f5b0ae5351598092a3dca9148228bf893a Mon Sep 17 00:00:00 2001 From: Riley Karson Date: Wed, 17 Oct 2018 15:23:41 -0700 Subject: [PATCH 14/15] Fix formatting on list --- products/storage/object_access_control.yaml | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/products/storage/object_access_control.yaml b/products/storage/object_access_control.yaml index 289d9f6d69f6..ff695258b807 100644 --- a/products/storage/object_access_control.yaml +++ b/products/storage/object_access_control.yaml @@ -30,21 +30,14 @@ 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 + * 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 - - 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. - !ruby/object:Api::Type::String name: 'entityId' output: true From 7e31caaf444a1af955781a22bbb707c76aa1479e Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Wed, 17 Oct 2018 22:45:31 +0000 Subject: [PATCH 15/15] Update tracked submodules -> HEAD on Wed Oct 17 22:45:31 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/ansible | 2 +- build/chef/storage | 2 +- build/puppet/storage | 2 +- build/terraform | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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