1package main
2
3import (
4	"fmt"
5	"log"
6	"sort"
7
8	"github.com/aws/aws-sdk-go/aws"
9	"github.com/aws/aws-sdk-go/aws/session"
10	"github.com/aws/aws-sdk-go/service/ec2"
11)
12
13func clientForRegion(region string) (*ec2.EC2, error) {
14	sess, err := session.NewSession(&aws.Config{
15		Region: &region,
16	})
17	if err != nil {
18		return nil, err
19	}
20	return ec2.New(sess), nil
21}
22
23func getRegions(client *ec2.EC2) ([]*ec2.Region, error) {
24	all := false // beyond account access
25	regions, err := client.DescribeRegions(&ec2.DescribeRegionsInput{
26		AllRegions: &all,
27	})
28	if err != nil {
29		return nil, err
30	}
31	return regions.Regions, nil
32}
33
34type specs struct {
35	Cores int
36	Speed float64
37}
38
39func (s specs) String() string {
40	return fmt.Sprintf("(%d %.2f)", s.Cores, s.Speed)
41}
42
43func getData(regions []*ec2.Region, verbose bool) (map[string]map[string]specs, error) {
44	data := make(map[string]map[string]specs)
45
46	for _, region := range regions {
47		rData, rProblems, err := getDataForRegion(*region.RegionName)
48		if err != nil {
49			return nil, err
50		}
51		data[*region.RegionName] = rData
52
53		if verbose {
54			log.Println("region", *region.RegionName, "got data for", len(rData), "instance types", len(rProblems), "incomplete")
55			instanceProblems(rProblems)
56		}
57	}
58
59	return data, nil
60}
61
62func instanceProblems(problems map[string]string) {
63	types := make([]string, 0, len(problems))
64	for k := range problems {
65		types = append(types, k)
66	}
67	sort.Strings(types)
68	for _, iType := range types {
69		log.Println(" ->", iType, problems[iType])
70	}
71}
72
73func getDataForRegion(region string) (map[string]specs, map[string]string, error) {
74	client, err := clientForRegion(region)
75	if err != nil {
76		return nil, nil, err
77	}
78
79	data := make(map[string]specs)
80	problems := make(map[string]string)
81	regionInfoPage(client, true, region, nil, data, problems)
82	return data, problems, nil
83}
84
85func regionInfoPage(client *ec2.EC2, first bool, region string, token *string, data map[string]specs, problems map[string]string) {
86	if first || token != nil {
87		output, err := client.DescribeInstanceTypes(&ec2.DescribeInstanceTypesInput{
88			NextToken: token,
89		})
90		if err != nil {
91			log.Fatal(err)
92		}
93
94		// recursively accumulate each page of data
95		regionInfoAccumulate(output, data, problems)
96		regionInfoPage(client, false, region, output.NextToken, data, problems)
97	}
98}
99
100func regionInfoAccumulate(output *ec2.DescribeInstanceTypesOutput, data map[string]specs, problems map[string]string) {
101	for _, iType := range output.InstanceTypes {
102		switch {
103
104		case iType.ProcessorInfo == nil:
105			fallthrough
106		case iType.ProcessorInfo.SustainedClockSpeedInGhz == nil:
107			problems[*iType.InstanceType] = "missing clock Speed"
108			continue
109
110		case iType.VCpuInfo == nil:
111			fallthrough
112		case iType.VCpuInfo.DefaultVCpus == nil:
113			problems[*iType.InstanceType] = "missing virtual cpu Cores"
114			continue
115
116		default:
117			data[*iType.InstanceType] = specs{
118				Speed: *iType.ProcessorInfo.SustainedClockSpeedInGhz,
119				Cores: int(*iType.VCpuInfo.DefaultVCpus),
120			}
121			continue
122		}
123	}
124}
125