diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6e756d8..2eb66cc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,7 +3,7 @@ name: EC2 Instance Selector CI and Release on: [push, pull_request, workflow_dispatch] env: - DEFAULT_GO_VERSION: ^1.17 + DEFAULT_GO_VERSION: ^1.18 GITHUB_USERNAME: ${{ secrets.EC2_BOT_GITHUB_USERNAME }} GITHUB_TOKEN: ${{ secrets.EC2_BOT_GITHUB_TOKEN }} DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} diff --git a/Dockerfile b/Dockerfile index ab66165..f39964f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17 as builder +FROM golang:1.18 as builder ## GOLANG env ARG GOPROXY="https://proxy.golang.org|direct" diff --git a/README.md b/README.md index 2a353c5..c1ee48c 100644 --- a/README.md +++ b/README.md @@ -166,11 +166,14 @@ ec2-instance-selector --memory-min 4 --memory-max 8 --vcpus-min 4 --vcpus-max 8 Filter Flags: --allow-list string List of allowed instance types to select from w/ regex syntax (Example: m[3-5]\.*) + --auto-recovery EC2 Auto-Recovery supported -z, --availability-zones strings Availability zones or zone ids to check EC2 capacity offered in specific AZs --baremetal Bare Metal instance types (.metal instances) -b, --burst-support Burstable instance types -a, --cpu-architecture string CPU architecture [x86_64/amd64, x86_64_mac, i386, or arm64] + --cpu-manufacturer string CPU manufacturer [amd, intel, aws] --current-generation Current generation instance types (explicitly set this to false to not return current generation instance types) + --dedicated-hosts Dedicated Hosts supported --deny-list string List of instance types which should be excluded w/ regex syntax (Example: m[1-2]\.*) --disk-encryption EBS or local instance storage where encryption is supported or required --disk-type string Disk Type: [hdd or ssd] @@ -187,14 +190,19 @@ Filter Flags: --efa-support Instance types that support Elastic Fabric Adapters (EFA) -e, --ena-support Instance types where ENA is supported or required -f, --fpga-support FPGA instance types + --free-tier Free Tier supported + --gpu-manufacturer string GPU Manufacturer name (Example: NVIDIA) --gpu-memory-total string Number of GPUs' total memory (Example: 4 GiB) (sets --gpu-memory-total-min and -max to the same value) --gpu-memory-total-max string Maximum Number of GPUs' total memory (Example: 4 GiB) If --gpu-memory-total-min is not specified, the lower bound will be 0 --gpu-memory-total-min string Minimum Number of GPUs' total memory (Example: 4 GiB) If --gpu-memory-total-max is not specified, the upper bound will be infinity + --gpu-model string GPU Model name (Example: K520) -g, --gpus int Total Number of GPUs (Example: 4) (sets --gpus-min and -max to the same value) --gpus-max int Maximum Total Number of GPUs (Example: 4) If --gpus-min is not specified, the lower bound will be 0 --gpus-min int Minimum Total Number of GPUs (Example: 4) If --gpus-max is not specified, the upper bound will be infinity --hibernation-support Hibernation supported --hypervisor string Hypervisor: [xen or nitro] + --inference-accelerator-manufacturer string Inference Accelerator Manufacturer name (Example: AWS) + --inference-accelerator-model string Inference Accelerator Model name (Example: Inferentia) --inference-accelerators int Total Number of inference accelerators (Example: 4) (sets --inference-accelerators-min and -max to the same value) --inference-accelerators-max int Maximum Total Number of inference accelerators (Example: 4) If --inference-accelerators-min is not specified, the lower bound will be 0 --inference-accelerators-min int Minimum Total Number of inference accelerators (Example: 4) If --inference-accelerators-max is not specified, the upper bound will be infinity diff --git a/THIRD_PARTY_LICENSES b/THIRD_PARTY_LICENSES index 2a3d9c9..8fcb97b 100644 --- a/THIRD_PARTY_LICENSES +++ b/THIRD_PARTY_LICENSES @@ -1,3 +1,6 @@ +github.com/aws/amazon-ec2-instance-selector (Apache-2.0) Third Party Licenses: +--- + ** aws-sdk-go; version v1.29.33 -- https://github.com/aws/aws-sdk-go AWS SDK for Go Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/cmd/main.go b/cmd/main.go index 29cc902..f57e56d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -49,41 +49,49 @@ const ( // Filter Flag Constants const ( - vcpus = "vcpus" - memory = "memory" - vcpusToMemoryRatio = "vcpus-to-memory-ratio" - cpuArchitecture = "cpu-architecture" - gpus = "gpus" - gpuMemoryTotal = "gpu-memory-total" - inferenceAccelerators = "inference-accelerators" - placementGroupStrategy = "placement-group-strategy" - usageClass = "usage-class" - rootDeviceType = "root-device-type" - enaSupport = "ena-support" - efaSupport = "efa-support" - hibernationSupport = "hibernation-support" - baremetal = "baremetal" - fpgaSupport = "fpga-support" - burstSupport = "burst-support" - hypervisor = "hypervisor" - availabilityZones = "availability-zones" - currentGeneration = "current-generation" - networkInterfaces = "network-interfaces" - networkPerformance = "network-performance" - networkEncryption = "network-encryption" - ipv6 = "ipv6" - allowList = "allow-list" - denyList = "deny-list" - virtualizationType = "virtualization-type" - pricePerHour = "price-per-hour" - instanceStorage = "instance-storage" - diskType = "disk-type" - diskEncryption = "disk-encryption" - nvme = "nvme" - ebsOptimized = "ebs-optimized" - ebsOptimizedBaselineBandwidth = "ebs-optimized-baseline-bandwidth" - ebsOptimizedBaselineThroughput = "ebs-optimized-baseline-throughput" - ebsOptimizedBaselineIOPS = "ebs-optimized-baseline-iops" + vcpus = "vcpus" + memory = "memory" + vcpusToMemoryRatio = "vcpus-to-memory-ratio" + cpuArchitecture = "cpu-architecture" + cpuManufacturer = "cpu-manufacturer" + gpus = "gpus" + gpuMemoryTotal = "gpu-memory-total" + gpuManufacturer = "gpu-manufacturer" + gpuModel = "gpu-model" + inferenceAccelerators = "inference-accelerators" + inferenceAcceleratorManufacturer = "inference-accelerator-manufacturer" + inferenceAcceleratorModel = "inference-accelerator-model" + placementGroupStrategy = "placement-group-strategy" + usageClass = "usage-class" + rootDeviceType = "root-device-type" + enaSupport = "ena-support" + efaSupport = "efa-support" + hibernationSupport = "hibernation-support" + baremetal = "baremetal" + fpgaSupport = "fpga-support" + burstSupport = "burst-support" + hypervisor = "hypervisor" + availabilityZones = "availability-zones" + currentGeneration = "current-generation" + networkInterfaces = "network-interfaces" + networkPerformance = "network-performance" + networkEncryption = "network-encryption" + ipv6 = "ipv6" + allowList = "allow-list" + denyList = "deny-list" + virtualizationType = "virtualization-type" + pricePerHour = "price-per-hour" + instanceStorage = "instance-storage" + diskType = "disk-type" + diskEncryption = "disk-encryption" + nvme = "nvme" + ebsOptimized = "ebs-optimized" + ebsOptimizedBaselineBandwidth = "ebs-optimized-baseline-bandwidth" + ebsOptimizedBaselineThroughput = "ebs-optimized-baseline-throughput" + ebsOptimizedBaselineIOPS = "ebs-optimized-baseline-iops" + freeTier = "free-tier" + autoRecovery = "auto-recovery" + dedicatedHosts = "dedicated-hosts" ) // Aggregate Filter Flags @@ -141,9 +149,14 @@ Full docs can be found at github.com/aws/amazon-` + binName cli.ByteQuantityMinMaxRangeFlags(memory, cli.StringMe("m"), nil, "Amount of Memory available (Example: 4 GiB)") cli.RatioFlag(vcpusToMemoryRatio, nil, nil, "The ratio of vcpus to GiBs of memory. (Example: 1:2)") cli.StringOptionsFlag(cpuArchitecture, cli.StringMe("a"), nil, "CPU architecture [x86_64/amd64, x86_64_mac, i386, or arm64]", []string{"x86_64", "x86_64_mac", "amd64", "i386", "arm64"}) + cli.StringOptionsFlag(cpuManufacturer, nil, nil, "CPU manufacturer [amd, intel, aws]", []string{"amd", "intel", "aws"}) cli.IntMinMaxRangeFlags(gpus, cli.StringMe("g"), nil, "Total Number of GPUs (Example: 4)") cli.ByteQuantityMinMaxRangeFlags(gpuMemoryTotal, nil, nil, "Number of GPUs' total memory (Example: 4 GiB)") + cli.StringFlag(gpuManufacturer, nil, nil, "GPU Manufacturer name (Example: NVIDIA)", nil) + cli.StringFlag(gpuModel, nil, nil, "GPU Model name (Example: K520)", nil) cli.IntMinMaxRangeFlags(inferenceAccelerators, nil, nil, "Total Number of inference accelerators (Example: 4)") + cli.StringFlag(inferenceAcceleratorManufacturer, nil, nil, "Inference Accelerator Manufacturer name (Example: AWS)", nil) + cli.StringFlag(inferenceAcceleratorModel, nil, nil, "Inference Accelerator Model name (Example: Inferentia)", nil) cli.StringOptionsFlag(placementGroupStrategy, nil, nil, "Placement group strategy: [cluster, partition, spread]", []string{"cluster", "partition", "spread"}) cli.StringOptionsFlag(usageClass, cli.StringMe("u"), nil, "Usage class: [spot or on-demand]", []string{"spot", "on-demand"}) cli.StringOptionsFlag(rootDeviceType, nil, nil, "Supported root device types: [ebs or instance-store]", []string{"ebs", "instance-store"}) @@ -172,6 +185,9 @@ Full docs can be found at github.com/aws/amazon-` + binName cli.ByteQuantityMinMaxRangeFlags(ebsOptimizedBaselineBandwidth, nil, nil, "EBS Optimized baseline bandwidth (Example: 4 GiB)") cli.ByteQuantityMinMaxRangeFlags(ebsOptimizedBaselineThroughput, nil, nil, "EBS Optimized baseline throughput per second (Example: 4 GiB)") cli.IntMinMaxRangeFlags(ebsOptimizedBaselineIOPS, nil, nil, "EBS Optimized baseline IOPS per second (Example: 10000)") + cli.BoolFlag(freeTier, nil, nil, "Free Tier supported") + cli.BoolFlag(autoRecovery, nil, nil, "EC2 Auto-Recovery supported") + cli.BoolFlag(dedicatedHosts, nil, nil, "Dedicated Hosts supported") // Suite Flags - higher level aggregate filters that return opinionated result @@ -247,46 +263,54 @@ Full docs can be found at github.com/aws/amazon-` + binName } filters := selector.Filters{ - VCpusRange: cli.IntRangeMe(flags[vcpus]), - MemoryRange: cli.ByteQuantityRangeMe(flags[memory]), - VCpusToMemoryRatio: cli.Float64Me(flags[vcpusToMemoryRatio]), - CPUArchitecture: cli.StringMe(flags[cpuArchitecture]), - GpusRange: cli.IntRangeMe(flags[gpus]), - GpuMemoryRange: cli.ByteQuantityRangeMe(flags[gpuMemoryTotal]), - InferenceAcceleratorsRange: cli.IntRangeMe(flags[inferenceAccelerators]), - PlacementGroupStrategy: cli.StringMe(flags[placementGroupStrategy]), - UsageClass: cli.StringMe(flags[usageClass]), - RootDeviceType: cli.StringMe(flags[rootDeviceType]), - EnaSupport: cli.BoolMe(flags[enaSupport]), - EfaSupport: cli.BoolMe(flags[efaSupport]), - HibernationSupported: cli.BoolMe(flags[hibernationSupport]), - Hypervisor: cli.StringMe(flags[hypervisor]), - BareMetal: cli.BoolMe(flags[baremetal]), - Fpga: cli.BoolMe(flags[fpgaSupport]), - Burstable: cli.BoolMe(flags[burstSupport]), - Region: cli.StringMe(flags[region]), - AvailabilityZones: cli.StringSliceMe(flags[availabilityZones]), - CurrentGeneration: cli.BoolMe(flags[currentGeneration]), - MaxResults: cli.IntMe(flags[maxResults]), - NetworkInterfaces: cli.IntRangeMe(flags[networkInterfaces]), - NetworkPerformance: cli.IntRangeMe(flags[networkPerformance]), - NetworkEncryption: cli.BoolMe(flags[networkEncryption]), - IPv6: cli.BoolMe(flags[ipv6]), - AllowList: cli.RegexMe(flags[allowList]), - DenyList: cli.RegexMe(flags[denyList]), - InstanceTypeBase: cli.StringMe(flags[instanceTypeBase]), - Flexible: cli.BoolMe(flags[flexible]), - Service: cli.StringMe(flags[service]), - VirtualizationType: cli.StringMe(flags[virtualizationType]), - PricePerHour: cli.Float64RangeMe(flags[pricePerHour]), - InstanceStorageRange: cli.ByteQuantityRangeMe(flags[instanceStorage]), - DiskType: cli.StringMe(flags[diskType]), - DiskEncryption: cli.BoolMe(flags[diskEncryption]), - NVME: cli.BoolMe(flags[nvme]), - EBSOptimized: cli.BoolMe(flags[ebsOptimized]), - EBSOptimizedBaselineBandwidth: cli.ByteQuantityRangeMe(flags[ebsOptimizedBaselineBandwidth]), - EBSOptimizedBaselineThroughput: cli.ByteQuantityRangeMe(flags[ebsOptimizedBaselineThroughput]), - EBSOptimizedBaselineIOPS: cli.IntRangeMe(flags[ebsOptimizedBaselineIOPS]), + VCpusRange: cli.IntRangeMe(flags[vcpus]), + MemoryRange: cli.ByteQuantityRangeMe(flags[memory]), + VCpusToMemoryRatio: cli.Float64Me(flags[vcpusToMemoryRatio]), + CPUArchitecture: cli.StringMe(flags[cpuArchitecture]), + CPUManufacturer: cli.StringMe(flags[cpuManufacturer]), + GpusRange: cli.IntRangeMe(flags[gpus]), + GpuMemoryRange: cli.ByteQuantityRangeMe(flags[gpuMemoryTotal]), + GPUManufacturer: cli.StringMe(flags[gpuManufacturer]), + GPUModel: cli.StringMe(flags[gpuModel]), + InferenceAcceleratorsRange: cli.IntRangeMe(flags[inferenceAccelerators]), + InferenceAcceleratorManufacturer: cli.StringMe(flags[inferenceAcceleratorManufacturer]), + InferenceAcceleratorModel: cli.StringMe(flags[inferenceAcceleratorModel]), + PlacementGroupStrategy: cli.StringMe(flags[placementGroupStrategy]), + UsageClass: cli.StringMe(flags[usageClass]), + RootDeviceType: cli.StringMe(flags[rootDeviceType]), + EnaSupport: cli.BoolMe(flags[enaSupport]), + EfaSupport: cli.BoolMe(flags[efaSupport]), + HibernationSupported: cli.BoolMe(flags[hibernationSupport]), + Hypervisor: cli.StringMe(flags[hypervisor]), + BareMetal: cli.BoolMe(flags[baremetal]), + Fpga: cli.BoolMe(flags[fpgaSupport]), + Burstable: cli.BoolMe(flags[burstSupport]), + Region: cli.StringMe(flags[region]), + AvailabilityZones: cli.StringSliceMe(flags[availabilityZones]), + CurrentGeneration: cli.BoolMe(flags[currentGeneration]), + MaxResults: cli.IntMe(flags[maxResults]), + NetworkInterfaces: cli.IntRangeMe(flags[networkInterfaces]), + NetworkPerformance: cli.IntRangeMe(flags[networkPerformance]), + NetworkEncryption: cli.BoolMe(flags[networkEncryption]), + IPv6: cli.BoolMe(flags[ipv6]), + AllowList: cli.RegexMe(flags[allowList]), + DenyList: cli.RegexMe(flags[denyList]), + InstanceTypeBase: cli.StringMe(flags[instanceTypeBase]), + Flexible: cli.BoolMe(flags[flexible]), + Service: cli.StringMe(flags[service]), + VirtualizationType: cli.StringMe(flags[virtualizationType]), + PricePerHour: cli.Float64RangeMe(flags[pricePerHour]), + InstanceStorageRange: cli.ByteQuantityRangeMe(flags[instanceStorage]), + DiskType: cli.StringMe(flags[diskType]), + DiskEncryption: cli.BoolMe(flags[diskEncryption]), + NVME: cli.BoolMe(flags[nvme]), + EBSOptimized: cli.BoolMe(flags[ebsOptimized]), + EBSOptimizedBaselineBandwidth: cli.ByteQuantityRangeMe(flags[ebsOptimizedBaselineBandwidth]), + EBSOptimizedBaselineThroughput: cli.ByteQuantityRangeMe(flags[ebsOptimizedBaselineThroughput]), + EBSOptimizedBaselineIOPS: cli.IntRangeMe(flags[ebsOptimizedBaselineIOPS]), + FreeTier: cli.BoolMe(flags[freeTier]), + AutoRecovery: cli.BoolMe(flags[autoRecovery]), + DedicatedHosts: cli.BoolMe(flags[dedicatedHosts]), } if flags[verbose] != nil { diff --git a/go.mod b/go.mod index 30da4e9..388553c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/aws/amazon-ec2-instance-selector/v2 -go 1.17 +go 1.18 require ( github.com/aws/aws-sdk-go v1.43.31 diff --git a/pkg/selector/comparators.go b/pkg/selector/comparators.go index 33c8bb1..d1070ba 100644 --- a/pkg/selector/comparators.go +++ b/pkg/selector/comparators.go @@ -29,6 +29,8 @@ const ( required = "required" ) +var amdRegex = regexp.MustCompile("[a-zA-Z0-9]+a\\.[a-zA-Z0-9]") + func isSupportedFromString(instanceTypeValue *string, target *string) bool { if target == nil { return true @@ -140,6 +142,50 @@ func getTotalGpuMemory(gpusInfo *ec2.GpuInfo) *int64 { return gpusInfo.TotalGpuMemoryInMiB } +func getGPUManufacturers(gpusInfo *ec2.GpuInfo) []*string { + if gpusInfo == nil { + return nil + } + var manufacturers []*string + for _, info := range gpusInfo.Gpus { + manufacturers = append(manufacturers, info.Manufacturer) + } + return manufacturers +} + +func getGPUModels(gpusInfo *ec2.GpuInfo) []*string { + if gpusInfo == nil { + return nil + } + var models []*string + for _, info := range gpusInfo.Gpus { + models = append(models, info.Name) + } + return models +} + +func getInferenceAcceleratorManufacturers(acceleratorInfo *ec2.InferenceAcceleratorInfo) []*string { + if acceleratorInfo == nil { + return nil + } + var manufacturers []*string + for _, info := range acceleratorInfo.Accelerators { + manufacturers = append(manufacturers, info.Manufacturer) + } + return manufacturers +} + +func getInferenceAcceleratorModels(acceleratorInfo *ec2.InferenceAcceleratorInfo) []*string { + if acceleratorInfo == nil { + return nil + } + var models []*string + for _, info := range acceleratorInfo.Accelerators { + models = append(models, info.Name) + } + return models +} + func getNetworkPerformance(networkPerformance *string) *int { if networkPerformance == nil { return aws.Int(-1) @@ -219,6 +265,16 @@ func getEBSOptimizedBaselineIOPS(ebsInfo *ec2.EbsInfo) *int64 { return ebsInfo.EbsOptimizedInfo.BaselineIops } +func getCPUManufacturer(instanceTypeInfo *ec2.InstanceTypeInfo) *string { + if contains(instanceTypeInfo.ProcessorInfo.SupportedArchitectures, ec2.ArchitectureTypeArm64) { + return aws.String("aws") + } + if amdRegex.Match([]byte(*instanceTypeInfo.InstanceType)) { + return aws.String("amd") + } + return aws.String("intel") +} + // supportSyntaxToBool takes an instance spec field that uses ["unsupported", "supported", "required", or "default"] // and transforms it to a *bool to use in filter execution func supportSyntaxToBool(instanceTypeSupport *string) *bool { @@ -244,7 +300,7 @@ func calculateVCpusToMemoryRatio(vcpusVal *int64, memoryVal *int64) *float64 { func contains(slice []*string, target string) bool { for _, it := range slice { - if it != nil && *it == target { + if it != nil && strings.EqualFold(*it, target) { return true } } diff --git a/pkg/selector/selector.go b/pkg/selector/selector.go index a7c668b..ebb10a0 100644 --- a/pkg/selector/selector.go +++ b/pkg/selector/selector.go @@ -15,12 +15,14 @@ package selector import ( + "context" "fmt" "log" "reflect" "regexp" "sort" "strings" + "sync" "time" "github.com/aws/amazon-ec2-instance-selector/v2/pkg/ec2pricing" @@ -47,40 +49,48 @@ const ( // Filter Keys - cpuArchitecture = "cpuArchitecture" - usageClass = "usageClass" - rootDeviceType = "rootDeviceType" - hibernationSupported = "hibernationSupported" - vcpusRange = "vcpusRange" - memoryRange = "memoryRange" - gpuMemoryRange = "gpuMemoryRange" - gpusRange = "gpusRange" - inferenceAcceleratorsRange = "inferenceAcceleratorsRange" - placementGroupStrategy = "placementGroupStrategy" - hypervisor = "hypervisor" - baremetal = "baremetal" - burstable = "burstable" - fpga = "fpga" - enaSupport = "enaSupport" - efaSupport = "efaSupport" - vcpusToMemoryRatio = "vcpusToMemoryRatio" - currentGeneration = "currentGeneration" - networkInterfaces = "networkInterfaces" - networkPerformance = "networkPerformance" - networkEncryption = "networkEncryption" - ipv6 = "ipv6" - allowList = "allowList" - denyList = "denyList" - instanceTypes = "instanceTypes" - virtualizationType = "virtualizationType" - instanceStorageRange = "instanceStorageRange" - diskEncryption = "diskEncryption" - diskType = "diskType" - nvme = "nvme" - ebsOptimized = "ebsOptimized" - ebsOptimizedBaselineBandwidth = "ebsOptimizedBaselineBandwidth" - ebsOptimizedBaselineIOPS = "ebsOptimizedBaselineIOPS" - ebsOptimizedBaselineThroughput = "ebsOptimizedBaselineThroughput" + cpuArchitecture = "cpuArchitecture" + cpuManufacturer = "cpuManufacturer" + usageClass = "usageClass" + rootDeviceType = "rootDeviceType" + hibernationSupported = "hibernationSupported" + vcpusRange = "vcpusRange" + memoryRange = "memoryRange" + gpuMemoryRange = "gpuMemoryRange" + gpusRange = "gpusRange" + gpuManufacturer = "gpuManufacturer" + gpuModel = "gpuModel" + inferenceAcceleratorsRange = "inferenceAcceleratorsRange" + inferenceAcceleratorManufacturer = "inferenceAcceleartorManufacturer" + inferenceAcceleratorModel = "inferenceAcceleratorModel" + placementGroupStrategy = "placementGroupStrategy" + hypervisor = "hypervisor" + baremetal = "baremetal" + burstable = "burstable" + fpga = "fpga" + enaSupport = "enaSupport" + efaSupport = "efaSupport" + vcpusToMemoryRatio = "vcpusToMemoryRatio" + currentGeneration = "currentGeneration" + networkInterfaces = "networkInterfaces" + networkPerformance = "networkPerformance" + networkEncryption = "networkEncryption" + ipv6 = "ipv6" + allowList = "allowList" + denyList = "denyList" + instanceTypes = "instanceTypes" + virtualizationType = "virtualizationType" + instanceStorageRange = "instanceStorageRange" + diskEncryption = "diskEncryption" + diskType = "diskType" + nvme = "nvme" + ebsOptimized = "ebsOptimized" + ebsOptimizedBaselineBandwidth = "ebsOptimizedBaselineBandwidth" + ebsOptimizedBaselineIOPS = "ebsOptimizedBaselineIOPS" + ebsOptimizedBaselineThroughput = "ebsOptimizedBaselineThroughput" + freeTier = "freeTier" + autoRecovery = "autoRecovery" + dedicatedHosts = "dedicatedHosts" cpuArchitectureAMD64 = "amd64" cpuArchitectureX8664 = "x86_64" @@ -210,104 +220,133 @@ func (itf Selector) rawFilter(filters Filters) ([]*instancetypes.Details, error) return nil, err } filteredInstanceTypes := []*instancetypes.Details{} - + var wg sync.WaitGroup + instanceTypes := make(chan *instancetypes.Details, len(instanceTypeDetails)) for _, instanceTypeInfo := range instanceTypeDetails { - instanceTypeName := *instanceTypeInfo.InstanceType - isFpga := instanceTypeInfo.FpgaInfo != nil - var instanceTypeHourlyPriceForFilter float64 // Price used to filter based on usage class - var instanceTypeHourlyPriceOnDemand, instanceTypeHourlyPriceSpot *float64 - // If prices are fetched, populate the fields irrespective of the price filters - if itf.EC2Pricing.OnDemandCacheCount() > 0 { - price, err := itf.EC2Pricing.GetOnDemandInstanceTypeCost(instanceTypeName) + wg.Add(1) + go func(instanceTypeInfo instancetypes.Details) { + defer wg.Done() + it, err := itf.prepareFilter(filters, instanceTypeInfo, availabilityZones, locationInstanceOfferings) if err != nil { - log.Printf("Could not retrieve instantaneous hourly on-demand price for instance type %s\n", instanceTypeName) - } else { - instanceTypeHourlyPriceOnDemand = &price - instanceTypeInfo.OndemandPricePerHour = instanceTypeHourlyPriceOnDemand + log.Println(err) } - } - if itf.EC2Pricing.SpotCacheCount() > 0 && contains(instanceTypeInfo.SupportedUsageClasses, "spot") { - price, err := itf.EC2Pricing.GetSpotInstanceTypeNDayAvgCost(instanceTypeName, availabilityZones, 30) - if err != nil { - log.Printf("Could not retrieve 30 day avg hourly spot price for instance type %s\n", instanceTypeName) - } else { - instanceTypeHourlyPriceSpot = &price - instanceTypeInfo.SpotPrice = instanceTypeHourlyPriceSpot + if it != nil { + instanceTypes <- it } + }(*instanceTypeInfo) + } + wg.Wait() + close(instanceTypes) + for it := range instanceTypes { + filteredInstanceTypes = append(filteredInstanceTypes, it) + } + return sortInstanceTypeInfo(filteredInstanceTypes), nil +} + +func (itf Selector) prepareFilter(filters Filters, instanceTypeInfo instancetypes.Details, availabilityZones []string, locationInstanceOfferings map[string]string) (*instancetypes.Details, error) { + instanceTypeName := *instanceTypeInfo.InstanceType + isFpga := instanceTypeInfo.FpgaInfo != nil + var instanceTypeHourlyPriceForFilter float64 // Price used to filter based on usage class + var instanceTypeHourlyPriceOnDemand, instanceTypeHourlyPriceSpot *float64 + // If prices are fetched, populate the fields irrespective of the price filters + if itf.EC2Pricing.OnDemandCacheCount() > 0 { + price, err := itf.EC2Pricing.GetOnDemandInstanceTypeCost(instanceTypeName) + if err != nil { + log.Printf("Could not retrieve instantaneous hourly on-demand price for instance type %s\n", instanceTypeName) + } else { + instanceTypeHourlyPriceOnDemand = &price + instanceTypeInfo.OndemandPricePerHour = instanceTypeHourlyPriceOnDemand } - if filters.PricePerHour != nil { - // If price filter is present, prices should be already fetched - // If prices are not fetched, filter should fail and the corresponding error is already printed - if filters.UsageClass != nil && *filters.UsageClass == "spot" && instanceTypeHourlyPriceSpot != nil { - instanceTypeHourlyPriceForFilter = *instanceTypeHourlyPriceSpot - } else if instanceTypeHourlyPriceOnDemand != nil { - instanceTypeHourlyPriceForFilter = *instanceTypeHourlyPriceOnDemand - } + } + if itf.EC2Pricing.SpotCacheCount() > 0 && contains(instanceTypeInfo.SupportedUsageClasses, "spot") { + price, err := itf.EC2Pricing.GetSpotInstanceTypeNDayAvgCost(instanceTypeName, availabilityZones, 30) + if err != nil { + log.Printf("Could not retrieve 30 day avg hourly spot price for instance type %s\n", instanceTypeName) + } else { + instanceTypeHourlyPriceSpot = &price + instanceTypeInfo.SpotPrice = instanceTypeHourlyPriceSpot } - - // filterToInstanceSpecMappingPairs is a map of filter name [key] to filter pair [value]. - // A filter pair includes user input filter value and instance spec value retrieved from DescribeInstanceTypes - filterToInstanceSpecMappingPairs := map[string]filterPair{ - cpuArchitecture: {filters.CPUArchitecture, instanceTypeInfo.ProcessorInfo.SupportedArchitectures}, - usageClass: {filters.UsageClass, instanceTypeInfo.SupportedUsageClasses}, - rootDeviceType: {filters.RootDeviceType, instanceTypeInfo.SupportedRootDeviceTypes}, - hibernationSupported: {filters.HibernationSupported, instanceTypeInfo.HibernationSupported}, - vcpusRange: {filters.VCpusRange, instanceTypeInfo.VCpuInfo.DefaultVCpus}, - memoryRange: {filters.MemoryRange, instanceTypeInfo.MemoryInfo.SizeInMiB}, - gpuMemoryRange: {filters.GpuMemoryRange, getTotalGpuMemory(instanceTypeInfo.GpuInfo)}, - gpusRange: {filters.GpusRange, getTotalGpusCount(instanceTypeInfo.GpuInfo)}, - inferenceAcceleratorsRange: {filters.InferenceAcceleratorsRange, getTotalAcceleratorsCount(instanceTypeInfo.InferenceAcceleratorInfo)}, - placementGroupStrategy: {filters.PlacementGroupStrategy, instanceTypeInfo.PlacementGroupInfo.SupportedStrategies}, - hypervisor: {filters.Hypervisor, instanceTypeInfo.Hypervisor}, - baremetal: {filters.BareMetal, instanceTypeInfo.BareMetal}, - burstable: {filters.Burstable, instanceTypeInfo.BurstablePerformanceSupported}, - fpga: {filters.Fpga, &isFpga}, - enaSupport: {filters.EnaSupport, supportSyntaxToBool(instanceTypeInfo.NetworkInfo.EnaSupport)}, - efaSupport: {filters.EfaSupport, instanceTypeInfo.NetworkInfo.EfaSupported}, - vcpusToMemoryRatio: {filters.VCpusToMemoryRatio, calculateVCpusToMemoryRatio(instanceTypeInfo.VCpuInfo.DefaultVCpus, instanceTypeInfo.MemoryInfo.SizeInMiB)}, - currentGeneration: {filters.CurrentGeneration, instanceTypeInfo.CurrentGeneration}, - networkInterfaces: {filters.NetworkInterfaces, instanceTypeInfo.NetworkInfo.MaximumNetworkInterfaces}, - networkPerformance: {filters.NetworkPerformance, getNetworkPerformance(instanceTypeInfo.NetworkInfo.NetworkPerformance)}, - networkEncryption: {filters.NetworkEncryption, instanceTypeInfo.NetworkInfo.EncryptionInTransitSupported}, - ipv6: {filters.IPv6, instanceTypeInfo.NetworkInfo.Ipv6Supported}, - instanceTypes: {filters.InstanceTypes, instanceTypeInfo.InstanceType}, - virtualizationType: {filters.VirtualizationType, instanceTypeInfo.SupportedVirtualizationTypes}, - pricePerHour: {filters.PricePerHour, &instanceTypeHourlyPriceForFilter}, - instanceStorageRange: {filters.InstanceStorageRange, getInstanceStorage(instanceTypeInfo.InstanceStorageInfo)}, - diskType: {filters.DiskType, getDiskType(instanceTypeInfo.InstanceStorageInfo)}, - nvme: {filters.NVME, getNVMESupport(instanceTypeInfo.InstanceStorageInfo, instanceTypeInfo.EbsInfo)}, - ebsOptimized: {filters.EBSOptimized, supportSyntaxToBool(instanceTypeInfo.EbsInfo.EbsOptimizedSupport)}, - diskEncryption: {filters.DiskEncryption, getDiskEncryptionSupport(instanceTypeInfo.InstanceStorageInfo, instanceTypeInfo.EbsInfo)}, - ebsOptimizedBaselineBandwidth: {filters.EBSOptimizedBaselineBandwidth, getEBSOptimizedBaselineBandwidth(instanceTypeInfo.EbsInfo)}, - ebsOptimizedBaselineThroughput: {filters.EBSOptimizedBaselineThroughput, getEBSOptimizedBaselineThroughput(instanceTypeInfo.EbsInfo)}, - ebsOptimizedBaselineIOPS: {filters.EBSOptimizedBaselineIOPS, getEBSOptimizedBaselineIOPS(instanceTypeInfo.EbsInfo)}, + } + if filters.PricePerHour != nil { + // If price filter is present, prices should be already fetched + // If prices are not fetched, filter should fail and the corresponding error is already printed + if filters.UsageClass != nil && *filters.UsageClass == "spot" && instanceTypeHourlyPriceSpot != nil { + instanceTypeHourlyPriceForFilter = *instanceTypeHourlyPriceSpot + } else if instanceTypeHourlyPriceOnDemand != nil { + instanceTypeHourlyPriceForFilter = *instanceTypeHourlyPriceOnDemand } + } - if isInDenyList(filters.DenyList, instanceTypeName) || !isInAllowList(filters.AllowList, instanceTypeName) { - continue - } + // filterToInstanceSpecMappingPairs is a map of filter name [key] to filter pair [value]. + // A filter pair includes user input filter value and instance spec value retrieved from DescribeInstanceTypes + filterToInstanceSpecMappingPairs := map[string]filterPair{ + cpuArchitecture: {filters.CPUArchitecture, instanceTypeInfo.ProcessorInfo.SupportedArchitectures}, + cpuManufacturer: {filters.CPUManufacturer, getCPUManufacturer(&instanceTypeInfo.InstanceTypeInfo)}, + usageClass: {filters.UsageClass, instanceTypeInfo.SupportedUsageClasses}, + rootDeviceType: {filters.RootDeviceType, instanceTypeInfo.SupportedRootDeviceTypes}, + hibernationSupported: {filters.HibernationSupported, instanceTypeInfo.HibernationSupported}, + vcpusRange: {filters.VCpusRange, instanceTypeInfo.VCpuInfo.DefaultVCpus}, + memoryRange: {filters.MemoryRange, instanceTypeInfo.MemoryInfo.SizeInMiB}, + gpuMemoryRange: {filters.GpuMemoryRange, getTotalGpuMemory(instanceTypeInfo.GpuInfo)}, + gpusRange: {filters.GpusRange, getTotalGpusCount(instanceTypeInfo.GpuInfo)}, + inferenceAcceleratorsRange: {filters.InferenceAcceleratorsRange, getTotalAcceleratorsCount(instanceTypeInfo.InferenceAcceleratorInfo)}, + placementGroupStrategy: {filters.PlacementGroupStrategy, instanceTypeInfo.PlacementGroupInfo.SupportedStrategies}, + hypervisor: {filters.Hypervisor, instanceTypeInfo.Hypervisor}, + baremetal: {filters.BareMetal, instanceTypeInfo.BareMetal}, + burstable: {filters.Burstable, instanceTypeInfo.BurstablePerformanceSupported}, + fpga: {filters.Fpga, &isFpga}, + enaSupport: {filters.EnaSupport, supportSyntaxToBool(instanceTypeInfo.NetworkInfo.EnaSupport)}, + efaSupport: {filters.EfaSupport, instanceTypeInfo.NetworkInfo.EfaSupported}, + vcpusToMemoryRatio: {filters.VCpusToMemoryRatio, calculateVCpusToMemoryRatio(instanceTypeInfo.VCpuInfo.DefaultVCpus, instanceTypeInfo.MemoryInfo.SizeInMiB)}, + currentGeneration: {filters.CurrentGeneration, instanceTypeInfo.CurrentGeneration}, + networkInterfaces: {filters.NetworkInterfaces, instanceTypeInfo.NetworkInfo.MaximumNetworkInterfaces}, + networkPerformance: {filters.NetworkPerformance, getNetworkPerformance(instanceTypeInfo.NetworkInfo.NetworkPerformance)}, + networkEncryption: {filters.NetworkEncryption, instanceTypeInfo.NetworkInfo.EncryptionInTransitSupported}, + ipv6: {filters.IPv6, instanceTypeInfo.NetworkInfo.Ipv6Supported}, + instanceTypes: {filters.InstanceTypes, instanceTypeInfo.InstanceType}, + virtualizationType: {filters.VirtualizationType, instanceTypeInfo.SupportedVirtualizationTypes}, + pricePerHour: {filters.PricePerHour, &instanceTypeHourlyPriceForFilter}, + instanceStorageRange: {filters.InstanceStorageRange, getInstanceStorage(instanceTypeInfo.InstanceStorageInfo)}, + diskType: {filters.DiskType, getDiskType(instanceTypeInfo.InstanceStorageInfo)}, + nvme: {filters.NVME, getNVMESupport(instanceTypeInfo.InstanceStorageInfo, instanceTypeInfo.EbsInfo)}, + ebsOptimized: {filters.EBSOptimized, supportSyntaxToBool(instanceTypeInfo.EbsInfo.EbsOptimizedSupport)}, + diskEncryption: {filters.DiskEncryption, getDiskEncryptionSupport(instanceTypeInfo.InstanceStorageInfo, instanceTypeInfo.EbsInfo)}, + ebsOptimizedBaselineBandwidth: {filters.EBSOptimizedBaselineBandwidth, getEBSOptimizedBaselineBandwidth(instanceTypeInfo.EbsInfo)}, + ebsOptimizedBaselineThroughput: {filters.EBSOptimizedBaselineThroughput, getEBSOptimizedBaselineThroughput(instanceTypeInfo.EbsInfo)}, + ebsOptimizedBaselineIOPS: {filters.EBSOptimizedBaselineIOPS, getEBSOptimizedBaselineIOPS(instanceTypeInfo.EbsInfo)}, + freeTier: {filters.FreeTier, instanceTypeInfo.FreeTierEligible}, + autoRecovery: {filters.AutoRecovery, instanceTypeInfo.AutoRecoverySupported}, + gpuManufacturer: {filters.GPUManufacturer, getGPUManufacturers(instanceTypeInfo.GpuInfo)}, + gpuModel: {filters.GPUModel, getGPUModels(instanceTypeInfo.GpuInfo)}, + inferenceAcceleratorManufacturer: {filters.InferenceAcceleratorManufacturer, getInferenceAcceleratorManufacturers(instanceTypeInfo.InferenceAcceleratorInfo)}, + inferenceAcceleratorModel: {filters.InferenceAcceleratorModel, getInferenceAcceleratorModels(instanceTypeInfo.InferenceAcceleratorInfo)}, + dedicatedHosts: {filters.DedicatedHosts, instanceTypeInfo.DedicatedHostsSupported}, + } - if !isSupportedInLocation(locationInstanceOfferings, instanceTypeName) { - continue - } + if isInDenyList(filters.DenyList, instanceTypeName) || !isInAllowList(filters.AllowList, instanceTypeName) { + return nil, nil + } - var isInstanceSupported bool - isInstanceSupported, err = itf.executeFilters(filterToInstanceSpecMappingPairs, instanceTypeName) - if err != nil { - return nil, err - } - if !isInstanceSupported { - continue - } - itInfo := instanceTypeInfo - filteredInstanceTypes = append(filteredInstanceTypes, itInfo) + if !isSupportedInLocation(locationInstanceOfferings, instanceTypeName) { + return nil, nil } - return sortInstanceTypeInfo(filteredInstanceTypes), nil + var isInstanceSupported bool + isInstanceSupported, err := itf.executeFilters(filterToInstanceSpecMappingPairs, instanceTypeName) + if err != nil { + return nil, err + } + if !isInstanceSupported { + return nil, nil + } + return &instanceTypeInfo, nil } // sortInstanceTypeInfo will sort based on instance type info alpha-numerically func sortInstanceTypeInfo(instanceTypeInfoSlice []*instancetypes.Details) []*instancetypes.Details { + if len(instanceTypeInfoSlice) < 2 { + return instanceTypeInfoSlice + } sort.Slice(instanceTypeInfoSlice, func(i, j int) bool { iInstanceInfo := instanceTypeInfoSlice[i] jInstanceInfo := instanceTypeInfoSlice[j] @@ -319,122 +358,166 @@ func sortInstanceTypeInfo(instanceTypeInfoSlice []*instancetypes.Details) []*ins // executeFilters accepts a mapping of filter name to filter pairs which are iterated through // to determine if the instance type matches the filter values. func (itf Selector) executeFilters(filterToInstanceSpecMapping map[string]filterPair, instanceType string) (bool, error) { - for filterName, filterPair := range filterToInstanceSpecMapping { - filterVal := filterPair.filterValue - instanceSpec := filterPair.instanceSpec - // if filter is nil, user did not specify a filter, so skip evaluation - if reflect.ValueOf(filterVal).IsNil() { - continue - } - instanceSpecType := reflect.ValueOf(instanceSpec).Type() - filterType := reflect.ValueOf(filterVal).Type() - filterDetailsMsg := fmt.Sprintf("filter (%s: %s => %s) corresponding to instance spec (%s => %s) for instance type %s", filterName, filterVal, filterType, instanceSpec, instanceSpecType, instanceType) - invalidInstanceSpecTypeMsg := fmt.Sprintf("Unable to process for %s", filterDetailsMsg) - - // Determine appropriate filter comparator by switching on filter type - switch filter := filterVal.(type) { - case *string: - switch iSpec := instanceSpec.(type) { - case []*string: - if !isSupportedFromStrings(iSpec, filter) { - return false, nil + abort := make(chan bool, len(filterToInstanceSpecMapping)) + errs := make(chan error) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + var wg sync.WaitGroup + for filterName, filter := range filterToInstanceSpecMapping { + wg.Add(1) + go func(ctx context.Context, filterName string, filter filterPair) { + defer wg.Done() + select { + case <-ctx.Done(): + return + default: + ok, err := exec(instanceType, filterName, filter) + if err != nil { + errs <- err } - case *string: - if !isSupportedFromString(iSpec, filter) { - return false, nil + if !ok { + abort <- true } + } + }(ctx, filterName, filter) + } + done := make(chan bool) + go func() { + wg.Wait() + done <- true + }() + select { + case <-abort: + cancel() + var err error + for { + select { + case e := <-errs: + err = multierr.Append(err, e) default: - return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + return false, err } + } + case <-done: + return true, nil + } +} + +func exec(instanceType string, filterName string, filter filterPair) (bool, error) { + filterVal := filter.filterValue + instanceSpec := filter.instanceSpec + // if filter is nil, user did not specify a filter, so skip evaluation + if reflect.ValueOf(filterVal).IsNil() { + return true, nil + } + instanceSpecType := reflect.ValueOf(instanceSpec).Type() + filterType := reflect.ValueOf(filterVal).Type() + filterDetailsMsg := fmt.Sprintf("filter (%s: %s => %s) corresponding to instance spec (%s => %s) for instance type %s", filterName, filterVal, filterType, instanceSpec, instanceSpecType, instanceType) + invalidInstanceSpecTypeMsg := fmt.Sprintf("Unable to process for %s", filterDetailsMsg) + + // Determine appropriate filter comparator by switching on filter type + switch filter := filterVal.(type) { + case *string: + switch iSpec := instanceSpec.(type) { + case []*string: + if !isSupportedFromStrings(iSpec, filter) { + return false, nil + } + case *string: + if !isSupportedFromString(iSpec, filter) { + return false, nil + } + default: + return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + } + case *bool: + switch iSpec := instanceSpec.(type) { case *bool: - switch iSpec := instanceSpec.(type) { - case *bool: - if !isSupportedWithBool(iSpec, filter) { - return false, nil - } - default: - return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + if !isSupportedWithBool(iSpec, filter) { + return false, nil } - case *IntRangeFilter: - switch iSpec := instanceSpec.(type) { - case *int64: - if !isSupportedWithRangeInt64(iSpec, filter) { - return false, nil - } - case *int: - if !isSupportedWithRangeInt(iSpec, filter) { - return false, nil - } - default: - return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + default: + return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + } + case *IntRangeFilter: + switch iSpec := instanceSpec.(type) { + case *int64: + if !isSupportedWithRangeInt64(iSpec, filter) { + return false, nil } - case *Float64RangeFilter: - switch iSpec := instanceSpec.(type) { - case *float64: - if !isSupportedWithRangeFloat64(iSpec, filter) { - return false, nil - } - default: - return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + case *int: + if !isSupportedWithRangeInt(iSpec, filter) { + return false, nil } - case *ByteQuantityRangeFilter: - mibRange := Uint64RangeFilter{ - LowerBound: filter.LowerBound.Quantity, - UpperBound: filter.UpperBound.Quantity, + default: + return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + } + case *Float64RangeFilter: + switch iSpec := instanceSpec.(type) { + case *float64: + if !isSupportedWithRangeFloat64(iSpec, filter) { + return false, nil } - switch iSpec := instanceSpec.(type) { - case *int: - var iSpec64 *int64 - if iSpec != nil { - iSpecVal := int64(*iSpec) - iSpec64 = &iSpecVal - } - if !isSupportedWithRangeUint64(iSpec64, &mibRange) { - return false, nil - } - case *int64: - if !isSupportedWithRangeUint64(iSpec, &mibRange) { - return false, nil - } - case *float64: - floatMiBRange := Float64RangeFilter{ - LowerBound: float64(filter.LowerBound.Quantity), - UpperBound: float64(filter.UpperBound.Quantity), - } - if !isSupportedWithRangeFloat64(iSpec, &floatMiBRange) { - return false, nil - } - default: - return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + default: + return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + } + case *ByteQuantityRangeFilter: + mibRange := Uint64RangeFilter{ + LowerBound: filter.LowerBound.Quantity, + UpperBound: filter.UpperBound.Quantity, + } + switch iSpec := instanceSpec.(type) { + case *int: + var iSpec64 *int64 + if iSpec != nil { + iSpecVal := int64(*iSpec) + iSpec64 = &iSpecVal + } + if !isSupportedWithRangeUint64(iSpec64, &mibRange) { + return false, nil + } + case *int64: + if !isSupportedWithRangeUint64(iSpec, &mibRange) { + return false, nil } case *float64: - switch iSpec := instanceSpec.(type) { - case *float64: - if !isSupportedWithFloat64(iSpec, filter) { - return false, nil - } - default: - return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + floatMiBRange := Float64RangeFilter{ + LowerBound: float64(filter.LowerBound.Quantity), + UpperBound: float64(filter.UpperBound.Quantity), } - case *[]string: - switch iSpec := instanceSpec.(type) { - case *string: - filterOfPtrs := []*string{} - for _, f := range *filter { - // this allows us to copy a static pointer to f into filterOfPtrs - // since the pointer to f is updated on each loop iteration - temp := f - filterOfPtrs = append(filterOfPtrs, &temp) - } - if !isSupportedFromStrings(filterOfPtrs, iSpec) { - return false, nil - } - default: - return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + if !isSupportedWithRangeFloat64(iSpec, &floatMiBRange) { + return false, nil + } + default: + return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + } + case *float64: + switch iSpec := instanceSpec.(type) { + case *float64: + if !isSupportedWithFloat64(iSpec, filter) { + return false, nil + } + default: + return false, fmt.Errorf(invalidInstanceSpecTypeMsg) + } + case *[]string: + switch iSpec := instanceSpec.(type) { + case *string: + filterOfPtrs := []*string{} + for _, f := range *filter { + // this allows us to copy a static pointer to f into filterOfPtrs + // since the pointer to f is updated on each loop iteration + temp := f + filterOfPtrs = append(filterOfPtrs, &temp) + } + if !isSupportedFromStrings(filterOfPtrs, iSpec) { + return false, nil } default: - return false, fmt.Errorf("No filter handler found for %s", filterDetailsMsg) + return false, fmt.Errorf(invalidInstanceSpecTypeMsg) } + default: + return false, fmt.Errorf("No filter handler found for %s", filterDetailsMsg) } return true, nil } diff --git a/pkg/selector/selector_test.go b/pkg/selector/selector_test.go index d5049a8..a270938 100644 --- a/pkg/selector/selector_test.go +++ b/pkg/selector/selector_test.go @@ -293,7 +293,7 @@ func TestFilter_TruncateToMaxResults(t *testing.T) { } results, err = itf.Filter(filters) h.Ok(t, err) - h.Assert(t, len(results) == 25, "Should return 25 instance types since max results is set to 30 but only 25 are returned in total") + h.Assert(t, len(results) == 25, fmt.Sprintf("Should return 25 instance types since max results is set to 30 but only %d are returned in total", len(results))) } func TestFilter_Failure(t *testing.T) { diff --git a/pkg/selector/types.go b/pkg/selector/types.go index ab77dc3..95a634d 100644 --- a/pkg/selector/types.go +++ b/pkg/selector/types.go @@ -115,10 +115,19 @@ type Filters struct { // Burstable is used to only return burstable instance type results like the t* series Burstable *bool + // AutoRecovery is used to filter by instance types that support auto recovery + AutoRecovery *bool + + // FreeTier is used to filter by instance types that can be used as part of the EC2 free tier + FreeTier *bool + // CPUArchitecture of the EC2 instance type // Possible values are: x86_64/amd64 or arm64 CPUArchitecture *string + // CPUManufacturer is used to filter instance types with a specific CPU manufacturer + CPUManufacturer *string + // CurrentGeneration returns the latest generation of instance types CurrentGeneration *bool @@ -137,9 +146,21 @@ type Filters struct { // GpuMemoryRange filter is a range of acceptable GPU memory in Gibibytes (GiB) available to an EC2 instance type in aggreagte across all GPUs. GpuMemoryRange *ByteQuantityRangeFilter - // InferenceAcceleratorsRange filters inference acclerators available to the instance type + // GPUManufacturer filters by GPU manufacturer + GPUManufacturer *string + + // GPUModel filter by the GPU model name + GPUModel *string + + // InferenceAcceleratorsRange filters inference accelerators available to the instance type InferenceAcceleratorsRange *IntRangeFilter + // InferenceAcceleratorManufacturer filters by inference acceleartor manufacturer + InferenceAcceleratorManufacturer *string + + // InferenceAcceleratorModel filters by inference accelerator model name + InferenceAcceleratorModel *string + // HibernationSupported denotes whether EC2 hibernate is supported // Possible values are: true or false HibernationSupported *bool @@ -240,4 +261,7 @@ type Filters struct { // EBSOptimizedBaselineIOPS filters on a range of IOPS that an EBS Optimized volume supports EBSOptimizedBaselineIOPS *IntRangeFilter + + // DedicatedHosts filters on instance types that support dedicated hosts tenancy + DedicatedHosts *bool } diff --git a/test/license-test/license-config.hcl b/test/license-test/license-config.hcl index 1f9adaf..1618ffc 100644 --- a/test/license-test/license-config.hcl +++ b/test/license-test/license-config.hcl @@ -17,4 +17,5 @@ override = { "github.com/gogo/protobuf" = "BSD-3-Clause" "github.com/russross/blackfriday" = "BSD-2-Clause" "github.com/ghodss/yaml" = "MIT" + "github.com/aws/amazon-ec2-instance-selector" = "Apache-2.0" }