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 }