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 }