github.com/joshgarnett/terraform@v0.5.4-0.20160219181435-92dc20bb3594/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(`^[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 "tags": tagsSchema(), 133 }, 134 } 135 } 136 137 func resourceAwsElasticSearchDomainCreate(d *schema.ResourceData, meta interface{}) error { 138 conn := meta.(*AWSClient).esconn 139 140 input := elasticsearch.CreateElasticsearchDomainInput{ 141 DomainName: aws.String(d.Get("domain_name").(string)), 142 } 143 144 if v, ok := d.GetOk("access_policies"); ok { 145 input.AccessPolicies = aws.String(v.(string)) 146 } 147 148 if v, ok := d.GetOk("advanced_options"); ok { 149 input.AdvancedOptions = stringMapToPointers(v.(map[string]interface{})) 150 } 151 152 if v, ok := d.GetOk("ebs_options"); ok { 153 options := v.([]interface{}) 154 155 if len(options) > 1 { 156 return fmt.Errorf("Only a single ebs_options block is expected") 157 } else if len(options) == 1 { 158 if options[0] == nil { 159 return fmt.Errorf("At least one field is expected inside ebs_options") 160 } 161 162 s := options[0].(map[string]interface{}) 163 input.EBSOptions = expandESEBSOptions(s) 164 } 165 } 166 167 if v, ok := d.GetOk("cluster_config"); ok { 168 config := v.([]interface{}) 169 170 if len(config) > 1 { 171 return fmt.Errorf("Only a single cluster_config block is expected") 172 } else if len(config) == 1 { 173 if config[0] == nil { 174 return fmt.Errorf("At least one field is expected inside cluster_config") 175 } 176 m := config[0].(map[string]interface{}) 177 input.ElasticsearchClusterConfig = expandESClusterConfig(m) 178 } 179 } 180 181 if v, ok := d.GetOk("snapshot_options"); ok { 182 options := v.([]interface{}) 183 184 if len(options) > 1 { 185 return fmt.Errorf("Only a single snapshot_options block is expected") 186 } else if len(options) == 1 { 187 if options[0] == nil { 188 return fmt.Errorf("At least one field is expected inside snapshot_options") 189 } 190 191 o := options[0].(map[string]interface{}) 192 193 snapshotOptions := elasticsearch.SnapshotOptions{ 194 AutomatedSnapshotStartHour: aws.Int64(int64(o["automated_snapshot_start_hour"].(int))), 195 } 196 197 input.SnapshotOptions = &snapshotOptions 198 } 199 } 200 201 log.Printf("[DEBUG] Creating ElasticSearch domain: %s", input) 202 out, err := conn.CreateElasticsearchDomain(&input) 203 if err != nil { 204 return err 205 } 206 207 d.SetId(*out.DomainStatus.ARN) 208 209 log.Printf("[DEBUG] Waiting for ElasticSearch domain %q to be created", d.Id()) 210 err = resource.Retry(15*time.Minute, func() error { 211 out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{ 212 DomainName: aws.String(d.Get("domain_name").(string)), 213 }) 214 if err != nil { 215 return resource.RetryError{Err: err} 216 } 217 218 if !*out.DomainStatus.Processing && out.DomainStatus.Endpoint != nil { 219 return nil 220 } 221 222 return fmt.Errorf("%q: Timeout while waiting for the domain to be created", d.Id()) 223 }) 224 if err != nil { 225 return err 226 } 227 228 tags := tagsFromMapElasticsearchService(d.Get("tags").(map[string]interface{})) 229 230 if err := setTagsElasticsearchService(conn, d, *out.DomainStatus.ARN); err != nil { 231 return err 232 } 233 234 d.Set("tags", tagsToMapElasticsearchService(tags)) 235 d.SetPartial("tags") 236 d.Partial(false) 237 238 log.Printf("[DEBUG] ElasticSearch domain %q created", d.Id()) 239 240 return resourceAwsElasticSearchDomainRead(d, meta) 241 } 242 243 func resourceAwsElasticSearchDomainRead(d *schema.ResourceData, meta interface{}) error { 244 conn := meta.(*AWSClient).esconn 245 246 out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{ 247 DomainName: aws.String(d.Get("domain_name").(string)), 248 }) 249 if err != nil { 250 return err 251 } 252 253 log.Printf("[DEBUG] Received ElasticSearch domain: %s", out) 254 255 ds := out.DomainStatus 256 257 if ds.AccessPolicies != nil && *ds.AccessPolicies != "" { 258 d.Set("access_policies", normalizeJson(*ds.AccessPolicies)) 259 } 260 err = d.Set("advanced_options", pointersMapToStringList(ds.AdvancedOptions)) 261 if err != nil { 262 return err 263 } 264 d.Set("domain_id", *ds.DomainId) 265 d.Set("domain_name", *ds.DomainName) 266 if ds.Endpoint != nil { 267 d.Set("endpoint", *ds.Endpoint) 268 } 269 270 err = d.Set("ebs_options", flattenESEBSOptions(ds.EBSOptions)) 271 if err != nil { 272 return err 273 } 274 err = d.Set("cluster_config", flattenESClusterConfig(ds.ElasticsearchClusterConfig)) 275 if err != nil { 276 return err 277 } 278 if ds.SnapshotOptions != nil { 279 d.Set("snapshot_options", map[string]interface{}{ 280 "automated_snapshot_start_hour": *ds.SnapshotOptions.AutomatedSnapshotStartHour, 281 }) 282 } 283 284 d.Set("arn", *ds.ARN) 285 286 listOut, err := conn.ListTags(&elasticsearch.ListTagsInput{ 287 ARN: ds.ARN, 288 }) 289 290 if err != nil { 291 return err 292 } 293 294 log.Printf("[DEBUG] Received ElasticSearch tags: %s", out) 295 296 d.Set("tags", listOut) 297 298 return nil 299 } 300 301 func resourceAwsElasticSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { 302 conn := meta.(*AWSClient).esconn 303 304 d.Partial(true) 305 306 if err := setTagsElasticsearchService(conn, d, d.Id()); err != nil { 307 return err 308 } else { 309 d.SetPartial("tags") 310 } 311 312 input := elasticsearch.UpdateElasticsearchDomainConfigInput{ 313 DomainName: aws.String(d.Get("domain_name").(string)), 314 } 315 316 if d.HasChange("access_policies") { 317 input.AccessPolicies = aws.String(d.Get("access_policies").(string)) 318 } 319 320 if d.HasChange("advanced_options") { 321 input.AdvancedOptions = stringMapToPointers(d.Get("advanced_options").(map[string]interface{})) 322 } 323 324 if d.HasChange("ebs_options") { 325 options := d.Get("ebs_options").([]interface{}) 326 327 if len(options) > 1 { 328 return fmt.Errorf("Only a single ebs_options block is expected") 329 } else if len(options) == 1 { 330 s := options[0].(map[string]interface{}) 331 input.EBSOptions = expandESEBSOptions(s) 332 } 333 } 334 335 if d.HasChange("cluster_config") { 336 config := d.Get("cluster_config").([]interface{}) 337 338 if len(config) > 1 { 339 return fmt.Errorf("Only a single cluster_config block is expected") 340 } else if len(config) == 1 { 341 m := config[0].(map[string]interface{}) 342 input.ElasticsearchClusterConfig = expandESClusterConfig(m) 343 } 344 } 345 346 if d.HasChange("snapshot_options") { 347 options := d.Get("snapshot_options").([]interface{}) 348 349 if len(options) > 1 { 350 return fmt.Errorf("Only a single snapshot_options block is expected") 351 } else if len(options) == 1 { 352 o := options[0].(map[string]interface{}) 353 354 snapshotOptions := elasticsearch.SnapshotOptions{ 355 AutomatedSnapshotStartHour: aws.Int64(int64(o["automated_snapshot_start_hour"].(int))), 356 } 357 358 input.SnapshotOptions = &snapshotOptions 359 } 360 } 361 362 _, err := conn.UpdateElasticsearchDomainConfig(&input) 363 if err != nil { 364 return err 365 } 366 367 err = resource.Retry(25*time.Minute, func() error { 368 out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{ 369 DomainName: aws.String(d.Get("domain_name").(string)), 370 }) 371 if err != nil { 372 return resource.RetryError{Err: err} 373 } 374 375 if *out.DomainStatus.Processing == false { 376 return nil 377 } 378 379 return fmt.Errorf("%q: Timeout while waiting for changes to be processed", d.Id()) 380 }) 381 if err != nil { 382 return err 383 } 384 385 d.Partial(false) 386 387 return resourceAwsElasticSearchDomainRead(d, meta) 388 } 389 390 func resourceAwsElasticSearchDomainDelete(d *schema.ResourceData, meta interface{}) error { 391 conn := meta.(*AWSClient).esconn 392 393 log.Printf("[DEBUG] Deleting ElasticSearch domain: %q", d.Get("domain_name").(string)) 394 _, err := conn.DeleteElasticsearchDomain(&elasticsearch.DeleteElasticsearchDomainInput{ 395 DomainName: aws.String(d.Get("domain_name").(string)), 396 }) 397 if err != nil { 398 return err 399 } 400 401 log.Printf("[DEBUG] Waiting for ElasticSearch domain %q to be deleted", d.Get("domain_name").(string)) 402 err = resource.Retry(15*time.Minute, func() error { 403 out, err := conn.DescribeElasticsearchDomain(&elasticsearch.DescribeElasticsearchDomainInput{ 404 DomainName: aws.String(d.Get("domain_name").(string)), 405 }) 406 407 if err != nil { 408 awsErr, ok := err.(awserr.Error) 409 if !ok { 410 return resource.RetryError{Err: err} 411 } 412 413 if awsErr.Code() == "ResourceNotFoundException" { 414 return nil 415 } 416 417 return resource.RetryError{Err: awsErr} 418 } 419 420 if !*out.DomainStatus.Processing { 421 return nil 422 } 423 424 return fmt.Errorf("%q: Timeout while waiting for the domain to be deleted", d.Id()) 425 }) 426 427 d.SetId("") 428 429 return err 430 }