github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_directory_service_directory.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/hashicorp/terraform/helper/schema"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	"github.com/aws/aws-sdk-go/service/directoryservice"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  )
    15  
    16  var directoryCreationFuncs = map[string]func(*directoryservice.DirectoryService, *schema.ResourceData) (string, error){
    17  	"SimpleAD":    createSimpleDirectoryService,
    18  	"MicrosoftAD": createActiveDirectoryService,
    19  	"ADConnector": createDirectoryConnector,
    20  }
    21  
    22  func resourceAwsDirectoryServiceDirectory() *schema.Resource {
    23  	return &schema.Resource{
    24  		Create: resourceAwsDirectoryServiceDirectoryCreate,
    25  		Read:   resourceAwsDirectoryServiceDirectoryRead,
    26  		Update: resourceAwsDirectoryServiceDirectoryUpdate,
    27  		Delete: resourceAwsDirectoryServiceDirectoryDelete,
    28  
    29  		Schema: map[string]*schema.Schema{
    30  			"name": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  				ForceNew: true,
    34  			},
    35  			"password": &schema.Schema{
    36  				Type:      schema.TypeString,
    37  				Required:  true,
    38  				ForceNew:  true,
    39  				Sensitive: true,
    40  			},
    41  			"size": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Optional: true,
    44  				ForceNew: true,
    45  			},
    46  			"alias": &schema.Schema{
    47  				Type:     schema.TypeString,
    48  				Optional: true,
    49  				Computed: true,
    50  				ForceNew: true,
    51  			},
    52  			"description": &schema.Schema{
    53  				Type:     schema.TypeString,
    54  				Optional: true,
    55  				ForceNew: true,
    56  			},
    57  			"short_name": &schema.Schema{
    58  				Type:     schema.TypeString,
    59  				Optional: true,
    60  				Computed: true,
    61  				ForceNew: true,
    62  			},
    63  			"vpc_settings": &schema.Schema{
    64  				Type:     schema.TypeList,
    65  				Optional: true,
    66  				ForceNew: true,
    67  				Elem: &schema.Resource{
    68  					Schema: map[string]*schema.Schema{
    69  						"subnet_ids": &schema.Schema{
    70  							Type:     schema.TypeSet,
    71  							Required: true,
    72  							ForceNew: true,
    73  							Elem:     &schema.Schema{Type: schema.TypeString},
    74  							Set:      schema.HashString,
    75  						},
    76  						"vpc_id": &schema.Schema{
    77  							Type:     schema.TypeString,
    78  							Required: true,
    79  							ForceNew: true,
    80  						},
    81  					},
    82  				},
    83  			},
    84  			"connect_settings": &schema.Schema{
    85  				Type:     schema.TypeList,
    86  				Optional: true,
    87  				ForceNew: true,
    88  				Elem: &schema.Resource{
    89  					Schema: map[string]*schema.Schema{
    90  						"customer_username": &schema.Schema{
    91  							Type:     schema.TypeString,
    92  							Required: true,
    93  							ForceNew: true,
    94  						},
    95  						"customer_dns_ips": &schema.Schema{
    96  							Type:     schema.TypeSet,
    97  							Required: true,
    98  							ForceNew: true,
    99  							Elem:     &schema.Schema{Type: schema.TypeString},
   100  							Set:      schema.HashString,
   101  						},
   102  						"subnet_ids": &schema.Schema{
   103  							Type:     schema.TypeSet,
   104  							Required: true,
   105  							ForceNew: true,
   106  							Elem:     &schema.Schema{Type: schema.TypeString},
   107  							Set:      schema.HashString,
   108  						},
   109  						"vpc_id": &schema.Schema{
   110  							Type:     schema.TypeString,
   111  							Required: true,
   112  							ForceNew: true,
   113  						},
   114  					},
   115  				},
   116  			},
   117  			"enable_sso": &schema.Schema{
   118  				Type:     schema.TypeBool,
   119  				Optional: true,
   120  				Default:  false,
   121  			},
   122  			"access_url": &schema.Schema{
   123  				Type:     schema.TypeString,
   124  				Computed: true,
   125  			},
   126  			"dns_ip_addresses": &schema.Schema{
   127  				Type:     schema.TypeSet,
   128  				Elem:     &schema.Schema{Type: schema.TypeString},
   129  				Set:      schema.HashString,
   130  				Computed: true,
   131  			},
   132  			"type": &schema.Schema{
   133  				Type:     schema.TypeString,
   134  				Optional: true,
   135  				Default:  "SimpleAD",
   136  				ForceNew: true,
   137  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   138  					validTypes := []string{"SimpleAD", "MicrosoftAD"}
   139  					value := v.(string)
   140  					for validType, _ := range directoryCreationFuncs {
   141  						if validType == value {
   142  							return
   143  						}
   144  					}
   145  					es = append(es, fmt.Errorf("%q must be one of %q", k, validTypes))
   146  					return
   147  				},
   148  			},
   149  		},
   150  	}
   151  }
   152  
   153  func buildVpcSettings(d *schema.ResourceData) (vpcSettings *directoryservice.DirectoryVpcSettings, err error) {
   154  	if v, ok := d.GetOk("vpc_settings"); !ok {
   155  		return nil, fmt.Errorf("vpc_settings is required for type = SimpleAD or MicrosoftAD")
   156  	} else {
   157  		settings := v.([]interface{})
   158  
   159  		if len(settings) > 1 {
   160  			return nil, fmt.Errorf("Only a single vpc_settings block is expected")
   161  		} else if len(settings) == 1 {
   162  			s := settings[0].(map[string]interface{})
   163  			var subnetIds []*string
   164  			for _, id := range s["subnet_ids"].(*schema.Set).List() {
   165  				subnetIds = append(subnetIds, aws.String(id.(string)))
   166  			}
   167  
   168  			vpcSettings = &directoryservice.DirectoryVpcSettings{
   169  				SubnetIds: subnetIds,
   170  				VpcId:     aws.String(s["vpc_id"].(string)),
   171  			}
   172  		}
   173  	}
   174  
   175  	return vpcSettings, nil
   176  }
   177  
   178  func buildConnectSettings(d *schema.ResourceData) (connectSettings *directoryservice.DirectoryConnectSettings, err error) {
   179  	if v, ok := d.GetOk("connect_settings"); !ok {
   180  		return nil, fmt.Errorf("connect_settings is required for type = ADConnector")
   181  	} else {
   182  		settings := v.([]interface{})
   183  
   184  		if len(settings) > 1 {
   185  			return nil, fmt.Errorf("Only a single connect_settings block is expected")
   186  		} else if len(settings) == 1 {
   187  			s := settings[0].(map[string]interface{})
   188  
   189  			var subnetIds []*string
   190  			for _, id := range s["subnet_ids"].(*schema.Set).List() {
   191  				subnetIds = append(subnetIds, aws.String(id.(string)))
   192  			}
   193  
   194  			var customerDnsIps []*string
   195  			for _, id := range s["customer_dns_ips"].(*schema.Set).List() {
   196  				customerDnsIps = append(customerDnsIps, aws.String(id.(string)))
   197  			}
   198  
   199  			connectSettings = &directoryservice.DirectoryConnectSettings{
   200  				CustomerDnsIps:   customerDnsIps,
   201  				CustomerUserName: aws.String(s["customer_username"].(string)),
   202  				SubnetIds:        subnetIds,
   203  				VpcId:            aws.String(s["vpc_id"].(string)),
   204  			}
   205  		}
   206  	}
   207  
   208  	return connectSettings, nil
   209  }
   210  
   211  func createDirectoryConnector(dsconn *directoryservice.DirectoryService, d *schema.ResourceData) (directoryId string, err error) {
   212  	if _, ok := d.GetOk("size"); !ok {
   213  		return "", fmt.Errorf("size is required for type = ADConnector")
   214  	}
   215  
   216  	input := directoryservice.ConnectDirectoryInput{
   217  		Name:     aws.String(d.Get("name").(string)),
   218  		Password: aws.String(d.Get("password").(string)),
   219  		Size:     aws.String(d.Get("size").(string)),
   220  	}
   221  
   222  	if v, ok := d.GetOk("description"); ok {
   223  		input.Description = aws.String(v.(string))
   224  	}
   225  	if v, ok := d.GetOk("short_name"); ok {
   226  		input.ShortName = aws.String(v.(string))
   227  	}
   228  
   229  	input.ConnectSettings, err = buildConnectSettings(d)
   230  	if err != nil {
   231  		return "", err
   232  	}
   233  
   234  	log.Printf("[DEBUG] Creating Directory Connector: %s", input)
   235  	out, err := dsconn.ConnectDirectory(&input)
   236  	if err != nil {
   237  		return "", err
   238  	}
   239  	log.Printf("[DEBUG] Directory Connector created: %s", out)
   240  
   241  	return *out.DirectoryId, nil
   242  }
   243  
   244  func createSimpleDirectoryService(dsconn *directoryservice.DirectoryService, d *schema.ResourceData) (directoryId string, err error) {
   245  	if _, ok := d.GetOk("size"); !ok {
   246  		return "", fmt.Errorf("size is required for type = SimpleAD")
   247  	}
   248  
   249  	input := directoryservice.CreateDirectoryInput{
   250  		Name:     aws.String(d.Get("name").(string)),
   251  		Password: aws.String(d.Get("password").(string)),
   252  		Size:     aws.String(d.Get("size").(string)),
   253  	}
   254  
   255  	if v, ok := d.GetOk("description"); ok {
   256  		input.Description = aws.String(v.(string))
   257  	}
   258  	if v, ok := d.GetOk("short_name"); ok {
   259  		input.ShortName = aws.String(v.(string))
   260  	}
   261  
   262  	input.VpcSettings, err = buildVpcSettings(d)
   263  	if err != nil {
   264  		return "", err
   265  	}
   266  
   267  	log.Printf("[DEBUG] Creating Simple Directory Service: %s", input)
   268  	out, err := dsconn.CreateDirectory(&input)
   269  	if err != nil {
   270  		return "", err
   271  	}
   272  	log.Printf("[DEBUG] Simple Directory Service created: %s", out)
   273  
   274  	return *out.DirectoryId, nil
   275  }
   276  
   277  func createActiveDirectoryService(dsconn *directoryservice.DirectoryService, d *schema.ResourceData) (directoryId string, err error) {
   278  	input := directoryservice.CreateMicrosoftADInput{
   279  		Name:     aws.String(d.Get("name").(string)),
   280  		Password: aws.String(d.Get("password").(string)),
   281  	}
   282  
   283  	if v, ok := d.GetOk("description"); ok {
   284  		input.Description = aws.String(v.(string))
   285  	}
   286  	if v, ok := d.GetOk("short_name"); ok {
   287  		input.ShortName = aws.String(v.(string))
   288  	}
   289  
   290  	input.VpcSettings, err = buildVpcSettings(d)
   291  	if err != nil {
   292  		return "", err
   293  	}
   294  
   295  	log.Printf("[DEBUG] Creating Microsoft AD Directory Service: %s", input)
   296  	out, err := dsconn.CreateMicrosoftAD(&input)
   297  	if err != nil {
   298  		return "", err
   299  	}
   300  	log.Printf("[DEBUG] Microsoft AD Directory Service created: %s", out)
   301  
   302  	return *out.DirectoryId, nil
   303  }
   304  
   305  func resourceAwsDirectoryServiceDirectoryCreate(d *schema.ResourceData, meta interface{}) error {
   306  	dsconn := meta.(*AWSClient).dsconn
   307  
   308  	creationFunc, ok := directoryCreationFuncs[d.Get("type").(string)]
   309  	if !ok {
   310  		// Shouldn't happen as this is validated above
   311  		return fmt.Errorf("Unsupported directory type: %s", d.Get("type"))
   312  	}
   313  
   314  	directoryId, err := creationFunc(dsconn, d)
   315  	if err != nil {
   316  		return err
   317  	}
   318  
   319  	d.SetId(directoryId)
   320  
   321  	// Wait for creation
   322  	log.Printf("[DEBUG] Waiting for DS (%q) to become available", d.Id())
   323  	stateConf := &resource.StateChangeConf{
   324  		Pending: []string{"Requested", "Creating", "Created"},
   325  		Target:  []string{"Active"},
   326  		Refresh: func() (interface{}, string, error) {
   327  			resp, err := dsconn.DescribeDirectories(&directoryservice.DescribeDirectoriesInput{
   328  				DirectoryIds: []*string{aws.String(d.Id())},
   329  			})
   330  			if err != nil {
   331  				log.Printf("Error during creation of DS: %q", err.Error())
   332  				return nil, "", err
   333  			}
   334  
   335  			ds := resp.DirectoryDescriptions[0]
   336  			log.Printf("[DEBUG] Creation of DS %q is in following stage: %q.",
   337  				d.Id(), *ds.Stage)
   338  			return ds, *ds.Stage, nil
   339  		},
   340  		Timeout: 60 * time.Minute,
   341  	}
   342  	if _, err := stateConf.WaitForState(); err != nil {
   343  		return fmt.Errorf(
   344  			"Error waiting for Directory Service (%s) to become available: %s",
   345  			d.Id(), err)
   346  	}
   347  
   348  	if v, ok := d.GetOk("alias"); ok {
   349  		d.SetPartial("alias")
   350  
   351  		input := directoryservice.CreateAliasInput{
   352  			DirectoryId: aws.String(d.Id()),
   353  			Alias:       aws.String(v.(string)),
   354  		}
   355  
   356  		log.Printf("[DEBUG] Assigning alias %q to DS directory %q",
   357  			v.(string), d.Id())
   358  		out, err := dsconn.CreateAlias(&input)
   359  		if err != nil {
   360  			return err
   361  		}
   362  		log.Printf("[DEBUG] Alias %q assigned to DS directory %q",
   363  			*out.Alias, *out.DirectoryId)
   364  	}
   365  
   366  	return resourceAwsDirectoryServiceDirectoryUpdate(d, meta)
   367  }
   368  
   369  func resourceAwsDirectoryServiceDirectoryUpdate(d *schema.ResourceData, meta interface{}) error {
   370  	dsconn := meta.(*AWSClient).dsconn
   371  
   372  	if d.HasChange("enable_sso") {
   373  		d.SetPartial("enable_sso")
   374  		var err error
   375  
   376  		if v, ok := d.GetOk("enable_sso"); ok && v.(bool) {
   377  			log.Printf("[DEBUG] Enabling SSO for DS directory %q", d.Id())
   378  			_, err = dsconn.EnableSso(&directoryservice.EnableSsoInput{
   379  				DirectoryId: aws.String(d.Id()),
   380  			})
   381  		} else {
   382  			log.Printf("[DEBUG] Disabling SSO for DS directory %q", d.Id())
   383  			_, err = dsconn.DisableSso(&directoryservice.DisableSsoInput{
   384  				DirectoryId: aws.String(d.Id()),
   385  			})
   386  		}
   387  
   388  		if err != nil {
   389  			return err
   390  		}
   391  	}
   392  
   393  	return resourceAwsDirectoryServiceDirectoryRead(d, meta)
   394  }
   395  
   396  func resourceAwsDirectoryServiceDirectoryRead(d *schema.ResourceData, meta interface{}) error {
   397  	dsconn := meta.(*AWSClient).dsconn
   398  
   399  	input := directoryservice.DescribeDirectoriesInput{
   400  		DirectoryIds: []*string{aws.String(d.Id())},
   401  	}
   402  	out, err := dsconn.DescribeDirectories(&input)
   403  	if err != nil {
   404  		return err
   405  
   406  	}
   407  
   408  	if len(out.DirectoryDescriptions) == 0 {
   409  		log.Printf("[WARN] Directory %s not found", d.Id())
   410  		d.SetId("")
   411  		return nil
   412  	}
   413  
   414  	dir := out.DirectoryDescriptions[0]
   415  	log.Printf("[DEBUG] Received DS directory: %s", dir)
   416  
   417  	d.Set("access_url", *dir.AccessUrl)
   418  	d.Set("alias", *dir.Alias)
   419  	if dir.Description != nil {
   420  		d.Set("description", *dir.Description)
   421  	}
   422  
   423  	if *dir.Type == "ADConnector" {
   424  		d.Set("dns_ip_addresses", schema.NewSet(schema.HashString, flattenStringList(dir.ConnectSettings.ConnectIps)))
   425  	} else {
   426  		d.Set("dns_ip_addresses", schema.NewSet(schema.HashString, flattenStringList(dir.DnsIpAddrs)))
   427  	}
   428  	d.Set("name", *dir.Name)
   429  	if dir.ShortName != nil {
   430  		d.Set("short_name", *dir.ShortName)
   431  	}
   432  	if dir.Size != nil {
   433  		d.Set("size", *dir.Size)
   434  	}
   435  	d.Set("type", *dir.Type)
   436  	d.Set("vpc_settings", flattenDSVpcSettings(dir.VpcSettings))
   437  	d.Set("connect_settings", flattenDSConnectSettings(dir.DnsIpAddrs, dir.ConnectSettings))
   438  	d.Set("enable_sso", *dir.SsoEnabled)
   439  
   440  	return nil
   441  }
   442  
   443  func resourceAwsDirectoryServiceDirectoryDelete(d *schema.ResourceData, meta interface{}) error {
   444  	dsconn := meta.(*AWSClient).dsconn
   445  
   446  	input := directoryservice.DeleteDirectoryInput{
   447  		DirectoryId: aws.String(d.Id()),
   448  	}
   449  
   450  	log.Printf("[DEBUG] Delete Directory input: %s", input)
   451  	_, err := dsconn.DeleteDirectory(&input)
   452  	if err != nil {
   453  		return err
   454  	}
   455  
   456  	// Wait for deletion
   457  	log.Printf("[DEBUG] Waiting for DS (%q) to be deleted", d.Id())
   458  	stateConf := &resource.StateChangeConf{
   459  		Pending: []string{"Deleting"},
   460  		Target:  []string{"Deleted"},
   461  		Refresh: func() (interface{}, string, error) {
   462  			resp, err := dsconn.DescribeDirectories(&directoryservice.DescribeDirectoriesInput{
   463  				DirectoryIds: []*string{aws.String(d.Id())},
   464  			})
   465  			if err != nil {
   466  				if dserr, ok := err.(awserr.Error); ok && dserr.Code() == "EntityDoesNotExistException" {
   467  					return 42, "Deleted", nil
   468  				}
   469  				return nil, "error", err
   470  			}
   471  
   472  			if len(resp.DirectoryDescriptions) == 0 {
   473  				return 42, "Deleted", nil
   474  			}
   475  
   476  			ds := resp.DirectoryDescriptions[0]
   477  			log.Printf("[DEBUG] Deletion of DS %q is in following stage: %q.",
   478  				d.Id(), *ds.Stage)
   479  			return ds, *ds.Stage, nil
   480  		},
   481  		Timeout: 60 * time.Minute,
   482  	}
   483  	if _, err := stateConf.WaitForState(); err != nil {
   484  		return fmt.Errorf(
   485  			"Error waiting for Directory Service (%s) to be deleted: %q",
   486  			d.Id(), err.Error())
   487  	}
   488  
   489  	return nil
   490  }