diff --git a/aws/resource_aws_storagegateway_gateway.go b/aws/resource_aws_storagegateway_gateway.go index fca86b9f966..f1679468530 100644 --- a/aws/resource_aws_storagegateway_gateway.go +++ b/aws/resource_aws_storagegateway_gateway.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/storagegateway" + "github.com/hashicorp/terraform/helper/customdiff" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -20,6 +21,11 @@ func resourceAwsStorageGatewayGateway() *schema.Resource { Read: resourceAwsStorageGatewayGatewayRead, Update: resourceAwsStorageGatewayGatewayUpdate, Delete: resourceAwsStorageGatewayGatewayDelete, + CustomizeDiff: customdiff.Sequence( + customdiff.ForceNewIfChange("smb_active_directory_settings", func(old, new, meta interface{}) bool { + return len(old.([]interface{})) == 1 && len(new.([]interface{})) == 0 + }), + ), Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -81,6 +87,33 @@ func resourceAwsStorageGatewayGateway() *schema.Resource { "STK-L700", }, false), }, + "smb_active_directory_settings": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain_name": { + Type: schema.TypeString, + Required: true, + }, + "password": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + }, + "username": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "smb_guest_password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, "tape_drive_type": { Type: schema.TypeString, Optional: true, @@ -199,6 +232,36 @@ func resourceAwsStorageGatewayGatewayCreate(d *schema.ResourceData, meta interfa return fmt.Errorf("error waiting for Storage Gateway Gateway activation: %s", err) } + if v, ok := d.GetOk("smb_active_directory_settings"); ok && len(v.([]interface{})) > 0 { + m := v.([]interface{})[0].(map[string]interface{}) + + input := &storagegateway.JoinDomainInput{ + DomainName: aws.String(m["domain_name"].(string)), + GatewayARN: aws.String(d.Id()), + Password: aws.String(m["password"].(string)), + UserName: aws.String(m["username"].(string)), + } + + log.Printf("[DEBUG] Storage Gateway Gateway %q joining Active Directory domain: %s", d.Id(), m["domain_name"].(string)) + _, err := conn.JoinDomain(input) + if err != nil { + return fmt.Errorf("error joining Active Directory domain: %s", err) + } + } + + if v, ok := d.GetOk("smb_guest_password"); ok && v.(string) != "" { + input := &storagegateway.SetSMBGuestPasswordInput{ + GatewayARN: aws.String(d.Id()), + Password: aws.String(v.(string)), + } + + log.Printf("[DEBUG] Storage Gateway Gateway %q setting SMB guest password", d.Id()) + _, err := conn.SetSMBGuestPassword(input) + if err != nil { + return fmt.Errorf("error setting SMB guest password: %s", err) + } + } + return resourceAwsStorageGatewayGatewayRead(d, meta) } @@ -220,18 +283,79 @@ func resourceAwsStorageGatewayGatewayRead(d *schema.ResourceData, meta interface return fmt.Errorf("error reading Storage Gateway Gateway: %s", err) } + smbSettingsInput := &storagegateway.DescribeSMBSettingsInput{ + GatewayARN: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Reading Storage Gateway SMB Settings: %s", smbSettingsInput) + smbSettingsOutput, err := conn.DescribeSMBSettings(smbSettingsInput) + if err != nil && !isAWSErr(err, storagegateway.ErrCodeInvalidGatewayRequestException, "This operation is not valid for the specified gateway") { + if isAWSErrStorageGatewayGatewayNotFound(err) { + log.Printf("[WARN] Storage Gateway Gateway %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("error reading Storage Gateway SMB Settings: %s", err) + } + // The Storage Gateway API currently provides no way to read this value + // We allow Terraform to passthrough the configuration value into the state d.Set("activation_key", d.Get("activation_key").(string)) + d.Set("arn", output.GatewayARN) d.Set("gateway_id", output.GatewayId) + // The Storage Gateway API currently provides no way to read this value + // We allow Terraform to passthrough the configuration value into the state d.Set("gateway_ip_address", d.Get("gateway_ip_address").(string)) + d.Set("gateway_name", output.GatewayName) d.Set("gateway_timezone", output.GatewayTimezone) d.Set("gateway_type", output.GatewayType) + // The Storage Gateway API currently provides no way to read this value + // We allow Terraform to passthrough the configuration value into the state d.Set("medium_changer_type", d.Get("medium_changer_type").(string)) + + // Treat the entire nested argument as a whole, based on domain name + // to simplify schema and difference logic + if smbSettingsOutput == nil || aws.StringValue(smbSettingsOutput.DomainName) == "" { + if err := d.Set("smb_active_directory_settings", []interface{}{}); err != nil { + return fmt.Errorf("error setting smb_active_directory_settings: %s", err) + } + } else { + m := map[string]interface{}{ + "domain_name": aws.StringValue(smbSettingsOutput.DomainName), + // The Storage Gateway API currently provides no way to read these values + // "password": ..., + // "username": ..., + } + // We must assemble these into the map from configuration or Terraform will enter "" + // into state and constantly show a difference (also breaking downstream references) + // UPDATE: aws_storagegateway_gateway.test + // smb_active_directory_settings.0.password: "" => "" (attribute changed) + // smb_active_directory_settings.0.username: "" => "Administrator" + if v, ok := d.GetOk("smb_active_directory_settings"); ok && len(v.([]interface{})) > 0 { + configM := v.([]interface{})[0].(map[string]interface{}) + m["password"] = configM["password"] + m["username"] = configM["username"] + } + if err := d.Set("smb_active_directory_settings", []map[string]interface{}{m}); err != nil { + return fmt.Errorf("error setting smb_active_directory_settings: %s", err) + } + } + // The Storage Gateway API currently provides no way to read this value + // We allow Terraform to _automatically_ passthrough the configuration value into the state here + // as the API does clue us in whether or not its actually set at all, + // which can be used to tell Terraform to show a difference in this case + // as well as ensuring there is some sort of attribute value (unlike the others) + if smbSettingsOutput == nil || !aws.BoolValue(smbSettingsOutput.SMBGuestPasswordSet) { + d.Set("smb_guest_password", "") + } + + // The Storage Gateway API currently provides no way to read this value + // We allow Terraform to passthrough the configuration value into the state d.Set("tape_drive_type", d.Get("tape_drive_type").(string)) return nil @@ -240,16 +364,49 @@ func resourceAwsStorageGatewayGatewayRead(d *schema.ResourceData, meta interface func resourceAwsStorageGatewayGatewayUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).storagegatewayconn - input := &storagegateway.UpdateGatewayInformationInput{ - GatewayARN: aws.String(d.Id()), - GatewayName: aws.String(d.Get("gateway_name").(string)), - GatewayTimezone: aws.String(d.Get("gateway_timezone").(string)), + if d.HasChange("gateway_name") || d.HasChange("gateway_timezone") { + input := &storagegateway.UpdateGatewayInformationInput{ + GatewayARN: aws.String(d.Id()), + GatewayName: aws.String(d.Get("gateway_name").(string)), + GatewayTimezone: aws.String(d.Get("gateway_timezone").(string)), + } + + log.Printf("[DEBUG] Updating Storage Gateway Gateway: %s", input) + _, err := conn.UpdateGatewayInformation(input) + if err != nil { + return fmt.Errorf("error updating Storage Gateway Gateway: %s", err) + } } - log.Printf("[DEBUG] Updating Storage Gateway Gateway: %s", input) - _, err := conn.UpdateGatewayInformation(input) - if err != nil { - return fmt.Errorf("error updating Storage Gateway Gateway: %s", err) + if d.HasChange("smb_active_directory_settings") { + l := d.Get("smb_active_directory_settings").([]interface{}) + m := l[0].(map[string]interface{}) + + input := &storagegateway.JoinDomainInput{ + DomainName: aws.String(m["domain_name"].(string)), + GatewayARN: aws.String(d.Id()), + Password: aws.String(m["password"].(string)), + UserName: aws.String(m["username"].(string)), + } + + log.Printf("[DEBUG] Storage Gateway Gateway %q joining Active Directory domain: %s", d.Id(), m["domain_name"].(string)) + _, err := conn.JoinDomain(input) + if err != nil { + return fmt.Errorf("error joining Active Directory domain: %s", err) + } + } + + if d.HasChange("smb_guest_password") { + input := &storagegateway.SetSMBGuestPasswordInput{ + GatewayARN: aws.String(d.Id()), + Password: aws.String(d.Get("smb_guest_password").(string)), + } + + log.Printf("[DEBUG] Storage Gateway Gateway %q setting SMB guest password", d.Id()) + _, err := conn.SetSMBGuestPassword(input) + if err != nil { + return fmt.Errorf("error setting SMB guest password: %s", err) + } } return resourceAwsStorageGatewayGatewayRead(d, meta) diff --git a/aws/resource_aws_storagegateway_gateway_test.go b/aws/resource_aws_storagegateway_gateway_test.go index f312eb3df3f..ec425c64dfc 100644 --- a/aws/resource_aws_storagegateway_gateway_test.go +++ b/aws/resource_aws_storagegateway_gateway_test.go @@ -86,6 +86,8 @@ func TestAccAWSStorageGatewayGateway_GatewayType_Cached(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "gateway_timezone", "GMT"), resource.TestCheckResourceAttr(resourceName, "gateway_type", "CACHED"), resource.TestCheckResourceAttr(resourceName, "medium_changer_type", ""), + resource.TestCheckResourceAttr(resourceName, "smb_active_directory_settings.#", "0"), + resource.TestCheckResourceAttr(resourceName, "smb_guest_password", ""), resource.TestCheckResourceAttr(resourceName, "tape_drive_type", ""), ), }, @@ -119,6 +121,8 @@ func TestAccAWSStorageGatewayGateway_GatewayType_FileS3(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "gateway_timezone", "GMT"), resource.TestCheckResourceAttr(resourceName, "gateway_type", "FILE_S3"), resource.TestCheckResourceAttr(resourceName, "medium_changer_type", ""), + resource.TestCheckResourceAttr(resourceName, "smb_active_directory_settings.#", "0"), + resource.TestCheckResourceAttr(resourceName, "smb_guest_password", ""), resource.TestCheckResourceAttr(resourceName, "tape_drive_type", ""), ), }, @@ -152,6 +156,8 @@ func TestAccAWSStorageGatewayGateway_GatewayType_Stored(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "gateway_timezone", "GMT"), resource.TestCheckResourceAttr(resourceName, "gateway_type", "STORED"), resource.TestCheckResourceAttr(resourceName, "medium_changer_type", ""), + resource.TestCheckResourceAttr(resourceName, "smb_active_directory_settings.#", "0"), + resource.TestCheckResourceAttr(resourceName, "smb_guest_password", ""), resource.TestCheckResourceAttr(resourceName, "tape_drive_type", ""), ), }, @@ -185,6 +191,8 @@ func TestAccAWSStorageGatewayGateway_GatewayType_Vtl(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "gateway_timezone", "GMT"), resource.TestCheckResourceAttr(resourceName, "gateway_type", "VTL"), resource.TestCheckResourceAttr(resourceName, "medium_changer_type", ""), + resource.TestCheckResourceAttr(resourceName, "smb_active_directory_settings.#", "0"), + resource.TestCheckResourceAttr(resourceName, "smb_guest_password", ""), resource.TestCheckResourceAttr(resourceName, "tape_drive_type", ""), ), }, @@ -267,6 +275,68 @@ func TestAccAWSStorageGatewayGateway_GatewayTimezone(t *testing.T) { }) } +func TestAccAWSStorageGatewayGateway_SmbActiveDirectorySettings(t *testing.T) { + var gateway storagegateway.DescribeGatewayInformationOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_storagegateway_gateway.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSStorageGatewayGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSStorageGatewayGatewayConfig_SmbActiveDirectorySettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSStorageGatewayGatewayExists(resourceName, &gateway), + resource.TestCheckResourceAttr(resourceName, "smb_active_directory_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "smb_active_directory_settings.0.domain_name", "terraformtesting.com"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"activation_key", "gateway_ip_address", "smb_active_directory_settings"}, + }, + }, + }) +} + +func TestAccAWSStorageGatewayGateway_SmbGuestPassword(t *testing.T) { + var gateway storagegateway.DescribeGatewayInformationOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_storagegateway_gateway.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSStorageGatewayGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSStorageGatewayGatewayConfig_SmbGuestPassword(rName, "myguestpassword1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSStorageGatewayGatewayExists(resourceName, &gateway), + resource.TestCheckResourceAttr(resourceName, "smb_guest_password", "myguestpassword1"), + ), + }, + { + Config: testAccAWSStorageGatewayGatewayConfig_SmbGuestPassword(rName, "myguestpassword2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSStorageGatewayGatewayExists(resourceName, &gateway), + resource.TestCheckResourceAttr(resourceName, "smb_guest_password", "myguestpassword2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"activation_key", "gateway_ip_address", "smb_guest_password"}, + }, + }, + }) +} + func testAccCheckAWSStorageGatewayGatewayDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).storagegatewayconn @@ -516,3 +586,162 @@ resource "aws_storagegateway_gateway" "test" { } `, rName, gatewayTimezone) } + +func testAccAWSStorageGatewayGatewayConfig_SmbActiveDirectorySettings(rName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" {} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags { + Name = %q + } +} + +resource "aws_subnet" "test" { + count = 2 + + availability_zone = "${data.aws_availability_zones.available.names[count.index]}" + cidr_block = "10.0.${count.index}.0/24" + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = %q + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = "${aws_vpc.test.id}" + + tags { + Name = %q + } +} + +resource "aws_route_table" "test" { + vpc_id = "${aws_vpc.test.id}" + + route { + cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.test.id}" + } + + tags { + Name = %q + } +} + +resource "aws_route_table_association" "test" { + count = 2 + + subnet_id = "${aws_subnet.test.*.id[count.index]}" + route_table_id = "${aws_route_table.test.id}" +} + +resource "aws_security_group" "test" { + vpc_id = "${aws_vpc.test.id}" + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags { + Name = %q + } +} + +resource "aws_directory_service_directory" "test" { + name = "terraformtesting.com" + password = "SuperSecretPassw0rd" + size = "Small" + + vpc_settings { + subnet_ids = ["${aws_subnet.test.*.id}"] + vpc_id = "${aws_vpc.test.id}" + } + + tags { + Name = %q + } +} + +resource "aws_vpc_dhcp_options" "test" { + domain_name = "${aws_directory_service_directory.test.name}" + domain_name_servers = ["${aws_directory_service_directory.test.dns_ip_addresses}"] + + tags { + Name = %q + } +} + +resource "aws_vpc_dhcp_options_association" "test" { + dhcp_options_id = "${aws_vpc_dhcp_options.test.id}" + vpc_id = "${aws_vpc.test.id}" +} + +data "aws_ami" "aws-thinstaller" { + most_recent = true + + filter { + name = "owner-alias" + values = ["amazon"] + } + + filter { + name = "name" + values = ["aws-thinstaller-*"] + } +} + +resource "aws_instance" "test" { + depends_on = ["aws_internet_gateway.test", "aws_vpc_dhcp_options_association.test"] + + ami = "${data.aws_ami.aws-thinstaller.id}" + associate_public_ip_address = true + # https://docs.aws.amazon.com/storagegateway/latest/userguide/Requirements.html + instance_type = "m4.xlarge" + vpc_security_group_ids = ["${aws_security_group.test.id}"] + subnet_id = "${aws_subnet.test.*.id[0]}" + + tags { + Name = %q + } +} + +resource "aws_storagegateway_gateway" "test" { + gateway_ip_address = "${aws_instance.test.public_ip}" + gateway_name = %q + gateway_timezone = "GMT" + gateway_type = "FILE_S3" + + smb_active_directory_settings { + domain_name = "${aws_directory_service_directory.test.name}" + password = "${aws_directory_service_directory.test.password}" + username = "Administrator" + } +} +`, rName, rName, rName, rName, rName, rName, rName, rName, rName) +} + +func testAccAWSStorageGatewayGatewayConfig_SmbGuestPassword(rName, smbGuestPassword string) string { + return testAccAWSStorageGateway_FileGatewayBase(rName) + fmt.Sprintf(` +resource "aws_storagegateway_gateway" "test" { + gateway_ip_address = "${aws_instance.test.public_ip}" + gateway_name = %q + gateway_timezone = "GMT" + gateway_type = "FILE_S3" + smb_guest_password = %q +} +`, rName, smbGuestPassword) +} diff --git a/website/docs/r/storagegateway_gateway.html.markdown b/website/docs/r/storagegateway_gateway.html.markdown index caf5291a5a7..ff7df1b619a 100644 --- a/website/docs/r/storagegateway_gateway.html.markdown +++ b/website/docs/r/storagegateway_gateway.html.markdown @@ -72,8 +72,20 @@ The following arguments are supported: * `gateway_ip_address` - (Optional) Gateway IP address to retrieve activation key during resource creation. Conflicts with `activation_key`. Gateway must be accessible on port 80 from where Terraform is running. Additional information is available in the [Storage Gateway User Guide](https://docs.aws.amazon.com/storagegateway/latest/userguide/get-activation-key.html). * `gateway_type` - (Optional) Type of the gateway. The default value is `STORED`. Valid values: `CACHED`, `FILE_S3`, `STORED`, `VTL`. * `media_changer_type` - (Optional) Type of medium changer to use for tape gateway. Terraform cannot detect drift of this argument. Valid values: `STK-L700`, `AWS-Gateway-VTL`. +* `smb_active_directory_settings` - (Optional) Nested argument with Active Directory domain join information for Server Message Block (SMB) file shares. Only valid for `FILE_S3` gateway type. Must be set before creating `ActiveDirectory` authentication SMB file shares. More details below. +* `smb_guest_password` - (Optional) Guest password for Server Message Block (SMB) file shares. Only valid for `FILE_S3` gateway type. Must be set before creating `GuestAccess` authentication SMB file shares. Terraform can only detect drift of the existence of a guest password, not its actual value from the gateway. Terraform can however update the password with changing the argument. * `tape_drive_type` - (Optional) Type of tape drive to use for tape gateway. Terraform cannot detect drift of this argument. Valid values: `IBM-ULT3580-TD5`. +### smb_active_directory_settings + +Information to join the gateway to an Active Directory domain for Server Message Block (SMB) file shares. + +~> **NOTE** It is not possible to unconfigure this setting without recreating the gateway. Also, Terraform can only detect drift of the `domain_name` argument from the gateway. + +* `domain_name` - (Required) The name of the domain that you want the gateway to join. +* `password` - (Required) The password of the user who has permission to add the gateway to the Active Directory domain. +* `username` - (Required) The user name of user who has permission to add the gateway to the Active Directory domain. + ## Attribute Reference In addition to all arguments above, the following attributes are exported: