github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/builtin/providers/aws/resource_aws_elasticsearch_domain.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"regexp"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice"
    12  	"github.com/hashicorp/errwrap"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsElasticSearchDomain() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsElasticSearchDomainCreate,
    20  		Read:   resourceAwsElasticSearchDomainRead,
    21  		Update: resourceAwsElasticSearchDomainUpdate,
    22  		Delete: resourceAwsElasticSearchDomainDelete,
    23  		Importer: &schema.ResourceImporter{
    24  			State: resourceAwsElasticSearchDomainImport,
    25  		},
    26  
    27  		Schema: map[string]*schema.Schema{
    28  			"access_policies": {
    29  				Type:             schema.TypeString,
    30  				Optional:         true,
    31  				Computed:         true,
    32  				ValidateFunc:     validateJsonString,
    33  				DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs,
    34  			},
    35  			"advanced_options": {
    36  				Type:     schema.TypeMap,
    37  				Optional: true,
    38  				Computed: true,
    39  			},
    40  			"domain_name": {
    41  				Type:     schema.TypeString,
    42  				Required: true,
    43  				ForceNew: true,
    44  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    45  					value := v.(string)
    46  					if !regexp.MustCompile(`^[a-z][0-9a-z\-]{2,27}$`).MatchString(value) {
    47  						errors = append(errors, fmt.Errorf(
    48  							"%q must start with a lowercase alphabet and be at least 3 and no more than 28 characters long. Valid characters are a-z (lowercase letters), 0-9, and - (hyphen).", k))
    49  					}
    50  					return
    51  				},
    52  			},
    53  			"arn": {
    54  				Type:     schema.TypeString,
    55  				Computed: true,
    56  			},
    57  			"domain_id": {
    58  				Type:     schema.TypeString,
    59  				Computed: true,
    60  			},
    61  			"endpoint": {
    62  				Type:     schema.TypeString,
    63  				Computed: true,
    64  			},
    65  			"ebs_options": {
    66  				Type:     schema.TypeList,
    67  				Optional: true,
    68  				Computed: true,
    69  				Elem: &schema.Resource{
    70  					Schema: map[string]*schema.Schema{
    71  						"ebs_enabled": {
    72  							Type:     schema.TypeBool,
    73  							Required: true,
    74  						},
    75  						"iops": {
    76  							Type:     schema.TypeInt,
    77  							Optional: true,
    78  						},
    79  						"volume_size": {
    80  							Type:     schema.TypeInt,
    81  							Optional: true,
    82  						},
    83  						"volume_type": {
    84  							Type:     schema.TypeString,
    85  							Optional: true,
    86  							Computed: true,
    87  						},
    88  					},
    89  				},
    90  			},
    91  			"cluster_config": {
    92  				Type:     schema.TypeList,
    93  				Optional: true,
    94  				Computed: true,
    95  				Elem: &schema.Resource{
    96  					Schema: map[string]*schema.Schema{
    97  						"dedicated_master_count": {
    98  							Type:     schema.TypeInt,
    99  							Optional: true,
   100  						},
   101  						"dedicated_master_enabled": {
   102  							Type:     schema.TypeBool,
   103  							Optional: true,
   104  							Default:  false,
   105  						},
   106  						"dedicated_master_type": {
   107  							Type:     schema.TypeString,
   108  							Optional: true,
   109  						},
   110  						"instance_count": {
   111  							Type:     schema.TypeInt,
   112  							Optional: true,
   113  							Default:  1,
   114  						},
   115  						"instance_type": {
   116  							Type:     schema.TypeString,
   117  							Optional: true,
   118  							Default:  "m3.medium.elasticsearch",
   119  						},
   120  						"zone_awareness_enabled": {
   121  							Type:     schema.TypeBool,
   122  							Optional: true,
   123  						},
   124  					},
   125  				},
   126  			},
   127  			"snapshot_options": {
   128  				Type:     schema.TypeList,
   129  				Optional: true,
   130  				Elem: &schema.Resource{
   131  					Schema: map[string]*schema.Schema{
   132  						"automated_snapshot_start_hour": {
   133  							Type:     schema.TypeInt,
   134  							Required: true,
   135  						},
   136  					},
   137  				},
   138  			},
   139  			"elasticsearch_version": {
   140  				Type:     schema.TypeString,
   141  				Optional: true,
   142  				Default:  "1.5",
   143  				ForceNew: true,
   144  			},
   145  
   146  			"tags": tagsSchema(),
   147  		},
   148  	}
   149  }
   150  
   151  func resourceAwsElasticSearchDomainImport(
   152  	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   153  	d.Set("domain_name", d.Id())
   154  	return []*schema.ResourceData{d}, nil
   155  }
   156  
   157  func resourceAwsElasticSearchDomainCreate(d *schema.ResourceData, meta interface{}) error {
   158  	conn := meta.(*AWSClient).esconn
   159  
   160  	input := elasticsearch.CreateElasticsearchDomainInput{
   161  		DomainName:           aws.String(d.Get("domain_name").(string)),
   162  		ElasticsearchVersion: aws.String(d.Get("elasticsearch_version").(string)),
   163  	}
   164  
   165  	if v, ok := d.GetOk("access_policies"); ok {
   166  		input.AccessPolicies = aws.String(v.(string))
   167  	}
   168  
   169  	if v, ok := d.GetOk("advanced_options"); ok {
   170  		input.AdvancedOptions = stringMapToPointers(v.(map[string]interface{}))
   171  	}
   172  
   173  	if v, ok := d.GetOk("ebs_options"); ok {
   174  		options := v.([]interface{})
   175  
   176  		if len(options) > 1 {
   177  			return fmt.Errorf("Only a single ebs_options block is expected")
   178  		} else if len(options) == 1 {
   179  			if options[0] == nil {
   180  				return fmt.Errorf("At least one field is expected inside ebs_options")
   181  			}
   182  
   183  			s := options[0].(map[string]interface{})
   184  			input.EBSOptions = expandESEBSOptions(s)
   185  		}
   186  	}
   187  
   188  	if v, ok := d.GetOk("cluster_config"); ok {
   189  		config := v.([]interface{})
   190  
   191  		if len(config) > 1 {
   192  			return fmt.Errorf("Only a single cluster_config block is expected")
   193  		} else if len(config) == 1 {
   194  			if config[0] == nil {
   195  				return fmt.Errorf("At least one field is expected inside cluster_config")
   196  			}
   197  			m := config[0].(map[string]interface{})
   198  			input.ElasticsearchClusterConfig = expandESClusterConfig(m)
   199  		}
   200  	}
   201  
   202  	if v, ok := d.GetOk("snapshot_options"); ok {
   203  		options := v.([]interface{})
   204  
   205  		if len(options) > 1 {
   206  			return fmt.Errorf("Only a single snapshot_options block is expected")
   207  		} else if len(options) == 1 {
   208  			if options[0] == nil {
   209  				return fmt.Errorf("At least one field is expected inside snapshot_options")
   210  			}
   211  
   212  			o := options[0].(map[string]interface{})
   213  
   214  			snapshotOptions := elasticsearch.SnapshotOptions{
   215  				AutomatedSnapshotStartHour: aws.Int64(int64(o["automated_snapshot_start_hour"].(int))),
   216  			}
   217  
   218  			input.SnapshotOptions = &snapshotOptions
   219  		}
   220  	}
   221  
   222  	log.Printf("[DEBUG] Creating ElasticSearch domain: %s", input)
   223  	out, err := conn.CreateElasticsearchDomain(&input)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	d.SetId(*out.DomainStatus.ARN)
   229  
   230  	log.Printf("[DEBUG] Waiting for ElasticSearch domain %q to be created", d.Id())
   231  	err = resource.Retry(60*time.Minute, func() *resource.RetryError {
   232  		out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{
   233  			DomainName: aws.String(d.Get("domain_name").(string)),
   234  		})
   235  		if err != nil {
   236  			return resource.NonRetryableError(err)
   237  		}
   238  
   239  		if !*out.DomainStatus.Processing && out.DomainStatus.Endpoint != nil {
   240  			return nil
   241  		}
   242  
   243  		return resource.RetryableError(
   244  			fmt.Errorf("%q: Timeout while waiting for the domain to be created", d.Id()))
   245  	})
   246  	if err != nil {
   247  		return err
   248  	}
   249  
   250  	tags := tagsFromMapElasticsearchService(d.Get("tags").(map[string]interface{}))
   251  
   252  	if err := setTagsElasticsearchService(conn, d, *out.DomainStatus.ARN); err != nil {
   253  		return err
   254  	}
   255  
   256  	d.Set("tags", tagsToMapElasticsearchService(tags))
   257  	d.SetPartial("tags")
   258  	d.Partial(false)
   259  
   260  	log.Printf("[DEBUG] ElasticSearch domain %q created", d.Id())
   261  
   262  	return resourceAwsElasticSearchDomainRead(d, meta)
   263  }
   264  
   265  func resourceAwsElasticSearchDomainRead(d *schema.ResourceData, meta interface{}) error {
   266  	conn := meta.(*AWSClient).esconn
   267  
   268  	out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{
   269  		DomainName: aws.String(d.Get("domain_name").(string)),
   270  	})
   271  	if err != nil {
   272  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "ResourceNotFoundException" {
   273  			log.Printf("[INFO] ElasticSearch Domain %q not found", d.Get("domain_name").(string))
   274  			d.SetId("")
   275  			return nil
   276  		}
   277  		return err
   278  	}
   279  
   280  	log.Printf("[DEBUG] Received ElasticSearch domain: %s", out)
   281  
   282  	ds := out.DomainStatus
   283  
   284  	if ds.AccessPolicies != nil && *ds.AccessPolicies != "" {
   285  		policies, err := normalizeJsonString(*ds.AccessPolicies)
   286  		if err != nil {
   287  			return errwrap.Wrapf("access policies contain an invalid JSON: {{err}}", err)
   288  		}
   289  		d.Set("access_policies", policies)
   290  	}
   291  	err = d.Set("advanced_options", pointersMapToStringList(ds.AdvancedOptions))
   292  	if err != nil {
   293  		return err
   294  	}
   295  	d.SetId(*ds.ARN)
   296  	d.Set("domain_id", ds.DomainId)
   297  	d.Set("domain_name", ds.DomainName)
   298  	d.Set("elasticsearch_version", ds.ElasticsearchVersion)
   299  	if ds.Endpoint != nil {
   300  		d.Set("endpoint", *ds.Endpoint)
   301  	}
   302  
   303  	err = d.Set("ebs_options", flattenESEBSOptions(ds.EBSOptions))
   304  	if err != nil {
   305  		return err
   306  	}
   307  	err = d.Set("cluster_config", flattenESClusterConfig(ds.ElasticsearchClusterConfig))
   308  	if err != nil {
   309  		return err
   310  	}
   311  	if ds.SnapshotOptions != nil {
   312  		d.Set("snapshot_options", map[string]interface{}{
   313  			"automated_snapshot_start_hour": *ds.SnapshotOptions.AutomatedSnapshotStartHour,
   314  		})
   315  	}
   316  
   317  	d.Set("arn", ds.ARN)
   318  
   319  	listOut, err := conn.ListTags(&elasticsearch.ListTagsInput{
   320  		ARN: ds.ARN,
   321  	})
   322  
   323  	if err != nil {
   324  		return err
   325  	}
   326  	var est []*elasticsearch.Tag
   327  	if len(listOut.TagList) > 0 {
   328  		est = listOut.TagList
   329  	}
   330  
   331  	d.Set("tags", tagsToMapElasticsearchService(est))
   332  
   333  	return nil
   334  }
   335  
   336  func resourceAwsElasticSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error {
   337  	conn := meta.(*AWSClient).esconn
   338  
   339  	d.Partial(true)
   340  
   341  	if err := setTagsElasticsearchService(conn, d, d.Id()); err != nil {
   342  		return err
   343  	} else {
   344  		d.SetPartial("tags")
   345  	}
   346  
   347  	input := elasticsearch.UpdateElasticsearchDomainConfigInput{
   348  		DomainName: aws.String(d.Get("domain_name").(string)),
   349  	}
   350  
   351  	if d.HasChange("access_policies") {
   352  		input.AccessPolicies = aws.String(d.Get("access_policies").(string))
   353  	}
   354  
   355  	if d.HasChange("advanced_options") {
   356  		input.AdvancedOptions = stringMapToPointers(d.Get("advanced_options").(map[string]interface{}))
   357  	}
   358  
   359  	if d.HasChange("ebs_options") {
   360  		options := d.Get("ebs_options").([]interface{})
   361  
   362  		if len(options) > 1 {
   363  			return fmt.Errorf("Only a single ebs_options block is expected")
   364  		} else if len(options) == 1 {
   365  			s := options[0].(map[string]interface{})
   366  			input.EBSOptions = expandESEBSOptions(s)
   367  		}
   368  	}
   369  
   370  	if d.HasChange("cluster_config") {
   371  		config := d.Get("cluster_config").([]interface{})
   372  
   373  		if len(config) > 1 {
   374  			return fmt.Errorf("Only a single cluster_config block is expected")
   375  		} else if len(config) == 1 {
   376  			m := config[0].(map[string]interface{})
   377  			input.ElasticsearchClusterConfig = expandESClusterConfig(m)
   378  		}
   379  	}
   380  
   381  	if d.HasChange("snapshot_options") {
   382  		options := d.Get("snapshot_options").([]interface{})
   383  
   384  		if len(options) > 1 {
   385  			return fmt.Errorf("Only a single snapshot_options block is expected")
   386  		} else if len(options) == 1 {
   387  			o := options[0].(map[string]interface{})
   388  
   389  			snapshotOptions := elasticsearch.SnapshotOptions{
   390  				AutomatedSnapshotStartHour: aws.Int64(int64(o["automated_snapshot_start_hour"].(int))),
   391  			}
   392  
   393  			input.SnapshotOptions = &snapshotOptions
   394  		}
   395  	}
   396  
   397  	_, err := conn.UpdateElasticsearchDomainConfig(&input)
   398  	if err != nil {
   399  		return err
   400  	}
   401  
   402  	err = resource.Retry(60*time.Minute, func() *resource.RetryError {
   403  		out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{
   404  			DomainName: aws.String(d.Get("domain_name").(string)),
   405  		})
   406  		if err != nil {
   407  			return resource.NonRetryableError(err)
   408  		}
   409  
   410  		if *out.DomainStatus.Processing == false {
   411  			return nil
   412  		}
   413  
   414  		return resource.RetryableError(
   415  			fmt.Errorf("%q: Timeout while waiting for changes to be processed", d.Id()))
   416  	})
   417  	if err != nil {
   418  		return err
   419  	}
   420  
   421  	d.Partial(false)
   422  
   423  	return resourceAwsElasticSearchDomainRead(d, meta)
   424  }
   425  
   426  func resourceAwsElasticSearchDomainDelete(d *schema.ResourceData, meta interface{}) error {
   427  	conn := meta.(*AWSClient).esconn
   428  
   429  	log.Printf("[DEBUG] Deleting ElasticSearch domain: %q", d.Get("domain_name").(string))
   430  	_, err := conn.DeleteElasticsearchDomain(&elasticsearch.DeleteElasticsearchDomainInput{
   431  		DomainName: aws.String(d.Get("domain_name").(string)),
   432  	})
   433  	if err != nil {
   434  		return err
   435  	}
   436  
   437  	log.Printf("[DEBUG] Waiting for ElasticSearch domain %q to be deleted", d.Get("domain_name").(string))
   438  	err = resource.Retry(90*time.Minute, func() *resource.RetryError {
   439  		out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{
   440  			DomainName: aws.String(d.Get("domain_name").(string)),
   441  		})
   442  
   443  		if err != nil {
   444  			awsErr, ok := err.(awserr.Error)
   445  			if !ok {
   446  				return resource.NonRetryableError(err)
   447  			}
   448  
   449  			if awsErr.Code() == "ResourceNotFoundException" {
   450  				return nil
   451  			}
   452  
   453  			return resource.NonRetryableError(err)
   454  		}
   455  
   456  		if !*out.DomainStatus.Processing {
   457  			return nil
   458  		}
   459  
   460  		return resource.RetryableError(
   461  			fmt.Errorf("%q: Timeout while waiting for the domain to be deleted", d.Id()))
   462  	})
   463  
   464  	d.SetId("")
   465  
   466  	return err
   467  }