func GetHostedZones(search string) (*HostedZones, error) {
	hostedZoneList := new(HostedZones)
	regions := GetRegionListWithoutIgnored()
	rand.Seed(time.Now().UnixNano())
	region := regions[rand.Intn(len(regions))]  
	err := GetRegionHostedZones(*region.RegionName, hostedZoneList, search)
	return hostedZoneList, err
}
GetHostedZones returns a list of Route53 Hosted Zones that match the provided search term
package aws
import (
	"errors"
	"fmt"
	"math/rand"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"
	"github.com/asaskevich/govalidator"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/route53"
	"github.com/murdinc/awsm/models"
	"github.com/murdinc/terminal"
	"github.com/olekukonko/tablewriter"
)
type ResourceRecords []ResourceRecord
type ResourceRecord models.ResourceRecord
type ResourceRecordChanges []ResourceRecordChange
type ResourceRecordChange models.ResourceRecordChange
type HostedZones []HostedZone
type HostedZone models.HostedZone
func DeleteResourceRecords(search string, dryRun bool) error {
	if dryRun {
		terminal.Information("--dry-run flag is set, not making any actual changes!")
	}
	resourceRecordList, err := GetResourceRecords(search)
	if err != nil {
		return err
	}
	if len(*resourceRecordList) > 0 {
		resourceRecordList.PrintTable()
	} else {
		return errors.New("No Resource Records found, Aborting!")
	}
	if !terminal.PromptBool("Are you sure you want to delete these Resource Records?") {
		return errors.New("Aborting!")
	}
	changeSet := make(map[string][]ResourceRecordChange)
	for _, record := range *resourceRecordList {
		changeSet[record.HostedZoneId] = append(changeSet[record.HostedZoneId],
			ResourceRecordChange{
				Action: "DELETE",
				Name:   record.Name,
				Values: record.Values,
				Type:   record.Type,
				TTL:    record.TTL,
			},
		)
	}
	return changeResourceRecord(changeSet, dryRun)
}
func (h *HostedZone) GetResourceRecords(search string) (*ResourceRecords, error) {
	regions := GetRegionListWithoutIgnored()
	rand.Seed(time.Now().UnixNano())
	region := regions[rand.Intn(len(regions))]  
	resourceRecordList := new(ResourceRecords)
	err := GetRegionResourceRecords(h.Id, *region.RegionName, resourceRecordList, search)
	if err != nil {
		return resourceRecordList, err
	}
	if len(*resourceRecordList) == 0 {
		return resourceRecordList, errors.New("No Resource Records Found!")
	}
	return resourceRecordList, nil
}
func CreateResourceRecord(name, value string, ttl string, force, private, dryRun bool) error {
	if value == "" {
		terminal.Notice("No value given, attempting to get value from ec2 meta-data...")
		sess := session.Must(session.NewSession())
		svc := ec2metadata.New(sess)
		if private {
			localIp, err := svc.GetMetadata("local-ipv4")
			if localIp == "" || err != nil {
				terminal.Notice("Unable to find a Local IPv4 Address, please provide a value instead. Aborting!")
				return err
			}
			value = localIp
		} else {
			terminal.Information("Trying to fine a Public IP Address...")
			publicIp, err := svc.GetMetadata("public-ipv4")
			if publicIp == "" || err != nil {
				terminal.Notice("Unable to find a Public IPv4 Address, looking for a Local IPv4 Address...")
				localIp, err := svc.GetMetadata("local-ipv4")
				if localIp == "" || err != nil {
					terminal.Notice("Unable to find a Local IPv4 Address, please provide a value instead. Aborting!")
					return err
				}
				value = localIp
			} else {
				value = publicIp
			}
			terminal.Delta("Using IP Address: " + value)
		}
	}
	if dryRun {
		terminal.Information("--dry-run flag is set, not making any actual changes!")
	}
	action := "CREATE"
	if force {
		terminal.Information("--force flag is set, using UPSERT Action!")
		action = "UPSERT"
	}
	var ttlInt int64
	if ttl == "" {
		ttlInt = 300
		terminal.Information("Using default TTL of [300]")
	} else {
		var err error
		ttlInt, err = strconv.ParseInt(ttl, 10, 64)
		if err != nil {
			return errors.New("Unable to use TTL value [" + ttl + "]")
		}
	}
	if validRecord(name) {
		terminal.Information("Name [" + name + "] appears to be a valid DNS Record.")
	} else {
		return errors.New("Name [" + name + "] appears to be invalid!")
	}
	recordType := "CNAME"
	switch {
	default:
		return errors.New("Value [" + value + "] is of an unknown type!")
	case govalidator.IsIPv4(value):
		terminal.Information("Value [" + value + "] appears to be a valid IPv4 Address.")
		recordType = "A"
	case govalidator.IsIPv6(value):
		terminal.Information("Value [" + value + "] appears to be a valid IPv6 Address.")
		recordType = "AAAA"
	}
	hostedZone, err := findHostedZone(name)
	if err != nil {
		return err
	}
	terminal.Information(fmt.Sprintf("Found Hosted Zone [%s - %s] with [%d] existing records.", hostedZone.Id, hostedZone.Name, hostedZone.ResourceRecordSetCount))
	changeSet := make(map[string][]ResourceRecordChange)
	changeSet[hostedZone.Id] = append(changeSet[hostedZone.Id],
		ResourceRecordChange{
			Action: action,
			Name:   name,
			Values: []string{value},
			Type:   recordType,
			TTL:    int(ttlInt),
		},
	)
	err = changeResourceRecord(changeSet, dryRun)
	if !force && err != nil && strings.Contains(err.Error(), "already exists") {
		terminal.Information(err.Error())
		update := terminal.PromptBool("Do you want to update (UPSERT) it instead?")
		if update {
			changeSet[hostedZone.Id][0].Action = "UPSERT"
			return changeResourceRecord(changeSet, dryRun)
		}
		terminal.ErrorLine("Aborting!")
		return nil
	}
	return err
}
func findHostedZone(name string) (HostedZone, error) {
	hostedZoneSearch := strings.Split(name, ".")[1:]
	hostedZoneSearchLen := len(hostedZoneSearch)
	hostedZones, err := GetHostedZones("")
	if err != nil {
		return HostedZone{}, err
	}
	for _, hostedZone := range *hostedZones {
		hostedZoneChunked := strings.Split(hostedZone.Name[:len(hostedZone.Name)-1], ".")
		for s := 0; s < hostedZoneSearchLen; s++ {
			hostedZoneSearchChunk := hostedZoneSearch[s:]
			if reflect.DeepEqual(hostedZoneChunked, hostedZoneSearchChunk) {
				return hostedZone, nil
			}
		}
	}
	return HostedZone{}, errors.New("Unable to find Hosted Zone for [" + name + "]")
}
func changeResourceRecord(changeSet map[string][]ResourceRecordChange, dryRun bool) error {
	for id, changes := range changeSet {
		params := &route53.ChangeResourceRecordSetsInput{
			HostedZoneId: aws.String(id),
		}
		recordChanges := make([]*route53.Change, len(changes))
		for i, change := range changes {
			recordChanges[i] = &route53.Change{
				Action: aws.String(change.Action),
				ResourceRecordSet: &route53.ResourceRecordSet{
					Name: aws.String(change.Name),
					Type: aws.String(change.Type),
					TTL:  aws.Int64(int64(change.TTL)),
				},
			}
			resourceRecords := make([]*route53.ResourceRecord, len(change.Values))
			for j, value := range change.Values {
				resourceRecords[j] = new(route53.ResourceRecord)
				resourceRecords[j].SetValue(value)
			}
			terminal.Delta("[" + change.Action + "] - Resource Record [" + change.Name + "] : [" + strings.Join(change.Values, ", ") + "]")
			recordChanges[i].ResourceRecordSet.SetResourceRecords(resourceRecords)
		}
		changeBatch := &route53.ChangeBatch{}
		changeBatch.SetChanges(recordChanges)
		params.SetChangeBatch(changeBatch)
		if !dryRun {
			regions := GetRegionListWithoutIgnored()
			rand.Seed(time.Now().UnixNano())
			region := regions[rand.Intn(len(regions))]  
			sess := session.Must(session.NewSession(&aws.Config{Region: region.RegionName}))
			svc := route53.New(sess)
			_, err := svc.ChangeResourceRecordSets(params)
			if err != nil {
				if awsErr, ok := err.(awserr.Error); ok {
					return errors.New(awsErr.Message())
				}
				return err
			}
		}
	}
	terminal.Information("Done!")
	return nil
}
func GetResourceRecords(search string) (*ResourceRecords, error) {
	resourceRecordList := new(ResourceRecords)
	hostZones, err := GetHostedZones("")
	if err != nil {
		return resourceRecordList, err
	}
	regions := GetRegionListWithoutIgnored()
	rand.Seed(time.Now().UnixNano())
	region := regions[rand.Intn(len(regions))]  
	for _, host := range *hostZones {
		err = GetRegionResourceRecords(host.Id, *region.RegionName, resourceRecordList, search)
		if err != nil {
			return resourceRecordList, err
		}
	}
	return resourceRecordList, err
}
func GetRegionResourceRecords(hostedZoneId, region string, resourceRecordList *ResourceRecords, search string) error {
	params := &route53.ListResourceRecordSetsInput{
		HostedZoneId: aws.String(hostedZoneId),
	}
	sess := session.Must(session.NewSession(&aws.Config{Region: aws.String(region)}))
	svc := route53.New(sess)
	var resourceRecordSetsResult []*route53.ResourceRecordSet
ListLoop:
	for {
		result, err := svc.ListResourceRecordSets(params)
		if err != nil {
			return err
		}
		resourceRecordSetsResult = append(resourceRecordSetsResult, result.ResourceRecordSets...)
		if !aws.BoolValue(result.IsTruncated) {
			break ListLoop
		}
		params.StartRecordName = result.NextRecordName
	}
	resourceRecords := make(ResourceRecords, len(resourceRecordSetsResult))
	for i, b := range resourceRecordSetsResult {
		resourceRecords[i].Marshal(b, hostedZoneId)
	}
	if search != "" {
		term := regexp.MustCompile(search)
	Loop:
		for i, kp := range resourceRecords {
			rhostedZones := reflect.ValueOf(kp)
			for k := 0; k < rhostedZones.NumField(); k++ {
				sVal := rhostedZones.Field(k).String()
				if term.MatchString(sVal) {
					*resourceRecordList = append(*resourceRecordList, resourceRecords[i])
					continue Loop
				}
			}
		}
	} else {
		*resourceRecordList = append(*resourceRecordList, resourceRecords[:]...)
	}
	return nil
}