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 }