github.com/wangzhucn/terraform@v0.6.7-0.20151109233120-4eea011b56b3/builtin/providers/aws/structure.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "regexp" 8 "sort" 9 "strings" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/service/cloudformation" 13 "github.com/aws/aws-sdk-go/service/directoryservice" 14 "github.com/aws/aws-sdk-go/service/ec2" 15 "github.com/aws/aws-sdk-go/service/ecs" 16 "github.com/aws/aws-sdk-go/service/elasticache" 17 elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" 18 "github.com/aws/aws-sdk-go/service/elb" 19 "github.com/aws/aws-sdk-go/service/rds" 20 "github.com/aws/aws-sdk-go/service/route53" 21 "github.com/hashicorp/terraform/helper/schema" 22 ) 23 24 // Takes the result of flatmap.Expand for an array of listeners and 25 // returns ELB API compatible objects 26 func expandListeners(configured []interface{}) ([]*elb.Listener, error) { 27 listeners := make([]*elb.Listener, 0, len(configured)) 28 29 // Loop over our configured listeners and create 30 // an array of aws-sdk-go compatabile objects 31 for _, lRaw := range configured { 32 data := lRaw.(map[string]interface{}) 33 34 ip := int64(data["instance_port"].(int)) 35 lp := int64(data["lb_port"].(int)) 36 l := &elb.Listener{ 37 InstancePort: &ip, 38 InstanceProtocol: aws.String(data["instance_protocol"].(string)), 39 LoadBalancerPort: &lp, 40 Protocol: aws.String(data["lb_protocol"].(string)), 41 } 42 43 if v, ok := data["ssl_certificate_id"]; ok { 44 l.SSLCertificateId = aws.String(v.(string)) 45 } 46 47 listeners = append(listeners, l) 48 } 49 50 return listeners, nil 51 } 52 53 // Takes the result of flatmap. Expand for an array of listeners and 54 // returns ECS Volume compatible objects 55 func expandEcsVolumes(configured []interface{}) ([]*ecs.Volume, error) { 56 volumes := make([]*ecs.Volume, 0, len(configured)) 57 58 // Loop over our configured volumes and create 59 // an array of aws-sdk-go compatible objects 60 for _, lRaw := range configured { 61 data := lRaw.(map[string]interface{}) 62 63 l := &ecs.Volume{ 64 Name: aws.String(data["name"].(string)), 65 Host: &ecs.HostVolumeProperties{ 66 SourcePath: aws.String(data["host_path"].(string)), 67 }, 68 } 69 70 volumes = append(volumes, l) 71 } 72 73 return volumes, nil 74 } 75 76 // Takes JSON in a string. Decodes JSON into 77 // an array of ecs.ContainerDefinition compatible objects 78 func expandEcsContainerDefinitions(rawDefinitions string) ([]*ecs.ContainerDefinition, error) { 79 var definitions []*ecs.ContainerDefinition 80 81 err := json.Unmarshal([]byte(rawDefinitions), &definitions) 82 if err != nil { 83 return nil, fmt.Errorf("Error decoding JSON: %s", err) 84 } 85 86 return definitions, nil 87 } 88 89 // Takes the result of flatmap. Expand for an array of load balancers and 90 // returns ecs.LoadBalancer compatible objects 91 func expandEcsLoadBalancers(configured []interface{}) []*ecs.LoadBalancer { 92 loadBalancers := make([]*ecs.LoadBalancer, 0, len(configured)) 93 94 // Loop over our configured load balancers and create 95 // an array of aws-sdk-go compatible objects 96 for _, lRaw := range configured { 97 data := lRaw.(map[string]interface{}) 98 99 l := &ecs.LoadBalancer{ 100 ContainerName: aws.String(data["container_name"].(string)), 101 ContainerPort: aws.Int64(int64(data["container_port"].(int))), 102 LoadBalancerName: aws.String(data["elb_name"].(string)), 103 } 104 105 loadBalancers = append(loadBalancers, l) 106 } 107 108 return loadBalancers 109 } 110 111 // Takes the result of flatmap.Expand for an array of ingress/egress security 112 // group rules and returns EC2 API compatible objects. This function will error 113 // if it finds invalid permissions input, namely a protocol of "-1" with either 114 // to_port or from_port set to a non-zero value. 115 func expandIPPerms( 116 group *ec2.SecurityGroup, configured []interface{}) ([]*ec2.IpPermission, error) { 117 vpc := group.VpcId != nil 118 119 perms := make([]*ec2.IpPermission, len(configured)) 120 for i, mRaw := range configured { 121 var perm ec2.IpPermission 122 m := mRaw.(map[string]interface{}) 123 124 perm.FromPort = aws.Int64(int64(m["from_port"].(int))) 125 perm.ToPort = aws.Int64(int64(m["to_port"].(int))) 126 perm.IpProtocol = aws.String(m["protocol"].(string)) 127 128 // When protocol is "-1", AWS won't store any ports for the 129 // rule, but also won't error if the user specifies ports other 130 // than '0'. Force the user to make a deliberate '0' port 131 // choice when specifying a "-1" protocol, and tell them about 132 // AWS's behavior in the error message. 133 if *perm.IpProtocol == "-1" && (*perm.FromPort != 0 || *perm.ToPort != 0) { 134 return nil, fmt.Errorf( 135 "from_port (%d) and to_port (%d) must both be 0 to use the the 'ALL' \"-1\" protocol!", 136 *perm.FromPort, *perm.ToPort) 137 } 138 139 var groups []string 140 if raw, ok := m["security_groups"]; ok { 141 list := raw.(*schema.Set).List() 142 for _, v := range list { 143 groups = append(groups, v.(string)) 144 } 145 } 146 if v, ok := m["self"]; ok && v.(bool) { 147 if vpc { 148 groups = append(groups, *group.GroupId) 149 } else { 150 groups = append(groups, *group.GroupName) 151 } 152 } 153 154 if len(groups) > 0 { 155 perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups)) 156 for i, name := range groups { 157 ownerId, id := "", name 158 if items := strings.Split(id, "/"); len(items) > 1 { 159 ownerId, id = items[0], items[1] 160 } 161 162 perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{ 163 GroupId: aws.String(id), 164 } 165 166 if ownerId != "" { 167 perm.UserIdGroupPairs[i].UserId = aws.String(ownerId) 168 } 169 170 if !vpc { 171 perm.UserIdGroupPairs[i].GroupId = nil 172 perm.UserIdGroupPairs[i].GroupName = aws.String(id) 173 } 174 } 175 } 176 177 if raw, ok := m["cidr_blocks"]; ok { 178 list := raw.([]interface{}) 179 for _, v := range list { 180 perm.IpRanges = append(perm.IpRanges, &ec2.IpRange{CidrIp: aws.String(v.(string))}) 181 } 182 } 183 184 perms[i] = &perm 185 } 186 187 return perms, nil 188 } 189 190 // Takes the result of flatmap.Expand for an array of parameters and 191 // returns Parameter API compatible objects 192 func expandParameters(configured []interface{}) ([]*rds.Parameter, error) { 193 var parameters []*rds.Parameter 194 195 // Loop over our configured parameters and create 196 // an array of aws-sdk-go compatabile objects 197 for _, pRaw := range configured { 198 data := pRaw.(map[string]interface{}) 199 200 if data["name"].(string) == "" { 201 continue 202 } 203 204 p := &rds.Parameter{ 205 ApplyMethod: aws.String(data["apply_method"].(string)), 206 ParameterName: aws.String(data["name"].(string)), 207 ParameterValue: aws.String(data["value"].(string)), 208 } 209 210 parameters = append(parameters, p) 211 } 212 213 return parameters, nil 214 } 215 216 // Takes the result of flatmap.Expand for an array of parameters and 217 // returns Parameter API compatible objects 218 func expandElastiCacheParameters(configured []interface{}) ([]*elasticache.ParameterNameValue, error) { 219 parameters := make([]*elasticache.ParameterNameValue, 0, len(configured)) 220 221 // Loop over our configured parameters and create 222 // an array of aws-sdk-go compatabile objects 223 for _, pRaw := range configured { 224 data := pRaw.(map[string]interface{}) 225 226 p := &elasticache.ParameterNameValue{ 227 ParameterName: aws.String(data["name"].(string)), 228 ParameterValue: aws.String(data["value"].(string)), 229 } 230 231 parameters = append(parameters, p) 232 } 233 234 return parameters, nil 235 } 236 237 // Flattens a health check into something that flatmap.Flatten() 238 // can handle 239 func flattenHealthCheck(check *elb.HealthCheck) []map[string]interface{} { 240 result := make([]map[string]interface{}, 0, 1) 241 242 chk := make(map[string]interface{}) 243 chk["unhealthy_threshold"] = *check.UnhealthyThreshold 244 chk["healthy_threshold"] = *check.HealthyThreshold 245 chk["target"] = *check.Target 246 chk["timeout"] = *check.Timeout 247 chk["interval"] = *check.Interval 248 249 result = append(result, chk) 250 251 return result 252 } 253 254 // Flattens an array of UserSecurityGroups into a []string 255 func flattenSecurityGroups(list []*ec2.UserIdGroupPair) []string { 256 result := make([]string, 0, len(list)) 257 for _, g := range list { 258 result = append(result, *g.GroupId) 259 } 260 return result 261 } 262 263 // Flattens an array of Instances into a []string 264 func flattenInstances(list []*elb.Instance) []string { 265 result := make([]string, 0, len(list)) 266 for _, i := range list { 267 result = append(result, *i.InstanceId) 268 } 269 return result 270 } 271 272 // Expands an array of String Instance IDs into a []Instances 273 func expandInstanceString(list []interface{}) []*elb.Instance { 274 result := make([]*elb.Instance, 0, len(list)) 275 for _, i := range list { 276 result = append(result, &elb.Instance{InstanceId: aws.String(i.(string))}) 277 } 278 return result 279 } 280 281 // Flattens an array of Backend Descriptions into a a map of instance_port to policy names. 282 func flattenBackendPolicies(backends []*elb.BackendServerDescription) map[int64][]string { 283 policies := make(map[int64][]string) 284 for _, i := range backends { 285 for _, p := range i.PolicyNames { 286 policies[*i.InstancePort] = append(policies[*i.InstancePort], *p) 287 } 288 sort.Strings(policies[*i.InstancePort]) 289 } 290 return policies 291 } 292 293 // Flattens an array of Listeners into a []map[string]interface{} 294 func flattenListeners(list []*elb.ListenerDescription) []map[string]interface{} { 295 result := make([]map[string]interface{}, 0, len(list)) 296 for _, i := range list { 297 l := map[string]interface{}{ 298 "instance_port": *i.Listener.InstancePort, 299 "instance_protocol": strings.ToLower(*i.Listener.InstanceProtocol), 300 "lb_port": *i.Listener.LoadBalancerPort, 301 "lb_protocol": strings.ToLower(*i.Listener.Protocol), 302 } 303 // SSLCertificateID is optional, and may be nil 304 if i.Listener.SSLCertificateId != nil { 305 l["ssl_certificate_id"] = *i.Listener.SSLCertificateId 306 } 307 result = append(result, l) 308 } 309 return result 310 } 311 312 // Flattens an array of Volumes into a []map[string]interface{} 313 func flattenEcsVolumes(list []*ecs.Volume) []map[string]interface{} { 314 result := make([]map[string]interface{}, 0, len(list)) 315 for _, volume := range list { 316 l := map[string]interface{}{ 317 "name": *volume.Name, 318 "host_path": *volume.Host.SourcePath, 319 } 320 result = append(result, l) 321 } 322 return result 323 } 324 325 // Flattens an array of ECS LoadBalancers into a []map[string]interface{} 326 func flattenEcsLoadBalancers(list []*ecs.LoadBalancer) []map[string]interface{} { 327 result := make([]map[string]interface{}, 0, len(list)) 328 for _, loadBalancer := range list { 329 l := map[string]interface{}{ 330 "elb_name": *loadBalancer.LoadBalancerName, 331 "container_name": *loadBalancer.ContainerName, 332 "container_port": *loadBalancer.ContainerPort, 333 } 334 result = append(result, l) 335 } 336 return result 337 } 338 339 // Encodes an array of ecs.ContainerDefinitions into a JSON string 340 func flattenEcsContainerDefinitions(definitions []*ecs.ContainerDefinition) (string, error) { 341 byteArray, err := json.Marshal(definitions) 342 if err != nil { 343 return "", fmt.Errorf("Error encoding to JSON: %s", err) 344 } 345 346 n := bytes.Index(byteArray, []byte{0}) 347 return string(byteArray[:n]), nil 348 } 349 350 // Flattens an array of Parameters into a []map[string]interface{} 351 func flattenParameters(list []*rds.Parameter) []map[string]interface{} { 352 result := make([]map[string]interface{}, 0, len(list)) 353 for _, i := range list { 354 result = append(result, map[string]interface{}{ 355 "name": strings.ToLower(*i.ParameterName), 356 "value": strings.ToLower(*i.ParameterValue), 357 }) 358 } 359 return result 360 } 361 362 // Flattens an array of Parameters into a []map[string]interface{} 363 func flattenElastiCacheParameters(list []*elasticache.Parameter) []map[string]interface{} { 364 result := make([]map[string]interface{}, 0, len(list)) 365 for _, i := range list { 366 result = append(result, map[string]interface{}{ 367 "name": strings.ToLower(*i.ParameterName), 368 "value": strings.ToLower(*i.ParameterValue), 369 }) 370 } 371 return result 372 } 373 374 // Takes the result of flatmap.Expand for an array of strings 375 // and returns a []*string 376 func expandStringList(configured []interface{}) []*string { 377 vs := make([]*string, 0, len(configured)) 378 for _, v := range configured { 379 vs = append(vs, aws.String(v.(string))) 380 } 381 return vs 382 } 383 384 // Takes list of pointers to strings. Expand to an array 385 // of raw strings and returns a []interface{} 386 // to keep compatibility w/ schema.NewSetschema.NewSet 387 func flattenStringList(list []*string) []interface{} { 388 vs := make([]interface{}, 0, len(list)) 389 for _, v := range list { 390 vs = append(vs, *v) 391 } 392 return vs 393 } 394 395 //Flattens an array of private ip addresses into a []string, where the elements returned are the IP strings e.g. "192.168.0.0" 396 func flattenNetworkInterfacesPrivateIPAddresses(dtos []*ec2.NetworkInterfacePrivateIpAddress) []string { 397 ips := make([]string, 0, len(dtos)) 398 for _, v := range dtos { 399 ip := *v.PrivateIpAddress 400 ips = append(ips, ip) 401 } 402 return ips 403 } 404 405 //Flattens security group identifiers into a []string, where the elements returned are the GroupIDs 406 func flattenGroupIdentifiers(dtos []*ec2.GroupIdentifier) []string { 407 ids := make([]string, 0, len(dtos)) 408 for _, v := range dtos { 409 group_id := *v.GroupId 410 ids = append(ids, group_id) 411 } 412 return ids 413 } 414 415 //Expands an array of IPs into a ec2 Private IP Address Spec 416 func expandPrivateIPAddresses(ips []interface{}) []*ec2.PrivateIpAddressSpecification { 417 dtos := make([]*ec2.PrivateIpAddressSpecification, 0, len(ips)) 418 for i, v := range ips { 419 new_private_ip := &ec2.PrivateIpAddressSpecification{ 420 PrivateIpAddress: aws.String(v.(string)), 421 } 422 423 new_private_ip.Primary = aws.Bool(i == 0) 424 425 dtos = append(dtos, new_private_ip) 426 } 427 return dtos 428 } 429 430 //Flattens network interface attachment into a map[string]interface 431 func flattenAttachment(a *ec2.NetworkInterfaceAttachment) map[string]interface{} { 432 att := make(map[string]interface{}) 433 att["instance"] = *a.InstanceId 434 att["device_index"] = *a.DeviceIndex 435 att["attachment_id"] = *a.AttachmentId 436 return att 437 } 438 439 func flattenResourceRecords(recs []*route53.ResourceRecord) []string { 440 strs := make([]string, 0, len(recs)) 441 for _, r := range recs { 442 if r.Value != nil { 443 s := strings.Replace(*r.Value, "\"", "", 2) 444 strs = append(strs, s) 445 } 446 } 447 return strs 448 } 449 450 func expandResourceRecords(recs []interface{}, typeStr string) []*route53.ResourceRecord { 451 records := make([]*route53.ResourceRecord, 0, len(recs)) 452 for _, r := range recs { 453 s := r.(string) 454 switch typeStr { 455 case "TXT": 456 str := fmt.Sprintf("\"%s\"", s) 457 records = append(records, &route53.ResourceRecord{Value: aws.String(str)}) 458 default: 459 records = append(records, &route53.ResourceRecord{Value: aws.String(s)}) 460 } 461 } 462 return records 463 } 464 465 func validateRdsId(v interface{}, k string) (ws []string, errors []error) { 466 value := v.(string) 467 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 468 errors = append(errors, fmt.Errorf( 469 "only lowercase alphanumeric characters and hyphens allowed in %q", k)) 470 } 471 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 472 errors = append(errors, fmt.Errorf( 473 "first character of %q must be a letter", k)) 474 } 475 if regexp.MustCompile(`--`).MatchString(value) { 476 errors = append(errors, fmt.Errorf( 477 "%q cannot contain two consecutive hyphens", k)) 478 } 479 if regexp.MustCompile(`-$`).MatchString(value) { 480 errors = append(errors, fmt.Errorf( 481 "%q cannot end with a hyphen", k)) 482 } 483 return 484 } 485 486 func expandESClusterConfig(m map[string]interface{}) *elasticsearch.ElasticsearchClusterConfig { 487 config := elasticsearch.ElasticsearchClusterConfig{} 488 489 if v, ok := m["dedicated_master_enabled"]; ok { 490 isEnabled := v.(bool) 491 config.DedicatedMasterEnabled = aws.Bool(isEnabled) 492 493 if isEnabled { 494 if v, ok := m["dedicated_master_count"]; ok && v.(int) > 0 { 495 config.DedicatedMasterCount = aws.Int64(int64(v.(int))) 496 } 497 if v, ok := m["dedicated_master_type"]; ok && v.(string) != "" { 498 config.DedicatedMasterType = aws.String(v.(string)) 499 } 500 } 501 } 502 503 if v, ok := m["instance_count"]; ok { 504 config.InstanceCount = aws.Int64(int64(v.(int))) 505 } 506 if v, ok := m["instance_type"]; ok { 507 config.InstanceType = aws.String(v.(string)) 508 } 509 510 if v, ok := m["zone_awareness_enabled"]; ok { 511 config.ZoneAwarenessEnabled = aws.Bool(v.(bool)) 512 } 513 514 return &config 515 } 516 517 func flattenESClusterConfig(c *elasticsearch.ElasticsearchClusterConfig) []map[string]interface{} { 518 m := map[string]interface{}{} 519 520 if c.DedicatedMasterCount != nil { 521 m["dedicated_master_count"] = *c.DedicatedMasterCount 522 } 523 if c.DedicatedMasterEnabled != nil { 524 m["dedicated_master_enabled"] = *c.DedicatedMasterEnabled 525 } 526 if c.DedicatedMasterType != nil { 527 m["dedicated_master_type"] = *c.DedicatedMasterType 528 } 529 if c.InstanceCount != nil { 530 m["instance_count"] = *c.InstanceCount 531 } 532 if c.InstanceType != nil { 533 m["instance_type"] = *c.InstanceType 534 } 535 if c.ZoneAwarenessEnabled != nil { 536 m["zone_awareness_enabled"] = *c.ZoneAwarenessEnabled 537 } 538 539 return []map[string]interface{}{m} 540 } 541 542 func flattenESEBSOptions(o *elasticsearch.EBSOptions) []map[string]interface{} { 543 m := map[string]interface{}{} 544 545 if o.EBSEnabled != nil { 546 m["ebs_enabled"] = *o.EBSEnabled 547 } 548 if o.Iops != nil { 549 m["iops"] = *o.Iops 550 } 551 if o.VolumeSize != nil { 552 m["volume_size"] = *o.VolumeSize 553 } 554 if o.VolumeType != nil { 555 m["volume_type"] = *o.VolumeType 556 } 557 558 return []map[string]interface{}{m} 559 } 560 561 func expandESEBSOptions(m map[string]interface{}) *elasticsearch.EBSOptions { 562 options := elasticsearch.EBSOptions{} 563 564 if v, ok := m["ebs_enabled"]; ok { 565 options.EBSEnabled = aws.Bool(v.(bool)) 566 } 567 if v, ok := m["iops"]; ok && v.(int) > 0 { 568 options.Iops = aws.Int64(int64(v.(int))) 569 } 570 if v, ok := m["volume_size"]; ok && v.(int) > 0 { 571 options.VolumeSize = aws.Int64(int64(v.(int))) 572 } 573 if v, ok := m["volume_type"]; ok && v.(string) != "" { 574 options.VolumeType = aws.String(v.(string)) 575 } 576 577 return &options 578 } 579 580 func pointersMapToStringList(pointers map[string]*string) map[string]interface{} { 581 list := make(map[string]interface{}, len(pointers)) 582 for i, v := range pointers { 583 list[i] = *v 584 } 585 return list 586 } 587 588 func stringMapToPointers(m map[string]interface{}) map[string]*string { 589 list := make(map[string]*string, len(m)) 590 for i, v := range m { 591 list[i] = aws.String(v.(string)) 592 } 593 return list 594 } 595 596 func flattenDSVpcSettings( 597 s *directoryservice.DirectoryVpcSettingsDescription) []map[string]interface{} { 598 settings := make(map[string]interface{}, 0) 599 600 settings["subnet_ids"] = schema.NewSet(schema.HashString, flattenStringList(s.SubnetIds)) 601 settings["vpc_id"] = *s.VpcId 602 603 return []map[string]interface{}{settings} 604 } 605 606 func expandCloudFormationParameters(params map[string]interface{}) []*cloudformation.Parameter { 607 var cfParams []*cloudformation.Parameter 608 for k, v := range params { 609 cfParams = append(cfParams, &cloudformation.Parameter{ 610 ParameterKey: aws.String(k), 611 ParameterValue: aws.String(v.(string)), 612 }) 613 } 614 615 return cfParams 616 } 617 618 // flattenCloudFormationParameters is flattening list of 619 // *cloudformation.Parameters and only returning existing 620 // parameters to avoid clash with default values 621 func flattenCloudFormationParameters(cfParams []*cloudformation.Parameter, 622 originalParams map[string]interface{}) map[string]interface{} { 623 params := make(map[string]interface{}, len(cfParams)) 624 for _, p := range cfParams { 625 _, isConfigured := originalParams[*p.ParameterKey] 626 if isConfigured { 627 params[*p.ParameterKey] = *p.ParameterValue 628 } 629 } 630 return params 631 } 632 633 func expandCloudFormationTags(tags map[string]interface{}) []*cloudformation.Tag { 634 var cfTags []*cloudformation.Tag 635 for k, v := range tags { 636 cfTags = append(cfTags, &cloudformation.Tag{ 637 Key: aws.String(k), 638 Value: aws.String(v.(string)), 639 }) 640 } 641 return cfTags 642 } 643 644 func flattenCloudFormationTags(cfTags []*cloudformation.Tag) map[string]string { 645 tags := make(map[string]string, len(cfTags)) 646 for _, t := range cfTags { 647 tags[*t.Key] = *t.Value 648 } 649 return tags 650 } 651 652 func flattenCloudFormationOutputs(cfOutputs []*cloudformation.Output) map[string]string { 653 outputs := make(map[string]string, len(cfOutputs)) 654 for _, o := range cfOutputs { 655 outputs[*o.OutputKey] = *o.OutputValue 656 } 657 return outputs 658 }