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