diff --git a/products/activedirectory/api.yaml b/products/activedirectory/api.yaml index f63f88d9f02b..be82576b999f 100644 --- a/products/activedirectory/api.yaml +++ b/products/activedirectory/api.yaml @@ -20,28 +20,6 @@ versions: base_url: https://managedidentities.googleapis.com/v1/ scopes: - https://www.googleapis.com/auth/cloud-platform -async: !ruby/object:Api::OpAsync - operation: !ruby/object:Api::OpAsync::Operation - path: 'name' - base_url: '{{op_id}}' - wait_ms: 1000 - # It takes about 35-40 mins to get the resource created - timeouts: !ruby/object:Api::Timeouts - insert_minutes: 60 - update_minutes: 60 - delete_minutes: 60 - result: !ruby/object:Api::OpAsync::Result - path: 'response' - resource_inside_response: true - status: !ruby/object:Api::OpAsync::Status - path: 'done' - complete: true - allowed: - - true - - false - error: !ruby/object:Api::OpAsync::Error - path: 'error' - message: 'message' objects: - !ruby/object:Api::Resource name: 'Domain' @@ -55,6 +33,28 @@ objects: guides: 'Managed Microsoft Active Directory Quickstart': 'https://cloud.google.com/managed-microsoft-ad/docs/quickstarts' api: 'https://cloud.google.com/managed-microsoft-ad/reference/rest/v1/projects.locations.global.domains' + async: !ruby/object:Api::OpAsync + operation: !ruby/object:Api::OpAsync::Operation + path: 'name' + base_url: '{{op_id}}' + wait_ms: 1000 + # It takes about 35-40 mins to get the resource created + timeouts: !ruby/object:Api::Timeouts + insert_minutes: 60 + update_minutes: 60 + delete_minutes: 60 + result: !ruby/object:Api::OpAsync::Result + path: 'response' + resource_inside_response: true + status: !ruby/object:Api::OpAsync::Status + path: 'done' + complete: true + allowed: + - true + - false + error: !ruby/object:Api::OpAsync::Error + path: 'error' + message: 'message' parameters: - !ruby/object:Api::Type::String name: domainName @@ -104,4 +104,94 @@ objects: output: true description: | The fully-qualified domain name of the exposed domain used by clients to connect to the service. - Similar to what would be chosen for an Active Directory set up on an internal network. \ No newline at end of file + Similar to what would be chosen for an Active Directory set up on an internal network. + - !ruby/object:Api::Resource + name: 'DomainTrust' + kind: 'activedirectory#trust' + base_url: projects/{{project}}/locations/global/domains + create_url: projects/{{project}}/locations/global/domains/{{domain}}:attachTrust + update_verb: :POST + update_url: projects/{{project}}/locations/global/domains/{{domain}}:reconfigureTrust + delete_verb: :POST + # Resource custom delete function needs to be modified any time when the resource schema is edited + delete_url: projects/{{project}}/locations/global/domains/{{domain}}:detachTrust + self_link: projects/{{project}}/locations/global/domains/{{domain}} + description: Adds a trust between Active Directory domains + identity: + - targetDomainName + nested_query: !ruby/object:Api::Resource::NestedQuery + kind: 'domain#trustList' + keys: ['trusts'] + async: !ruby/object:Api::OpAsync + operation: !ruby/object:Api::OpAsync::Operation + path: 'name' + base_url: '{{op_id}}' + wait_ms: 1000 + timeouts: !ruby/object:Api::Timeouts + insert_minutes: 10 + update_minutes: 10 + delete_minutes: 10 + result: !ruby/object:Api::OpAsync::Result + path: 'response' + resource_inside_response: true + status: !ruby/object:Api::OpAsync::Status + path: 'done' + complete: true + allowed: + - true + - false + error: !ruby/object:Api::OpAsync::Error + path: 'error' + message: 'message' + references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Active Directory Trust': 'https://cloud.google.com/managed-microsoft-ad/docs/create-one-way-trust' + api: 'https://cloud.google.com/managed-microsoft-ad/reference/rest/v1/projects.locations.global.domains/attachTrust' + parameters: + - !ruby/object:Api::Type::String + name: domain + required: true + url_param_only: true + input: true + description: | + The fully qualified domain name. e.g. mydomain.myorganization.com, with the restrictions, + https://cloud.google.com/managed-microsoft-ad/reference/rest/v1/projects.locations.global.domains. + properties: + - !ruby/object:Api::Type::String + name: 'targetDomainName' + required: true + description: 'The fully qualified target domain name which will be in trust with the current domain.' + - !ruby/object:Api::Type::Enum + name: 'trustType' + required: true + input: true + description: 'The type of trust represented by the trust resource.' + values: + - FOREST + - EXTERNAL + - !ruby/object:Api::Type::Enum + name: 'trustDirection' + required: true + input: true + description: 'The trust direction, which decides if the current domain is trusted, trusting, or both.' + values: + - INBOUND + - OUTBOUND + - BIDIRECTIONAL + - !ruby/object:Api::Type::Boolean + name: 'selectiveAuthentication' + input: true + description: | + Whether the trusted side has forest/domain wide access or selective access to an approved set of resources. + - !ruby/object:Api::Type::Array + name: 'targetDnsIpAddresses' + required: true + item_type: Api::Type::String + description: | + The target DNS server IP addresses which can resolve the remote domain involved in the trust. + - !ruby/object:Api::Type::String + name: 'trustHandshakeSecret' + required: true + input: true + description: | + The trust secret used for the handshake with the target domain. This will not be stored. \ No newline at end of file diff --git a/products/activedirectory/terraform.yaml b/products/activedirectory/terraform.yaml index e646bebb95a1..ed1be9491da3 100644 --- a/products/activedirectory/terraform.yaml +++ b/products/activedirectory/terraform.yaml @@ -32,9 +32,32 @@ overrides: !ruby/object:Overrides::ResourceOverrides primary_resource_id: "ad-domain" vars: name: "myorg" - domain_name: mydomain + DomainTrust: !ruby/object:Overrides::Terraform::ResourceOverride + id_format: "projects/{{project}}/locations/global/domains/{{domain}}/{{target_domain_name}}" + import_format: ["projects/{{project}}/locations/global/domains/{{domain}}/{{target_domain_name}}"] + autogen_async: true + properties: + targetDnsIpAddresses: !ruby/object:Overrides::Terraform::PropertyOverride + is_set: true + trustHandshakeSecret: !ruby/object:Overrides::Terraform::PropertyOverride + sensitive: true + ignore_read: true + custom_code: !ruby/object:Provider::Terraform::CustomCode + update_encoder: templates/terraform/update_encoder/active_directory_domain_trust.go.erb + # Delete function needs to be modified any time when the resource schema is edited + custom_delete: templates/terraform/custom_delete/active_directory_domain_trust.go.erb + encoder: templates/terraform/encoders/active_directory_domain_trust.go.erb + decoder: templates/terraform/decoders/unwrap_resource.go.erb + examples: + - !ruby/object:Provider::Terraform::Examples + name: "active_directory_domain_trust_basic" + primary_resource_id: "ad-domain-trust" + # Fine-grained resource need different autogenerated tests, as + # we need to check destroy during a test step where the parent resource + # still exists and we need to validate that child resource has been deleted + skip_test: true files: !ruby/object:Provider::Config::Files # 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/product~compile.yaml'), 4)) -%> +<%= lines(indent(compile('provider/terraform/product~compile.yaml'), 4)) -%> \ No newline at end of file diff --git a/templates/terraform/custom_delete/active_directory_domain_trust.go.erb b/templates/terraform/custom_delete/active_directory_domain_trust.go.erb new file mode 100644 index 000000000000..b3e7c4957d10 --- /dev/null +++ b/templates/terraform/custom_delete/active_directory_domain_trust.go.erb @@ -0,0 +1,74 @@ + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{ActiveDirectoryBasePath}}projects/{{project}}/locations/global/domains/{{domain}}:detachTrust") + if err != nil { + return err + } + + <%# The generate DELETE method isn't including the {trust: } object in the response body thus custom_delete is needed -%> + + obj := make(map[string]interface{}) + targetDomainNameProp, err := expandNestedActiveDirectoryDomainTrustTargetDomainName(d.Get("target_domain_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("target_domain_name"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, targetDomainNameProp)) { + obj["targetDomainName"] = targetDomainNameProp + } + trustTypeProp, err := expandNestedActiveDirectoryDomainTrustTrustType(d.Get("trust_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("trust_type"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, trustTypeProp)) { + obj["trustType"] = trustTypeProp + } + trustDirectionProp, err := expandNestedActiveDirectoryDomainTrustTrustDirection(d.Get("trust_direction"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("trust_direction"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, trustDirectionProp)) { + obj["trustDirection"] = trustDirectionProp + } + selectiveAuthenticationProp, err := expandNestedActiveDirectoryDomainTrustSelectiveAuthentication(d.Get("selective_authentication"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("selective_authentication"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, selectiveAuthenticationProp)) { + obj["selectiveAuthentication"] = selectiveAuthenticationProp + } + targetDnsIpAddressesProp, err := expandNestedActiveDirectoryDomainTrustTargetDnsIpAddresses(d.Get("target_dns_ip_addresses"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("target_dns_ip_addresses"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, targetDnsIpAddressesProp)) { + obj["targetDnsIpAddresses"] = targetDnsIpAddressesProp + } + trustHandshakeSecretProp, err := expandNestedActiveDirectoryDomainTrustTrustHandshakeSecret(d.Get("trust_handshake_secret"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("trust_handshake_secret"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, trustHandshakeSecretProp)) { + obj["trustHandshakeSecret"] = trustHandshakeSecretProp + } + + obj, err = resourceActiveDirectoryDomainTrustEncoder(d, meta, obj) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting DomainTrust %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "DomainTrust") + } + + err = activeDirectoryOperationWaitTime( + config, res, project, "Deleting DomainTrust", + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting DomainTrust %q: %#v", d.Id(), res) + return nil \ No newline at end of file diff --git a/templates/terraform/encoders/active_directory_domain_trust.go.erb b/templates/terraform/encoders/active_directory_domain_trust.go.erb new file mode 100644 index 000000000000..873c7aa74b04 --- /dev/null +++ b/templates/terraform/encoders/active_directory_domain_trust.go.erb @@ -0,0 +1,19 @@ +<%# The license inside this block applies to this file. + # Copyright 2020 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. +-%> + +wrappedReq := map[string]interface{}{ + "trust": obj, +} +return wrappedReq, nil diff --git a/templates/terraform/examples/active_directory_domain_trust_basic.tf.erb b/templates/terraform/examples/active_directory_domain_trust_basic.tf.erb new file mode 100644 index 000000000000..c16b4eab99d5 --- /dev/null +++ b/templates/terraform/examples/active_directory_domain_trust_basic.tf.erb @@ -0,0 +1,8 @@ +resource "google_active_directory_domain_trust" "ad-domain-trust" { + domain = "test-managed-ad.com" + target_domain_name = "example-gcp.com" + target_dns_ip_addresses = ["10.1.0.100"] + trust_direction = "OUTBOUND" + trust_type = "FOREST" + trust_handshake_secret = "Testing1!" +} \ No newline at end of file diff --git a/templates/terraform/update_encoder/active_directory_domain_trust.go.erb b/templates/terraform/update_encoder/active_directory_domain_trust.go.erb new file mode 100644 index 000000000000..02629593db47 --- /dev/null +++ b/templates/terraform/update_encoder/active_directory_domain_trust.go.erb @@ -0,0 +1,19 @@ +<%# The license inside this block applies to this file. + # Copyright 2020 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. +-%> +wrappedReq := map[string]interface{}{ + "targetDomainName": obj["targetDomainName"], + "targetDnsIpAddresses": obj["targetDnsIpAddresses"], +} +return wrappedReq, nil diff --git a/third_party/terraform/tests/resource_active_directory_domain_trust_test.go b/third_party/terraform/tests/resource_active_directory_domain_trust_test.go new file mode 100644 index 000000000000..6554d6a84c7c --- /dev/null +++ b/third_party/terraform/tests/resource_active_directory_domain_trust_test.go @@ -0,0 +1,101 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccActiveDirectoryDomainTrust_activeDirectoryDomainTrustBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckActiveDirectoryDomainTrustDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccActiveDirectoryDomainTrust_activeDirectoryDomainTrustBasicExample(context), + }, + { + ResourceName: "google_active_directory_domain_trust.ad-domain-trust", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"trust_handshake_secret", "domain"}, + }, + { + Config: testAccActiveDirectoryDomainTrust_activeDirectoryDomainTrustUpdate(context), + }, + { + ResourceName: "google_active_directory_domain_trust.ad-domain-trust", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"trust_handshake_secret", "domain"}, + }, + }, + }) +} + +func testAccActiveDirectoryDomainTrust_activeDirectoryDomainTrustBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_active_directory_domain_trust" "ad-domain-trust" { + domain = "test-managed-ad.com" + target_domain_name = "example-gcp.com" + target_dns_ip_addresses = ["10.1.0.100"] + trust_direction = "OUTBOUND" + trust_type = "FOREST" + trust_handshake_secret = "Testing1!" +} +`, context) +} + +func testAccActiveDirectoryDomainTrust_activeDirectoryDomainTrustUpdate(context map[string]interface{}) string { + return Nprintf(` +resource "google_active_directory_domain_trust" "ad-domain-trust" { + domain = "test-managed-ad.com" + target_domain_name = "example-gcp.com" + target_dns_ip_addresses = ["10.2.0.100"] + trust_direction = "OUTBOUND" + trust_type = "FOREST" + trust_handshake_secret = "Testing1!" +} +`, context) +} + +func testAccCheckActiveDirectoryDomainTrustDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_active_directory_domain_trust" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{ActiveDirectoryBasePath}}projects/{{project}}/locations/global/domains/{{domain}}") + if err != nil { + return err + } + + res, _ := sendRequest(config, "GET", "", url, nil) + + var v interface{} + var ok bool + + v, ok = res["trusts"] + if ok || v != nil { + return fmt.Errorf("ActiveDirectoryDomainTrust still exists at %s", url) + } + } + return nil + } +}