terraform/builtin/providers/aws/structure.go

482 lines
15 KiB
Go

package aws
import (
"bytes"
"encoding/json"
"fmt"
"regexp"
"sort"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/elasticache"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/hashicorp/terraform/helper/schema"
)
// Takes the result of flatmap.Expand for an array of listeners and
// returns ELB API compatible objects
func expandListeners(configured []interface{}) ([]*elb.Listener, error) {
listeners := make([]*elb.Listener, 0, len(configured))
// Loop over our configured listeners and create
// an array of aws-sdk-go compatabile objects
for _, lRaw := range configured {
data := lRaw.(map[string]interface{})
ip := int64(data["instance_port"].(int))
lp := int64(data["lb_port"].(int))
l := &elb.Listener{
InstancePort: &ip,
InstanceProtocol: aws.String(data["instance_protocol"].(string)),
LoadBalancerPort: &lp,
Protocol: aws.String(data["lb_protocol"].(string)),
}
if v, ok := data["ssl_certificate_id"]; ok {
l.SSLCertificateId = aws.String(v.(string))
}
listeners = append(listeners, l)
}
return listeners, nil
}
// Takes the result of flatmap. Expand for an array of listeners and
// returns ECS Volume compatible objects
func expandEcsVolumes(configured []interface{}) ([]*ecs.Volume, error) {
volumes := make([]*ecs.Volume, 0, len(configured))
// Loop over our configured volumes and create
// an array of aws-sdk-go compatible objects
for _, lRaw := range configured {
data := lRaw.(map[string]interface{})
l := &ecs.Volume{
Name: aws.String(data["name"].(string)),
Host: &ecs.HostVolumeProperties{
SourcePath: aws.String(data["host_path"].(string)),
},
}
volumes = append(volumes, l)
}
return volumes, nil
}
// Takes JSON in a string. Decodes JSON into
// an array of ecs.ContainerDefinition compatible objects
func expandEcsContainerDefinitions(rawDefinitions string) ([]*ecs.ContainerDefinition, error) {
var definitions []*ecs.ContainerDefinition
err := json.Unmarshal([]byte(rawDefinitions), &definitions)
if err != nil {
return nil, fmt.Errorf("Error decoding JSON: %s", err)
}
return definitions, nil
}
// Takes the result of flatmap. Expand for an array of load balancers and
// returns ecs.LoadBalancer compatible objects
func expandEcsLoadBalancers(configured []interface{}) []*ecs.LoadBalancer {
loadBalancers := make([]*ecs.LoadBalancer, 0, len(configured))
// Loop over our configured load balancers and create
// an array of aws-sdk-go compatible objects
for _, lRaw := range configured {
data := lRaw.(map[string]interface{})
l := &ecs.LoadBalancer{
ContainerName: aws.String(data["container_name"].(string)),
ContainerPort: aws.Int64(int64(data["container_port"].(int))),
LoadBalancerName: aws.String(data["elb_name"].(string)),
}
loadBalancers = append(loadBalancers, l)
}
return loadBalancers
}
// Takes the result of flatmap.Expand for an array of ingress/egress security
// group rules and returns EC2 API compatible objects. This function will error
// if it finds invalid permissions input, namely a protocol of "-1" with either
// to_port or from_port set to a non-zero value.
func expandIPPerms(
group *ec2.SecurityGroup, configured []interface{}) ([]*ec2.IpPermission, error) {
vpc := group.VpcId != nil
perms := make([]*ec2.IpPermission, len(configured))
for i, mRaw := range configured {
var perm ec2.IpPermission
m := mRaw.(map[string]interface{})
perm.FromPort = aws.Int64(int64(m["from_port"].(int)))
perm.ToPort = aws.Int64(int64(m["to_port"].(int)))
perm.IpProtocol = aws.String(m["protocol"].(string))
// When protocol is "-1", AWS won't store any ports for the
// rule, but also won't error if the user specifies ports other
// than '0'. Force the user to make a deliberate '0' port
// choice when specifying a "-1" protocol, and tell them about
// AWS's behavior in the error message.
if *perm.IpProtocol == "-1" && (*perm.FromPort != 0 || *perm.ToPort != 0) {
return nil, fmt.Errorf(
"from_port (%d) and to_port (%d) must both be 0 to use the the 'ALL' \"-1\" protocol!",
*perm.FromPort, *perm.ToPort)
}
var groups []string
if raw, ok := m["security_groups"]; ok {
list := raw.(*schema.Set).List()
for _, v := range list {
groups = append(groups, v.(string))
}
}
if v, ok := m["self"]; ok && v.(bool) {
if vpc {
groups = append(groups, *group.GroupId)
} else {
groups = append(groups, *group.GroupName)
}
}
if len(groups) > 0 {
perm.UserIdGroupPairs = make([]*ec2.UserIdGroupPair, len(groups))
for i, name := range groups {
ownerId, id := "", name
if items := strings.Split(id, "/"); len(items) > 1 {
ownerId, id = items[0], items[1]
}
perm.UserIdGroupPairs[i] = &ec2.UserIdGroupPair{
GroupId: aws.String(id),
}
if ownerId != "" {
perm.UserIdGroupPairs[i].UserId = aws.String(ownerId)
}
if !vpc {
perm.UserIdGroupPairs[i].GroupId = nil
perm.UserIdGroupPairs[i].GroupName = aws.String(id)
}
}
}
if raw, ok := m["cidr_blocks"]; ok {
list := raw.([]interface{})
for _, v := range list {
perm.IpRanges = append(perm.IpRanges, &ec2.IpRange{CidrIp: aws.String(v.(string))})
}
}
perms[i] = &perm
}
return perms, nil
}
// Takes the result of flatmap.Expand for an array of parameters and
// returns Parameter API compatible objects
func expandParameters(configured []interface{}) ([]*rds.Parameter, error) {
var parameters []*rds.Parameter
// Loop over our configured parameters and create
// an array of aws-sdk-go compatabile objects
for _, pRaw := range configured {
data := pRaw.(map[string]interface{})
if data["name"].(string) == "" {
continue
}
p := &rds.Parameter{
ApplyMethod: aws.String(data["apply_method"].(string)),
ParameterName: aws.String(data["name"].(string)),
ParameterValue: aws.String(data["value"].(string)),
}
parameters = append(parameters, p)
}
return parameters, nil
}
// Takes the result of flatmap.Expand for an array of parameters and
// returns Parameter API compatible objects
func expandElastiCacheParameters(configured []interface{}) ([]*elasticache.ParameterNameValue, error) {
parameters := make([]*elasticache.ParameterNameValue, 0, len(configured))
// Loop over our configured parameters and create
// an array of aws-sdk-go compatabile objects
for _, pRaw := range configured {
data := pRaw.(map[string]interface{})
p := &elasticache.ParameterNameValue{
ParameterName: aws.String(data["name"].(string)),
ParameterValue: aws.String(data["value"].(string)),
}
parameters = append(parameters, p)
}
return parameters, nil
}
// Flattens a health check into something that flatmap.Flatten()
// can handle
func flattenHealthCheck(check *elb.HealthCheck) []map[string]interface{} {
result := make([]map[string]interface{}, 0, 1)
chk := make(map[string]interface{})
chk["unhealthy_threshold"] = *check.UnhealthyThreshold
chk["healthy_threshold"] = *check.HealthyThreshold
chk["target"] = *check.Target
chk["timeout"] = *check.Timeout
chk["interval"] = *check.Interval
result = append(result, chk)
return result
}
// Flattens an array of UserSecurityGroups into a []string
func flattenSecurityGroups(list []*ec2.UserIdGroupPair) []string {
result := make([]string, 0, len(list))
for _, g := range list {
result = append(result, *g.GroupId)
}
return result
}
// Flattens an array of Instances into a []string
func flattenInstances(list []*elb.Instance) []string {
result := make([]string, 0, len(list))
for _, i := range list {
result = append(result, *i.InstanceId)
}
return result
}
// Expands an array of String Instance IDs into a []Instances
func expandInstanceString(list []interface{}) []*elb.Instance {
result := make([]*elb.Instance, 0, len(list))
for _, i := range list {
result = append(result, &elb.Instance{InstanceId: aws.String(i.(string))})
}
return result
}
// Flattens an array of Backend Descriptions into a a map of instance_port to policy names.
func flattenBackendPolicies(backends []*elb.BackendServerDescription) map[int64][]string {
policies := make(map[int64][]string)
for _, i := range backends {
for _, p := range i.PolicyNames {
policies[*i.InstancePort] = append(policies[*i.InstancePort], *p)
}
sort.Strings(policies[*i.InstancePort])
}
return policies
}
// Flattens an array of Listeners into a []map[string]interface{}
func flattenListeners(list []*elb.ListenerDescription) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, i := range list {
l := map[string]interface{}{
"instance_port": *i.Listener.InstancePort,
"instance_protocol": strings.ToLower(*i.Listener.InstanceProtocol),
"lb_port": *i.Listener.LoadBalancerPort,
"lb_protocol": strings.ToLower(*i.Listener.Protocol),
}
// SSLCertificateID is optional, and may be nil
if i.Listener.SSLCertificateId != nil {
l["ssl_certificate_id"] = *i.Listener.SSLCertificateId
}
result = append(result, l)
}
return result
}
// Flattens an array of Volumes into a []map[string]interface{}
func flattenEcsVolumes(list []*ecs.Volume) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, volume := range list {
l := map[string]interface{}{
"name": *volume.Name,
"host_path": *volume.Host.SourcePath,
}
result = append(result, l)
}
return result
}
// Flattens an array of ECS LoadBalancers into a []map[string]interface{}
func flattenEcsLoadBalancers(list []*ecs.LoadBalancer) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, loadBalancer := range list {
l := map[string]interface{}{
"elb_name": *loadBalancer.LoadBalancerName,
"container_name": *loadBalancer.ContainerName,
"container_port": *loadBalancer.ContainerPort,
}
result = append(result, l)
}
return result
}
// Encodes an array of ecs.ContainerDefinitions into a JSON string
func flattenEcsContainerDefinitions(definitions []*ecs.ContainerDefinition) (string, error) {
byteArray, err := json.Marshal(definitions)
if err != nil {
return "", fmt.Errorf("Error encoding to JSON: %s", err)
}
n := bytes.Index(byteArray, []byte{0})
return string(byteArray[:n]), nil
}
// Flattens an array of Parameters into a []map[string]interface{}
func flattenParameters(list []*rds.Parameter) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, i := range list {
result = append(result, map[string]interface{}{
"name": strings.ToLower(*i.ParameterName),
"value": strings.ToLower(*i.ParameterValue),
})
}
return result
}
// Flattens an array of Parameters into a []map[string]interface{}
func flattenElastiCacheParameters(list []*elasticache.Parameter) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(list))
for _, i := range list {
result = append(result, map[string]interface{}{
"name": strings.ToLower(*i.ParameterName),
"value": strings.ToLower(*i.ParameterValue),
})
}
return result
}
// Takes the result of flatmap.Expand for an array of strings
// and returns a []*string
func expandStringList(configured []interface{}) []*string {
vs := make([]*string, 0, len(configured))
for _, v := range configured {
vs = append(vs, aws.String(v.(string)))
}
return vs
}
// Takes list of pointers to strings. Expand to an array
// of raw strings and returns a []interface{}
// to keep compatibility w/ schema.NewSetschema.NewSet
func flattenStringList(list []*string) []interface{} {
vs := make([]interface{}, 0, len(list))
for _, v := range list {
vs = append(vs, *v)
}
return vs
}
//Flattens an array of private ip addresses into a []string, where the elements returned are the IP strings e.g. "192.168.0.0"
func flattenNetworkInterfacesPrivateIPAddresses(dtos []*ec2.NetworkInterfacePrivateIpAddress) []string {
ips := make([]string, 0, len(dtos))
for _, v := range dtos {
ip := *v.PrivateIpAddress
ips = append(ips, ip)
}
return ips
}
//Flattens security group identifiers into a []string, where the elements returned are the GroupIDs
func flattenGroupIdentifiers(dtos []*ec2.GroupIdentifier) []string {
ids := make([]string, 0, len(dtos))
for _, v := range dtos {
group_id := *v.GroupId
ids = append(ids, group_id)
}
return ids
}
//Expands an array of IPs into a ec2 Private IP Address Spec
func expandPrivateIPAddresses(ips []interface{}) []*ec2.PrivateIpAddressSpecification {
dtos := make([]*ec2.PrivateIpAddressSpecification, 0, len(ips))
for i, v := range ips {
new_private_ip := &ec2.PrivateIpAddressSpecification{
PrivateIpAddress: aws.String(v.(string)),
}
new_private_ip.Primary = aws.Bool(i == 0)
dtos = append(dtos, new_private_ip)
}
return dtos
}
//Flattens network interface attachment into a map[string]interface
func flattenAttachment(a *ec2.NetworkInterfaceAttachment) map[string]interface{} {
att := make(map[string]interface{})
att["instance"] = *a.InstanceId
att["device_index"] = *a.DeviceIndex
att["attachment_id"] = *a.AttachmentId
return att
}
func flattenResourceRecords(recs []*route53.ResourceRecord) []string {
strs := make([]string, 0, len(recs))
for _, r := range recs {
if r.Value != nil {
s := strings.Replace(*r.Value, "\"", "", 2)
strs = append(strs, s)
}
}
return strs
}
func expandResourceRecords(recs []interface{}, typeStr string) []*route53.ResourceRecord {
records := make([]*route53.ResourceRecord, 0, len(recs))
for _, r := range recs {
s := r.(string)
switch typeStr {
case "TXT":
str := fmt.Sprintf("\"%s\"", s)
records = append(records, &route53.ResourceRecord{Value: aws.String(str)})
default:
records = append(records, &route53.ResourceRecord{Value: aws.String(s)})
}
}
return records
}
func validateRdsId(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase alphanumeric characters and hyphens allowed in %q", k))
}
if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"first character of %q must be a letter", k))
}
if regexp.MustCompile(`--`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot contain two consecutive hyphens", k))
}
if regexp.MustCompile(`-$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot end with a hyphen", k))
}
return
}