github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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  				StateFunc: normalizeJson,
    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(`^[0-9A-Za-z]+`).MatchString(value) {
    41  						errors = append(errors, fmt.Errorf(
    42  							"%q must start with a letter or number", k))
    43  					}
    44  					if !regexp.MustCompile(`^[0-9A-Za-z][0-9a-z-]+$`).MatchString(value) {
    45  						errors = append(errors, fmt.Errorf(
    46  							"%q can only contain lowercase characters, numbers and hyphens", k))
    47  					}
    48  					return
    49  				},
    50  			},
    51  			"arn": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Computed: true,
    54  			},
    55  			"domain_id": &schema.Schema{
    56  				Type:     schema.TypeString,
    57  				Computed: true,
    58  			},
    59  			"endpoint": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Computed: true,
    62  			},
    63  			"ebs_options": &schema.Schema{
    64  				Type:     schema.TypeList,
    65  				Optional: true,
    66  				Computed: true,
    67  				Elem: &schema.Resource{
    68  					Schema: map[string]*schema.Schema{
    69  						"ebs_enabled": &schema.Schema{
    70  							Type:     schema.TypeBool,
    71  							Required: true,
    72  						},
    73  						"iops": &schema.Schema{
    74  							Type:     schema.TypeInt,
    75  							Optional: true,
    76  						},
    77  						"volume_size": &schema.Schema{
    78  							Type:     schema.TypeInt,
    79  							Optional: true,
    80  						},
    81  						"volume_type": &schema.Schema{
    82  							Type:     schema.TypeString,
    83  							Optional: true,
    84  						},
    85  					},
    86  				},
    87  			},
    88  			"cluster_config": &schema.Schema{
    89  				Type:     schema.TypeList,
    90  				Optional: true,
    91  				Computed: true,
    92  				Elem: &schema.Resource{
    93  					Schema: map[string]*schema.Schema{
    94  						"dedicated_master_count": &schema.Schema{
    95  							Type:     schema.TypeInt,
    96  							Optional: true,
    97  						},
    98  						"dedicated_master_enabled": &schema.Schema{
    99  							Type:     schema.TypeBool,
   100  							Optional: true,
   101  							Default:  false,
   102  						},
   103  						"dedicated_master_type": &schema.Schema{
   104  							Type:     schema.TypeString,
   105  							Optional: true,
   106  						},
   107  						"instance_count": &schema.Schema{
   108  							Type:     schema.TypeInt,
   109  							Optional: true,
   110  							Default:  1,
   111  						},
   112  						"instance_type": &schema.Schema{
   113  							Type:     schema.TypeString,
   114  							Optional: true,
   115  							Default:  "m3.medium.elasticsearch",
   116  						},
   117  						"zone_awareness_enabled": &schema.Schema{
   118  							Type:     schema.TypeBool,
   119  							Optional: true,
   120  						},
   121  					},
   122  				},
   123  			},
   124  			"snapshot_options": &schema.Schema{
   125  				Type:     schema.TypeList,
   126  				Optional: true,
   127  				Elem: &schema.Resource{
   128  					Schema: map[string]*schema.Schema{
   129  						"automated_snapshot_start_hour": &schema.Schema{
   130  							Type:     schema.TypeInt,
   131  							Required: true,
   132  						},
   133  					},
   134  				},
   135  			},
   136  		},
   137  	}
   138  }
   139  
   140  func resourceAwsElasticSearchDomainCreate(d *schema.ResourceData, meta interface{}) error {
   141  	conn := meta.(*AWSClient).esconn
   142  
   143  	input := elasticsearch.CreateElasticsearchDomainInput{
   144  		DomainName: aws.String(d.Get("domain_name").(string)),
   145  	}
   146  
   147  	if v, ok := d.GetOk("access_policies"); ok {
   148  		input.AccessPolicies = aws.String(v.(string))
   149  	}
   150  
   151  	if v, ok := d.GetOk("advanced_options"); ok {
   152  		input.AdvancedOptions = stringMapToPointers(v.(map[string]interface{}))
   153  	}
   154  
   155  	if v, ok := d.GetOk("ebs_options"); ok {
   156  		options := v.([]interface{})
   157  
   158  		if len(options) > 1 {
   159  			return fmt.Errorf("Only a single ebs_options block is expected")
   160  		} else if len(options) == 1 {
   161  			if options[0] == nil {
   162  				return fmt.Errorf("At least one field is expected inside ebs_options")
   163  			}
   164  
   165  			s := options[0].(map[string]interface{})
   166  			input.EBSOptions = expandESEBSOptions(s)
   167  		}
   168  	}
   169  
   170  	if v, ok := d.GetOk("cluster_config"); ok {
   171  		config := v.([]interface{})
   172  
   173  		if len(config) > 1 {
   174  			return fmt.Errorf("Only a single cluster_config block is expected")
   175  		} else if len(config) == 1 {
   176  			if config[0] == nil {
   177  				return fmt.Errorf("At least one field is expected inside cluster_config")
   178  			}
   179  			m := config[0].(map[string]interface{})
   180  			input.ElasticsearchClusterConfig = expandESClusterConfig(m)
   181  		}
   182  	}
   183  
   184  	if v, ok := d.GetOk("snapshot_options"); ok {
   185  		options := v.([]interface{})
   186  
   187  		if len(options) > 1 {
   188  			return fmt.Errorf("Only a single snapshot_options block is expected")
   189  		} else if len(options) == 1 {
   190  			if options[0] == nil {
   191  				return fmt.Errorf("At least one field is expected inside snapshot_options")
   192  			}
   193  
   194  			o := options[0].(map[string]interface{})
   195  
   196  			snapshotOptions := elasticsearch.SnapshotOptions{
   197  				AutomatedSnapshotStartHour: aws.Int64(int64(o["automated_snapshot_start_hour"].(int))),
   198  			}
   199  
   200  			input.SnapshotOptions = &snapshotOptions
   201  		}
   202  	}
   203  
   204  	log.Printf("[DEBUG] Creating ElasticSearch domain: %s", input)
   205  	out, err := conn.CreateElasticsearchDomain(&input)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	d.SetId(*out.DomainStatus.ARN)
   211  
   212  	log.Printf("[DEBUG] Waiting for ElasticSearch domain %q to be created", d.Id())
   213  	err = resource.Retry(15*time.Minute, func() error {
   214  		out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{
   215  			DomainName: aws.String(d.Get("domain_name").(string)),
   216  		})
   217  		if err != nil {
   218  			return resource.RetryError{Err: err}
   219  		}
   220  
   221  		if !*out.DomainStatus.Processing && out.DomainStatus.Endpoint != nil {
   222  			return nil
   223  		}
   224  
   225  		return fmt.Errorf("%q: Timeout while waiting for the domain to be created", d.Id())
   226  	})
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	log.Printf("[DEBUG] ElasticSearch domain %q created", d.Id())
   232  
   233  	return resourceAwsElasticSearchDomainRead(d, meta)
   234  }
   235  
   236  func resourceAwsElasticSearchDomainRead(d *schema.ResourceData, meta interface{}) error {
   237  	conn := meta.(*AWSClient).esconn
   238  
   239  	out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{
   240  		DomainName: aws.String(d.Get("domain_name").(string)),
   241  	})
   242  	if err != nil {
   243  		return err
   244  	}
   245  
   246  	log.Printf("[DEBUG] Received ElasticSearch domain: %s", out)
   247  
   248  	ds := out.DomainStatus
   249  
   250  	d.Set("access_policies", *ds.AccessPolicies)
   251  	err = d.Set("advanced_options", pointersMapToStringList(ds.AdvancedOptions))
   252  	if err != nil {
   253  		return err
   254  	}
   255  	d.Set("domain_id", *ds.DomainId)
   256  	d.Set("domain_name", *ds.DomainName)
   257  	if ds.Endpoint != nil {
   258  		d.Set("endpoint", *ds.Endpoint)
   259  	}
   260  
   261  	err = d.Set("ebs_options", flattenESEBSOptions(ds.EBSOptions))
   262  	if err != nil {
   263  		return err
   264  	}
   265  	err = d.Set("cluster_config", flattenESClusterConfig(ds.ElasticsearchClusterConfig))
   266  	if err != nil {
   267  		return err
   268  	}
   269  	if ds.SnapshotOptions != nil {
   270  		d.Set("snapshot_options", map[string]interface{}{
   271  			"automated_snapshot_start_hour": *ds.SnapshotOptions.AutomatedSnapshotStartHour,
   272  		})
   273  	}
   274  
   275  	d.Set("arn", *ds.ARN)
   276  
   277  	return nil
   278  }
   279  
   280  func resourceAwsElasticSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error {
   281  	conn := meta.(*AWSClient).esconn
   282  
   283  	input := elasticsearch.UpdateElasticsearchDomainConfigInput{
   284  		DomainName: aws.String(d.Get("domain_name").(string)),
   285  	}
   286  
   287  	if d.HasChange("access_policies") {
   288  		input.AccessPolicies = aws.String(d.Get("access_policies").(string))
   289  	}
   290  
   291  	if d.HasChange("advanced_options") {
   292  		input.AdvancedOptions = stringMapToPointers(d.Get("advanced_options").(map[string]interface{}))
   293  	}
   294  
   295  	if d.HasChange("ebs_options") {
   296  		options := d.Get("ebs_options").([]interface{})
   297  
   298  		if len(options) > 1 {
   299  			return fmt.Errorf("Only a single ebs_options block is expected")
   300  		} else if len(options) == 1 {
   301  			s := options[0].(map[string]interface{})
   302  			input.EBSOptions = expandESEBSOptions(s)
   303  		}
   304  	}
   305  
   306  	if d.HasChange("cluster_config") {
   307  		config := d.Get("cluster_config").([]interface{})
   308  
   309  		if len(config) > 1 {
   310  			return fmt.Errorf("Only a single cluster_config block is expected")
   311  		} else if len(config) == 1 {
   312  			m := config[0].(map[string]interface{})
   313  			input.ElasticsearchClusterConfig = expandESClusterConfig(m)
   314  		}
   315  	}
   316  
   317  	if d.HasChange("snapshot_options") {
   318  		options := d.Get("snapshot_options").([]interface{})
   319  
   320  		if len(options) > 1 {
   321  			return fmt.Errorf("Only a single snapshot_options block is expected")
   322  		} else if len(options) == 1 {
   323  			o := options[0].(map[string]interface{})
   324  
   325  			snapshotOptions := elasticsearch.SnapshotOptions{
   326  				AutomatedSnapshotStartHour: aws.Int64(int64(o["automated_snapshot_start_hour"].(int))),
   327  			}
   328  
   329  			input.SnapshotOptions = &snapshotOptions
   330  		}
   331  	}
   332  
   333  	_, err := conn.UpdateElasticsearchDomainConfig(&input)
   334  	if err != nil {
   335  		return err
   336  	}
   337  
   338  	err = resource.Retry(25*time.Minute, func() error {
   339  		out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{
   340  			DomainName: aws.String(d.Get("domain_name").(string)),
   341  		})
   342  		if err != nil {
   343  			return resource.RetryError{Err: err}
   344  		}
   345  
   346  		if *out.DomainStatus.Processing == false {
   347  			return nil
   348  		}
   349  
   350  		return fmt.Errorf("%q: Timeout while waiting for changes to be processed", d.Id())
   351  	})
   352  	if err != nil {
   353  		return err
   354  	}
   355  
   356  	return resourceAwsElasticSearchDomainRead(d, meta)
   357  }
   358  
   359  func resourceAwsElasticSearchDomainDelete(d *schema.ResourceData, meta interface{}) error {
   360  	conn := meta.(*AWSClient).esconn
   361  
   362  	log.Printf("[DEBUG] Deleting ElasticSearch domain: %q", d.Get("domain_name").(string))
   363  	_, err := conn.DeleteElasticsearchDomain(&elasticsearch.DeleteElasticsearchDomainInput{
   364  		DomainName: aws.String(d.Get("domain_name").(string)),
   365  	})
   366  	if err != nil {
   367  		return err
   368  	}
   369  
   370  	log.Printf("[DEBUG] Waiting for ElasticSearch domain %q to be deleted", d.Get("domain_name").(string))
   371  	err = resource.Retry(15*time.Minute, func() error {
   372  		out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{
   373  			DomainName: aws.String(d.Get("domain_name").(string)),
   374  		})
   375  
   376  		if err != nil {
   377  			awsErr, ok := err.(awserr.Error)
   378  			if !ok {
   379  				return resource.RetryError{Err: err}
   380  			}
   381  
   382  			if awsErr.Code() == "ResourceNotFoundException" {
   383  				return nil
   384  			}
   385  
   386  			return resource.RetryError{Err: awsErr}
   387  		}
   388  
   389  		if !*out.DomainStatus.Processing {
   390  			return nil
   391  		}
   392  
   393  		return fmt.Errorf("%q: Timeout while waiting for the domain to be deleted", d.Id())
   394  	})
   395  
   396  	d.SetId("")
   397  
   398  	return err
   399  }