diff --git a/builtin/providers/aws/resource_aws_directory_service_directory.go b/builtin/providers/aws/resource_aws_directory_service_directory.go index e5065e378..4fe1357ed 100644 --- a/builtin/providers/aws/resource_aws_directory_service_directory.go +++ b/builtin/providers/aws/resource_aws_directory_service_directory.go @@ -13,6 +13,12 @@ import ( "github.com/hashicorp/terraform/helper/resource" ) +var directoryCreationFuncs = map[string]func(*directoryservice.DirectoryService, *schema.ResourceData) (string, error){ + "SimpleAD": createSimpleDirectoryService, + "MicrosoftAD": createActiveDirectoryService, + "ADConnector": createDirectoryConnector, +} + func resourceAwsDirectoryServiceDirectory() *schema.Resource { return &schema.Resource{ Create: resourceAwsDirectoryServiceDirectoryCreate, @@ -33,7 +39,7 @@ func resourceAwsDirectoryServiceDirectory() *schema.Resource { }, "size": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, "alias": &schema.Schema{ @@ -55,7 +61,8 @@ func resourceAwsDirectoryServiceDirectory() *schema.Resource { }, "vpc_settings": &schema.Schema{ Type: schema.TypeList, - Required: true, + Optional: true, + ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "subnet_ids": &schema.Schema{ @@ -73,6 +80,39 @@ func resourceAwsDirectoryServiceDirectory() *schema.Resource { }, }, }, + "connect_settings": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "customer_username": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "customer_dns_ips": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "subnet_ids": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "vpc_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + }, "enable_sso": &schema.Schema{ Type: schema.TypeBool, Optional: true, @@ -90,14 +130,120 @@ func resourceAwsDirectoryServiceDirectory() *schema.Resource { }, "type": &schema.Schema{ Type: schema.TypeString, - Computed: true, + Optional: true, + Default: "SimpleAD", + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { + validTypes := []string{"SimpleAD", "MicrosoftAD"} + value := v.(string) + for validType, _ := range directoryCreationFuncs { + if validType == value { + return + } + } + es = append(es, fmt.Errorf("%q must be one of %q", k, validTypes)) + return + }, }, }, } } -func resourceAwsDirectoryServiceDirectoryCreate(d *schema.ResourceData, meta interface{}) error { - dsconn := meta.(*AWSClient).dsconn +func buildVpcSettings(d *schema.ResourceData) (vpcSettings *directoryservice.DirectoryVpcSettings, err error) { + if v, ok := d.GetOk("vpc_settings"); !ok { + return nil, fmt.Errorf("vpc_settings is required for type = SimpleAD or MicrosoftAD") + } else { + settings := v.([]interface{}) + + if len(settings) > 1 { + return nil, fmt.Errorf("Only a single vpc_settings block is expected") + } else if len(settings) == 1 { + s := settings[0].(map[string]interface{}) + var subnetIds []*string + for _, id := range s["subnet_ids"].(*schema.Set).List() { + subnetIds = append(subnetIds, aws.String(id.(string))) + } + + vpcSettings = &directoryservice.DirectoryVpcSettings{ + SubnetIds: subnetIds, + VpcId: aws.String(s["vpc_id"].(string)), + } + } + } + + return vpcSettings, nil +} + +func buildConnectSettings(d *schema.ResourceData) (connectSettings *directoryservice.DirectoryConnectSettings, err error) { + if v, ok := d.GetOk("connect_settings"); !ok { + return nil, fmt.Errorf("connect_settings is required for type = ADConnector") + } else { + settings := v.([]interface{}) + + if len(settings) > 1 { + return nil, fmt.Errorf("Only a single connect_settings block is expected") + } else if len(settings) == 1 { + s := settings[0].(map[string]interface{}) + + var subnetIds []*string + for _, id := range s["subnet_ids"].(*schema.Set).List() { + subnetIds = append(subnetIds, aws.String(id.(string))) + } + + var customerDnsIps []*string + for _, id := range s["customer_dns_ips"].(*schema.Set).List() { + customerDnsIps = append(customerDnsIps, aws.String(id.(string))) + } + + connectSettings = &directoryservice.DirectoryConnectSettings{ + CustomerDnsIps: customerDnsIps, + CustomerUserName: aws.String(s["customer_username"].(string)), + SubnetIds: subnetIds, + VpcId: aws.String(s["vpc_id"].(string)), + } + } + } + + return connectSettings, nil +} + +func createDirectoryConnector(dsconn *directoryservice.DirectoryService, d *schema.ResourceData) (directoryId string, err error) { + if _, ok := d.GetOk("size"); !ok { + return "", fmt.Errorf("size is required for type = ADConnector") + } + + input := directoryservice.ConnectDirectoryInput{ + Name: aws.String(d.Get("name").(string)), + Password: aws.String(d.Get("password").(string)), + Size: aws.String(d.Get("size").(string)), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + if v, ok := d.GetOk("short_name"); ok { + input.ShortName = aws.String(v.(string)) + } + + input.ConnectSettings, err = buildConnectSettings(d) + if err != nil { + return "", err + } + + log.Printf("[DEBUG] Creating Directory Connector: %s", input) + out, err := dsconn.ConnectDirectory(&input) + if err != nil { + return "", err + } + log.Printf("[DEBUG] Directory Connector created: %s", out) + + return *out.DirectoryId, nil +} + +func createSimpleDirectoryService(dsconn *directoryservice.DirectoryService, d *schema.ResourceData) (directoryId string, err error) { + if _, ok := d.GetOk("size"); !ok { + return "", fmt.Errorf("size is required for type = SimpleAD") + } input := directoryservice.CreateDirectoryInput{ Name: aws.String(d.Get("name").(string)), @@ -112,33 +258,64 @@ func resourceAwsDirectoryServiceDirectoryCreate(d *schema.ResourceData, meta int input.ShortName = aws.String(v.(string)) } - if v, ok := d.GetOk("vpc_settings"); ok { - settings := v.([]interface{}) - - if len(settings) > 1 { - return fmt.Errorf("Only a single vpc_settings block is expected") - } else if len(settings) == 1 { - s := settings[0].(map[string]interface{}) - var subnetIds []*string - for _, id := range s["subnet_ids"].(*schema.Set).List() { - subnetIds = append(subnetIds, aws.String(id.(string))) - } - - vpcSettings := directoryservice.DirectoryVpcSettings{ - SubnetIds: subnetIds, - VpcId: aws.String(s["vpc_id"].(string)), - } - input.VpcSettings = &vpcSettings - } + input.VpcSettings, err = buildVpcSettings(d) + if err != nil { + return "", err } - log.Printf("[DEBUG] Creating Directory Service: %s", input) + log.Printf("[DEBUG] Creating Simple Directory Service: %s", input) out, err := dsconn.CreateDirectory(&input) + if err != nil { + return "", err + } + log.Printf("[DEBUG] Simple Directory Service created: %s", out) + + return *out.DirectoryId, nil +} + +func createActiveDirectoryService(dsconn *directoryservice.DirectoryService, d *schema.ResourceData) (directoryId string, err error) { + input := directoryservice.CreateMicrosoftADInput{ + Name: aws.String(d.Get("name").(string)), + Password: aws.String(d.Get("password").(string)), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + if v, ok := d.GetOk("short_name"); ok { + input.ShortName = aws.String(v.(string)) + } + + input.VpcSettings, err = buildVpcSettings(d) + if err != nil { + return "", err + } + + log.Printf("[DEBUG] Creating Microsoft AD Directory Service: %s", input) + out, err := dsconn.CreateMicrosoftAD(&input) + if err != nil { + return "", err + } + log.Printf("[DEBUG] Microsoft AD Directory Service created: %s", out) + + return *out.DirectoryId, nil +} + +func resourceAwsDirectoryServiceDirectoryCreate(d *schema.ResourceData, meta interface{}) error { + dsconn := meta.(*AWSClient).dsconn + + creationFunc, ok := directoryCreationFuncs[d.Get("type").(string)] + if !ok { + // Shouldn't happen as this is validated above + return fmt.Errorf("Unsupported directory type: %s", d.Get("type")) + } + + directoryId, err := creationFunc(dsconn, d) if err != nil { return err } - log.Printf("[DEBUG] Directory Service created: %s", out) - d.SetId(*out.DirectoryId) + + d.SetId(directoryId) // Wait for creation log.Printf("[DEBUG] Waiting for DS (%q) to become available", d.Id()) @@ -159,7 +336,7 @@ func resourceAwsDirectoryServiceDirectoryCreate(d *schema.ResourceData, meta int d.Id(), *ds.Stage) return ds, *ds.Stage, nil }, - Timeout: 10 * time.Minute, + Timeout: 30 * time.Minute, } if _, err := stateConf.WaitForState(); err != nil { return fmt.Errorf( @@ -234,14 +411,22 @@ func resourceAwsDirectoryServiceDirectoryRead(d *schema.ResourceData, meta inter if dir.Description != nil { d.Set("description", *dir.Description) } - d.Set("dns_ip_addresses", schema.NewSet(schema.HashString, flattenStringList(dir.DnsIpAddrs))) + + if *dir.Type == "ADConnector" { + d.Set("dns_ip_addresses", schema.NewSet(schema.HashString, flattenStringList(dir.ConnectSettings.ConnectIps))) + } else { + d.Set("dns_ip_addresses", schema.NewSet(schema.HashString, flattenStringList(dir.DnsIpAddrs))) + } d.Set("name", *dir.Name) if dir.ShortName != nil { d.Set("short_name", *dir.ShortName) } - d.Set("size", *dir.Size) + if dir.Size != nil { + d.Set("size", *dir.Size) + } d.Set("type", *dir.Type) d.Set("vpc_settings", flattenDSVpcSettings(dir.VpcSettings)) + d.Set("connect_settings", flattenDSConnectSettings(dir.DnsIpAddrs, dir.ConnectSettings)) d.Set("enable_sso", *dir.SsoEnabled) return nil @@ -285,7 +470,7 @@ func resourceAwsDirectoryServiceDirectoryDelete(d *schema.ResourceData, meta int d.Id(), *ds.Stage) return ds, *ds.Stage, nil }, - Timeout: 10 * time.Minute, + Timeout: 30 * time.Minute, } if _, err := stateConf.WaitForState(); err != nil { return fmt.Errorf( diff --git a/builtin/providers/aws/resource_aws_directory_service_directory_test.go b/builtin/providers/aws/resource_aws_directory_service_directory_test.go index fefdeb751..779e56df4 100644 --- a/builtin/providers/aws/resource_aws_directory_service_directory_test.go +++ b/builtin/providers/aws/resource_aws_directory_service_directory_test.go @@ -28,6 +28,38 @@ func TestAccAWSDirectoryServiceDirectory_basic(t *testing.T) { }) } +func TestAccAWSDirectoryServiceDirectory_microsoft(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDirectoryServiceDirectoryDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDirectoryServiceDirectoryConfig_microsoft, + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceDirectoryExists("aws_directory_service_directory.bar"), + ), + }, + }, + }) +} + +func TestAccAWSDirectoryServiceDirectory_connector(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDirectoryServiceDirectoryDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDirectoryServiceDirectoryConfig_connector, + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceDirectoryExists("aws_directory_service_directory.connector"), + ), + }, + }, + }) +} + func TestAccAWSDirectoryServiceDirectory_withAliasAndSso(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -214,6 +246,76 @@ resource "aws_subnet" "bar" { } ` +const testAccDirectoryServiceDirectoryConfig_connector = ` +resource "aws_directory_service_directory" "bar" { + name = "corp.notexample.com" + password = "SuperSecretPassw0rd" + size = "Small" + + vpc_settings { + vpc_id = "${aws_vpc.main.id}" + subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"] + } +} + +resource "aws_directory_service_directory" "connector" { + name = "corp.notexample.com" + password = "SuperSecretPassw0rd" + size = "Small" + type = "ADConnector" + + connect_settings { + customer_dns_ips = ["${aws_directory_service_directory.bar.dns_ip_addresses}"] + customer_username = "Administrator" + vpc_id = "${aws_vpc.main.id}" + subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"] + } +} + +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.main.id}" + availability_zone = "us-west-2a" + cidr_block = "10.0.1.0/24" +} +resource "aws_subnet" "bar" { + vpc_id = "${aws_vpc.main.id}" + availability_zone = "us-west-2b" + cidr_block = "10.0.2.0/24" +} +` + +const testAccDirectoryServiceDirectoryConfig_microsoft = ` +resource "aws_directory_service_directory" "bar" { + name = "corp.notexample.com" + password = "SuperSecretPassw0rd" + type = "MicrosoftAD" + + vpc_settings { + vpc_id = "${aws_vpc.main.id}" + subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"] + } +} + +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "foo" { + vpc_id = "${aws_vpc.main.id}" + availability_zone = "us-west-2a" + cidr_block = "10.0.1.0/24" +} +resource "aws_subnet" "bar" { + vpc_id = "${aws_vpc.main.id}" + availability_zone = "us-west-2b" + cidr_block = "10.0.2.0/24" +} +` + var randomInteger = genRandInt() var testAccDirectoryServiceDirectoryConfig_withAlias = fmt.Sprintf(` resource "aws_directory_service_directory" "bar_a" { diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 35b7ba4e1..263386350 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -629,6 +629,28 @@ func flattenDSVpcSettings( s *directoryservice.DirectoryVpcSettingsDescription) []map[string]interface{} { settings := make(map[string]interface{}, 0) + if s == nil { + return nil + } + + settings["subnet_ids"] = schema.NewSet(schema.HashString, flattenStringList(s.SubnetIds)) + settings["vpc_id"] = *s.VpcId + + return []map[string]interface{}{settings} +} + +func flattenDSConnectSettings( + customerDnsIps []*string, + s *directoryservice.DirectoryConnectSettingsDescription) []map[string]interface{} { + if s == nil { + return nil + } + + settings := make(map[string]interface{}, 0) + + settings["customer_dns_ips"] = schema.NewSet(schema.HashString, flattenStringList(customerDnsIps)) + settings["connect_ips"] = schema.NewSet(schema.HashString, flattenStringList(s.ConnectIps)) + settings["customer_username"] = *s.CustomerUserName settings["subnet_ids"] = schema.NewSet(schema.HashString, flattenStringList(s.SubnetIds)) settings["vpc_id"] = *s.VpcId diff --git a/website/source/docs/providers/aws/r/directory_service_directory.html.markdown b/website/source/docs/providers/aws/r/directory_service_directory.html.markdown index 04049ee55..83f07649b 100644 --- a/website/source/docs/providers/aws/r/directory_service_directory.html.markdown +++ b/website/source/docs/providers/aws/r/directory_service_directory.html.markdown @@ -8,7 +8,7 @@ description: |- # aws\_directory\_service\_directory -Provides a directory in AWS Directory Service. +Provides a Simple or Managed Microsoft directory in AWS Directory Service. ## Example Usage @@ -45,24 +45,32 @@ resource "aws_subnet" "bar" { The following arguments are supported: * `name` - (Required) The fully qualified name for the directory, such as `corp.example.com` -* `password` - (Required) The password for the directory administrator. -* `size` - (Required) The size of the directory (`Small` or `Large` are accepted values). -* `vpc_settings` - (Required) VPC related information about the directory. Fields documented below. +* `password` - (Required) The password for the directory administrator or connector user. +* `size` - (Required for `SimpleAD` and `ADConnector`) The size of the directory (`Small` or `Large` are accepted values). +* `vpc_settings` - (Required for `SimpleAD` and `MicrosoftAD`) VPC related information about the directory. Fields documented below. +* `connect_settings` - (Required for `ADConnector`) Connector related information about the directory. Fields documented below. * `alias` - (Optional) The alias for the directory (must be unique amongst all aliases in AWS). Required for `enable_sso`. * `description` - (Optional) A textual description for the directory. * `short_name` - (Optional) The short name of the directory, such as `CORP`. * `enable_sso` - (Optional) Whether to enable single-sign on for the directory. Requires `alias`. Defaults to `false`. +* `type` (Optional) - The directory type (`SimpleAD` or `MicrosoftAD` are accepted values). Defaults to `SimpleAD`. **vpc\_settings** supports the following: * `subnet_ids` - (Required) The identifiers of the subnets for the directory servers (min. 2 subnets in 2 different AZs). * `vpc_id` - (Required) The identifier of the VPC that the directory is in. +**connect\_settings** supports the following: + +* `customer_username` - (Required) The username corresponding to the password provided. +* `customer_dns_ips` - (Required) The DNS IP addresses of the domain to connect to. +* `subnet_ids` - (Required) The identifiers of the subnets for the directory servers (min. 2 subnets in 2 different AZs). +* `vpc_id` - (Required) The identifier of the VPC that the directory is in. + ## Attributes Reference The following attributes are exported: * `id` - The directory identifier. * `access_url` - The access URL for the directory, such as `http://alias.awsapps.com`. -* `dns_ip_addresses` - A list of IP addresses of the DNS servers for the directory. -* `type` - The directory type. +* `dns_ip_addresses` - A list of IP addresses of the DNS servers for the directory or connector.