hack/code/bandwidth_gen/main.go (130 lines of code) (raw):

/* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "context" "flag" "fmt" "go/format" "log" "net/http" "os" "sort" "strconv" "strings" "github.com/PuerkitoBio/goquery" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/ec2" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/samber/lo" ) var uriSelectors = map[string]string{ "https://docs.aws.amazon.com/ec2/latest/instancetypes/gp.html": "#gp_network", "https://docs.aws.amazon.com/ec2/latest/instancetypes/co.html": "#co_network", "https://docs.aws.amazon.com/ec2/latest/instancetypes/mo.html": "#mo_network", "https://docs.aws.amazon.com/ec2/latest/instancetypes/so.html": "#so_network", "https://docs.aws.amazon.com/ec2/latest/instancetypes/ac.html": "#ac_network", "https://docs.aws.amazon.com/ec2/latest/instancetypes/hpc.html": "#hpc_network", "https://docs.aws.amazon.com/ec2/latest/instancetypes/pg.html": "#pg_network", } const fileFormat = ` %s package instancetype // GENERATED FILE. DO NOT EDIT DIRECTLY. // Update hack/code/bandwidth_gen.go and re-generate to edit // You can add instance types by adding to the --instance-types CLI flag var ( InstanceTypeBandwidthMegabits = map[string]int64{ %s } ) ` func main() { flag.Parse() if flag.NArg() != 1 { log.Fatalf("Usage: `bandwidth_gen.go pkg/providers/instancetype/zz_generated.bandwidth.go`") } bandwidth := map[ec2types.InstanceType]int64{} vagueBandwidth := map[ec2types.InstanceType]string{} for uri, selector := range uriSelectors { func() { response := lo.Must(http.Get(uri)) defer response.Body.Close() doc := lo.Must(goquery.NewDocumentFromReader(response.Body)) // grab the table that contains the network performance values. Some instance types will have vague // description for bandwidth such as "Very Low", "Low", "Low to Moderate", etc. These instance types // will be ignored since we don't know the exact bandwidth for these instance types for _, row := range doc.Find(selector).NextAllFiltered(".table-container").Eq(0).Find("tbody").Find("tr").Nodes { instanceTypeName := strings.TrimSpace(row.FirstChild.NextSibling.FirstChild.Data) if !strings.ContainsAny(instanceTypeName, ".") { continue } bandwidthData := row.FirstChild.NextSibling.NextSibling.NextSibling.FirstChild.Data // exclude all rows that contain any of the following strings if containsAny(bandwidthData, "Low", "Moderate", "High", "Up to") { vagueBandwidth[ec2types.InstanceType(instanceTypeName)] = bandwidthData continue } bandwidthSlice := strings.Split(bandwidthData, " ") // if the first value contains a multiplier i.e. (4x 100 Gigabit) if strings.HasSuffix(bandwidthSlice[0], "x") { multiplier := lo.Must(strconv.ParseFloat(bandwidthSlice[0][:len(bandwidthSlice[0])-1], 64)) bandwidth[ec2types.InstanceType(instanceTypeName)] = int64(lo.Must(strconv.ParseFloat(bandwidthSlice[1], 64)) * 1000 * multiplier) // Check row for instancetype for described network performance value i.e (2 Gigabit) } else { bandwidth[ec2types.InstanceType(instanceTypeName)] = int64(lo.Must(strconv.ParseFloat(bandwidthSlice[0], 64)) * 1000) } } }() } allInstanceTypes := getAllInstanceTypes() instanceTypes := lo.Keys(bandwidth) // 2d sort for readability sort.SliceStable(allInstanceTypes, func(i, j int) bool { return allInstanceTypes[i] < allInstanceTypes[j] }) sort.SliceStable(instanceTypes, func(i, j int) bool { return instanceTypes[i] < instanceTypes[j] }) sort.SliceStable(instanceTypes, func(i, j int) bool { return bandwidth[instanceTypes[i]] < bandwidth[instanceTypes[j]] }) // Generate body var body string for _, instanceType := range lo.Without(allInstanceTypes, instanceTypes...) { if lo.Contains(lo.Keys(vagueBandwidth), instanceType) { body += fmt.Sprintf("// %s has vague bandwidth information, bandwidth is %s\n", instanceType, vagueBandwidth[instanceType]) continue } body += fmt.Sprintf("// %s is not available in https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-network-bandwidth.html\n", instanceType) } for _, instanceType := range instanceTypes { body += fmt.Sprintf("\t\"%s\": %d,\n", instanceType, bandwidth[instanceType]) } license := lo.Must(os.ReadFile("hack/boilerplate.go.txt")) // Format and print to the file formatted := lo.Must(format.Source([]byte(fmt.Sprintf(fileFormat, license, body)))) file := lo.Must(os.Create(flag.Args()[0])) lo.Must(file.Write(formatted)) file.Close() } func containsAny(value string, excludedSubstrings ...string) bool { for _, str := range excludedSubstrings { if strings.Contains(value, str) { return true } } return false } func getAllInstanceTypes() []ec2types.InstanceType { if err := os.Setenv("AWS_SDK_LOAD_CONFIG", "true"); err != nil { log.Fatalf("setting AWS_SDK_LOAD_CONFIG, %s", err) } if err := os.Setenv("AWS_REGION", "us-east-1"); err != nil { log.Fatalf("setting AWS_REGION, %s", err) } ctx := context.Background() cfg := lo.Must(config.LoadDefaultConfig(ctx)) ec2api := ec2.NewFromConfig(cfg) var allInstanceTypes []ec2types.InstanceType params := &ec2.DescribeInstanceTypesInput{} // Retrieve the instance types in a loop using NextToken for { result := lo.Must(ec2api.DescribeInstanceTypes(ctx, params)) allInstanceTypes = append(allInstanceTypes, lo.Map(result.InstanceTypes, func(info ec2types.InstanceTypeInfo, _ int) ec2types.InstanceType { return info.InstanceType })...) // Check if they are any instances left if result.NextToken != nil { params.NextToken = result.NextToken } else { break } } return allInstanceTypes }