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