2014-07-03 00:55:28 +02:00
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
2015-05-04 00:12:50 +02:00
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2015-04-16 22:18:01 +02:00
|
|
|
"fmt"
|
2015-04-26 18:16:52 +02:00
|
|
|
"sort"
|
2014-07-17 00:51:50 +02:00
|
|
|
"strings"
|
|
|
|
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
2015-05-04 00:12:50 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/ecs"
|
2015-06-03 20:36:57 +02:00
|
|
|
"github.com/aws/aws-sdk-go/service/elb"
|
|
|
|
"github.com/aws/aws-sdk-go/service/rds"
|
|
|
|
"github.com/aws/aws-sdk-go/service/route53"
|
2014-10-21 19:49:27 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2014-07-03 00:55:28 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Takes the result of flatmap.Expand for an array of listeners and
|
|
|
|
// returns ELB API compatible objects
|
2015-04-16 22:28:18 +02:00
|
|
|
func expandListeners(configured []interface{}) ([]*elb.Listener, error) {
|
2015-04-16 22:18:01 +02:00
|
|
|
listeners := make([]*elb.Listener, 0, len(configured))
|
2014-07-03 00:55:28 +02:00
|
|
|
|
|
|
|
// Loop over our configured listeners and create
|
2015-03-12 22:49:38 +01:00
|
|
|
// an array of aws-sdk-go compatabile objects
|
2014-10-10 08:58:48 +02:00
|
|
|
for _, lRaw := range configured {
|
|
|
|
data := lRaw.(map[string]interface{})
|
2014-07-25 00:50:18 +02:00
|
|
|
|
2015-04-16 22:18:01 +02:00
|
|
|
ip := int64(data["instance_port"].(int))
|
|
|
|
lp := int64(data["lb_port"].(int))
|
|
|
|
l := &elb.Listener{
|
|
|
|
InstancePort: &ip,
|
2015-03-02 16:44:06 +01:00
|
|
|
InstanceProtocol: aws.String(data["instance_protocol"].(string)),
|
2015-04-16 22:18:01 +02:00
|
|
|
LoadBalancerPort: &lp,
|
2015-03-02 16:44:06 +01:00
|
|
|
Protocol: aws.String(data["lb_protocol"].(string)),
|
2014-07-03 00:55:28 +02:00
|
|
|
}
|
|
|
|
|
2014-10-10 08:58:48 +02:00
|
|
|
if v, ok := data["ssl_certificate_id"]; ok {
|
2015-03-02 16:44:06 +01:00
|
|
|
l.SSLCertificateID = aws.String(v.(string))
|
2014-08-11 01:09:05 +02:00
|
|
|
}
|
|
|
|
|
2014-07-03 00:55:28 +02:00
|
|
|
listeners = append(listeners, l)
|
|
|
|
}
|
|
|
|
|
2014-07-25 00:50:18 +02:00
|
|
|
return listeners, nil
|
2014-07-03 00:55:28 +02:00
|
|
|
}
|
2014-07-03 01:57:57 +02:00
|
|
|
|
2015-05-04 00:12:50 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2015-05-05 05:43:31 +02:00
|
|
|
// 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.
|
2015-04-16 22:28:18 +02:00
|
|
|
func expandIPPerms(
|
2015-05-05 05:43:31 +02:00
|
|
|
group *ec2.SecurityGroup, configured []interface{}) ([]*ec2.IPPermission, error) {
|
2015-03-18 14:47:59 +01:00
|
|
|
vpc := group.VPCID != nil
|
|
|
|
|
2015-04-16 22:18:01 +02:00
|
|
|
perms := make([]*ec2.IPPermission, len(configured))
|
2015-03-09 16:02:27 +01:00
|
|
|
for i, mRaw := range configured {
|
2015-03-12 22:49:38 +01:00
|
|
|
var perm ec2.IPPermission
|
2015-03-09 16:02:27 +01:00
|
|
|
m := mRaw.(map[string]interface{})
|
|
|
|
|
2015-04-16 22:18:01 +02:00
|
|
|
perm.FromPort = aws.Long(int64(m["from_port"].(int)))
|
|
|
|
perm.ToPort = aws.Long(int64(m["to_port"].(int)))
|
2015-03-09 16:02:27 +01:00
|
|
|
perm.IPProtocol = aws.String(m["protocol"].(string))
|
|
|
|
|
2015-05-05 05:43:31 +02:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2015-03-09 16:02:27 +01:00
|
|
|
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) {
|
2015-03-18 14:47:59 +01:00
|
|
|
if vpc {
|
|
|
|
groups = append(groups, *group.GroupID)
|
|
|
|
} else {
|
|
|
|
groups = append(groups, *group.GroupName)
|
|
|
|
}
|
2015-03-09 16:02:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(groups) > 0 {
|
2015-04-16 22:18:01 +02:00
|
|
|
perm.UserIDGroupPairs = make([]*ec2.UserIDGroupPair, len(groups))
|
2015-03-09 16:02:27 +01:00
|
|
|
for i, name := range groups {
|
|
|
|
ownerId, id := "", name
|
|
|
|
if items := strings.Split(id, "/"); len(items) > 1 {
|
|
|
|
ownerId, id = items[0], items[1]
|
|
|
|
}
|
|
|
|
|
2015-04-16 22:18:01 +02:00
|
|
|
perm.UserIDGroupPairs[i] = &ec2.UserIDGroupPair{
|
2015-03-09 16:02:27 +01:00
|
|
|
GroupID: aws.String(id),
|
|
|
|
UserID: aws.String(ownerId),
|
|
|
|
}
|
2015-03-18 14:47:59 +01:00
|
|
|
if !vpc {
|
|
|
|
perm.UserIDGroupPairs[i].GroupID = nil
|
|
|
|
perm.UserIDGroupPairs[i].GroupName = aws.String(id)
|
|
|
|
perm.UserIDGroupPairs[i].UserID = nil
|
|
|
|
}
|
2015-03-09 16:02:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if raw, ok := m["cidr_blocks"]; ok {
|
|
|
|
list := raw.([]interface{})
|
2015-04-21 17:57:50 +02:00
|
|
|
for _, v := range list {
|
|
|
|
perm.IPRanges = append(perm.IPRanges, &ec2.IPRange{CIDRIP: aws.String(v.(string))})
|
2015-03-09 16:02:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-16 22:18:01 +02:00
|
|
|
perms[i] = &perm
|
2015-03-09 16:02:27 +01:00
|
|
|
}
|
|
|
|
|
2015-05-05 05:43:31 +02:00
|
|
|
return perms, nil
|
2015-03-09 16:02:27 +01:00
|
|
|
}
|
|
|
|
|
2014-10-22 23:22:30 +02:00
|
|
|
// Takes the result of flatmap.Expand for an array of parameters and
|
|
|
|
// returns Parameter API compatible objects
|
2015-04-16 22:28:18 +02:00
|
|
|
func expandParameters(configured []interface{}) ([]*rds.Parameter, error) {
|
2015-04-16 22:18:01 +02:00
|
|
|
parameters := make([]*rds.Parameter, 0, len(configured))
|
2014-10-22 23:22:30 +02:00
|
|
|
|
|
|
|
// Loop over our configured parameters and create
|
2015-03-12 22:49:38 +01:00
|
|
|
// an array of aws-sdk-go compatabile objects
|
2014-10-22 23:22:30 +02:00
|
|
|
for _, pRaw := range configured {
|
|
|
|
data := pRaw.(map[string]interface{})
|
|
|
|
|
2015-04-16 22:18:01 +02:00
|
|
|
p := &rds.Parameter{
|
2015-02-26 16:33:33 +01:00
|
|
|
ApplyMethod: aws.String(data["apply_method"].(string)),
|
|
|
|
ParameterName: aws.String(data["name"].(string)),
|
|
|
|
ParameterValue: aws.String(data["value"].(string)),
|
2014-10-22 23:22:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
parameters = append(parameters, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
return parameters, nil
|
|
|
|
}
|
|
|
|
|
2014-07-30 16:15:22 +02:00
|
|
|
// Flattens a health check into something that flatmap.Flatten()
|
|
|
|
// can handle
|
2015-04-16 22:28:18 +02:00
|
|
|
func flattenHealthCheck(check *elb.HealthCheck) []map[string]interface{} {
|
2014-07-30 16:15:22 +02:00
|
|
|
result := make([]map[string]interface{}, 0, 1)
|
|
|
|
|
|
|
|
chk := make(map[string]interface{})
|
2015-03-02 16:44:06 +01:00
|
|
|
chk["unhealthy_threshold"] = *check.UnhealthyThreshold
|
|
|
|
chk["healthy_threshold"] = *check.HealthyThreshold
|
|
|
|
chk["target"] = *check.Target
|
|
|
|
chk["timeout"] = *check.Timeout
|
|
|
|
chk["interval"] = *check.Interval
|
2014-07-30 16:15:22 +02:00
|
|
|
|
|
|
|
result = append(result, chk)
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2014-07-10 20:15:58 +02:00
|
|
|
// Flattens an array of UserSecurityGroups into a []string
|
2015-04-16 22:28:18 +02:00
|
|
|
func flattenSecurityGroups(list []*ec2.UserIDGroupPair) []string {
|
2015-03-09 16:02:27 +01:00
|
|
|
result := make([]string, 0, len(list))
|
|
|
|
for _, g := range list {
|
|
|
|
result = append(result, *g.GroupID)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2014-07-16 23:02:47 +02:00
|
|
|
// Flattens an array of Instances into a []string
|
2015-04-16 22:28:18 +02:00
|
|
|
func flattenInstances(list []*elb.Instance) []string {
|
2014-07-16 23:02:47 +02:00
|
|
|
result := make([]string, 0, len(list))
|
|
|
|
for _, i := range list {
|
2015-03-02 16:44:06 +01:00
|
|
|
result = append(result, *i.InstanceID)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expands an array of String Instance IDs into a []Instances
|
2015-04-16 22:28:18 +02:00
|
|
|
func expandInstanceString(list []interface{}) []*elb.Instance {
|
2015-04-16 22:18:01 +02:00
|
|
|
result := make([]*elb.Instance, 0, len(list))
|
2015-03-02 16:44:06 +01:00
|
|
|
for _, i := range list {
|
2015-04-16 22:18:01 +02:00
|
|
|
result = append(result, &elb.Instance{InstanceID: aws.String(i.(string))})
|
2014-07-16 23:02:47 +02:00
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2015-04-26 18:16:52 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2014-10-11 01:35:52 +02:00
|
|
|
// Flattens an array of Listeners into a []map[string]interface{}
|
2015-04-16 22:28:18 +02:00
|
|
|
func flattenListeners(list []*elb.ListenerDescription) []map[string]interface{} {
|
2014-10-11 01:35:52 +02:00
|
|
|
result := make([]map[string]interface{}, 0, len(list))
|
|
|
|
for _, i := range list {
|
2015-03-02 16:44:06 +01:00
|
|
|
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)
|
2014-10-11 01:35:52 +02:00
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2015-05-04 00:12:50 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2014-10-23 02:03:57 +02:00
|
|
|
// Flattens an array of Parameters into a []map[string]interface{}
|
2015-04-16 22:28:18 +02:00
|
|
|
func flattenParameters(list []*rds.Parameter) []map[string]interface{} {
|
2014-10-22 23:22:30 +02:00
|
|
|
result := make([]map[string]interface{}, 0, len(list))
|
|
|
|
for _, i := range list {
|
|
|
|
result = append(result, map[string]interface{}{
|
2015-02-26 16:33:33 +01:00
|
|
|
"name": strings.ToLower(*i.ParameterName),
|
|
|
|
"value": strings.ToLower(*i.ParameterValue),
|
2014-11-24 21:22:18 +01:00
|
|
|
})
|
2014-10-22 23:22:30 +02:00
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2014-07-03 01:57:57 +02:00
|
|
|
// Takes the result of flatmap.Expand for an array of strings
|
|
|
|
// and returns a []string
|
2015-04-16 22:28:18 +02:00
|
|
|
func expandStringList(configured []interface{}) []*string {
|
2015-04-16 22:18:01 +02:00
|
|
|
vs := make([]*string, 0, len(configured))
|
2014-07-03 01:57:57 +02:00
|
|
|
for _, v := range configured {
|
2015-04-16 22:18:01 +02:00
|
|
|
vs = append(vs, aws.String(v.(string)))
|
2014-07-03 01:57:57 +02:00
|
|
|
}
|
|
|
|
return vs
|
|
|
|
}
|
2015-03-17 13:42:05 +01:00
|
|
|
|
|
|
|
//Flattens an array of private ip addresses into a []string, where the elements returned are the IP strings e.g. "192.168.0.0"
|
2015-04-16 22:28:18 +02:00
|
|
|
func flattenNetworkInterfacesPrivateIPAddesses(dtos []*ec2.NetworkInterfacePrivateIPAddress) []string {
|
2015-03-17 13:42:05 +01:00
|
|
|
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
|
2015-04-16 22:28:18 +02:00
|
|
|
func flattenGroupIdentifiers(dtos []*ec2.GroupIdentifier) []string {
|
2015-03-17 13:42:05 +01:00
|
|
|
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
|
2015-04-16 22:28:18 +02:00
|
|
|
func expandPrivateIPAddesses(ips []interface{}) []*ec2.PrivateIPAddressSpecification {
|
2015-04-16 22:18:01 +02:00
|
|
|
dtos := make([]*ec2.PrivateIPAddressSpecification, 0, len(ips))
|
2015-03-17 13:42:05 +01:00
|
|
|
for i, v := range ips {
|
2015-04-16 22:18:01 +02:00
|
|
|
new_private_ip := &ec2.PrivateIPAddressSpecification{
|
2015-03-17 14:00:36 +01:00
|
|
|
PrivateIPAddress: aws.String(v.(string)),
|
|
|
|
}
|
|
|
|
|
2015-03-17 13:42:05 +01:00
|
|
|
new_private_ip.Primary = aws.Boolean(i == 0)
|
2015-03-17 14:00:36 +01:00
|
|
|
|
2015-03-17 13:42:05 +01:00
|
|
|
dtos = append(dtos, new_private_ip)
|
|
|
|
}
|
|
|
|
return dtos
|
|
|
|
}
|
|
|
|
|
|
|
|
//Flattens network interface attachment into a map[string]interface
|
2015-04-16 22:28:18 +02:00
|
|
|
func flattenAttachment(a *ec2.NetworkInterfaceAttachment) map[string]interface{} {
|
2015-03-17 14:00:36 +01:00
|
|
|
att := make(map[string]interface{})
|
2015-03-17 13:42:05 +01:00
|
|
|
att["instance"] = *a.InstanceID
|
|
|
|
att["device_index"] = *a.DeviceIndex
|
2015-03-17 14:00:36 +01:00
|
|
|
att["attachment_id"] = *a.AttachmentID
|
2015-03-17 13:42:05 +01:00
|
|
|
return att
|
2015-03-17 14:00:36 +01:00
|
|
|
}
|
2015-04-16 22:18:01 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|