diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..19896109
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "gomod"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 2eb66ccb..873dfbac 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.18
+ DEFAULT_GO_VERSION: ^1.23
GITHUB_USERNAME: ${{ secrets.EC2_BOT_GITHUB_USERNAME }}
GITHUB_TOKEN: ${{ secrets.EC2_BOT_GITHUB_TOKEN }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -22,7 +22,7 @@ jobs:
go-version: ${{ env.DEFAULT_GO_VERSION }}
- name: Check out code into the Go module directory
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Unit Tests
run: make unit-test
@@ -43,7 +43,7 @@ jobs:
run: make build-docker-images
- name: Integration Tests
- if: github.event_name == 'push'
+ if: ${{ github.event_name == 'push' && !contains(github.ref, 'dependabot') }}
run: make integ-test
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
diff --git a/Dockerfile b/Dockerfile
index f39964fe..9195e802 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.18 as builder
+FROM golang:1.23 as builder
## GOLANG env
ARG GOPROXY="https://proxy.golang.org|direct"
diff --git a/Makefile b/Makefile
index 6dd1b60d..6af8ca16 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ GOPROXY ?= "https://proxy.golang.org,direct/"
MAKEFILE_PATH = $(dir $(realpath -s $(firstword $(MAKEFILE_LIST))))
BUILD_DIR_PATH = ${MAKEFILE_PATH}/build
SUPPORTED_PLATFORMS ?= "windows/amd64,darwin/amd64,darwin/arm64,linux/amd64,linux/arm64,linux/arm"
-SELECTOR_PKG_VERSION_VAR=github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector.versionID
+SELECTOR_PKG_VERSION_VAR=github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector.versionID
LATEST_RELEASE_TAG=$(shell git describe --tags --abbrev=0)
PREVIOUS_RELEASE_TAG=$(shell git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`)
@@ -85,7 +85,7 @@ sync-readme-to-dockerhub:
${MAKEFILE_PATH}/scripts/sync-readme-to-dockerhub
unit-test:
- go test -bench=. ${MAKEFILE_PATH}/... -v -coverprofile=coverage.out -covermode=atomic -outputdir=${BUILD_DIR_PATH}
+ go test -bench=. ./... -v -coverprofile=coverage.out -covermode=atomic -outputdir=${BUILD_DIR_PATH}
## requires aws credentials
e2e-test: build
diff --git a/README.md b/README.md
index cd9574bc..3f7db4df 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
A CLI tool and go library which recommends instance types based on resource criteria like vcpus and memory.
-
+
@@ -25,7 +25,7 @@
## Summary
-There are over 270 different instance types available on EC2 which can make the process of selecting appropriate instance types difficult. Instance Selector helps you select compatible instance types for your application to run on. The command line interface can be passed resource criteria like vcpus, memory, network performance, and much more and then return the available, matching instance types.
+There are over 800 different instance types available on EC2 which can make the process of selecting appropriate instance types difficult. Instance Selector helps you select compatible instance types for your application to run on. The command line interface can be passed resource criteria like vcpus, memory, network performance, and much more and then return the available, matching instance types.
If you are using spot instances to save on costs, it is a best practice to use multiple instances types within your auto-scaling group (ASG) to ensure your application doesn't experience downtime due to one instance type being interrupted. Instance Selector will help to find a set of instance types that your application can run on.
@@ -35,7 +35,8 @@ Instance Selector can also be consumed as a go library for direct integration in
- Filter AWS Instance Types using declarative resource criteria like vcpus, memory, network performance, and much more!
- Aggregate filters allow for more opinionated instance selections like `--base-instance-type` and `--flexible`
-- Consumable as a go library
+- Consumable as a go library or CLI
+- Interactive TUI w/ `--output interactive`
## Installation and Configuration
@@ -49,7 +50,9 @@ brew install ec2-instance-selector
#### Install w/ Curl for Linux/Mac
```
-curl -Lo ec2-instance-selector https://github.com/aws/amazon-ec2-instance-selector/releases/download/v2.4.0/ec2-instance-selector-`uname | tr '[:upper:]' '[:lower:]'`-amd64 && chmod +x ec2-instance-selector
+curl -Lo ec2-instance-selector https://github.com/aws/amazon-ec2-instance-selector/releases/download/v2.4.1/ec2-instance-selector-`uname | tr '[:upper:]' '[:lower:]'`-amd64 && chmod +x ec2-instance-selector
+sudo mv ec2-instance-selector /usr/local/bin/
+ec2-instance-selector --version
```
To execute the CLI, you will need AWS credentials configured. Take a look at the [AWS CLI configuration documentation](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#config-settings-and-precedence) for details on the various ways to configure credentials. An easy way to try out the ec2-instance-selector CLI is to populate the following environment variables with your AWS API credentials.
@@ -84,6 +87,11 @@ c5ad.large
c5d.large
c6a.large
c6i.large
+c6id.large
+c6in.large
+c7a.large
+c7i-flex.large
+c7i.large
t2.medium
t3.medium
t3a.medium
@@ -95,56 +103,66 @@ $ ec2-instance-selector --network-performance 100 --usage-class spot -r us-east-
c5n.18xlarge
c5n.metal
c6gn.16xlarge
+c6in.16xlarge
+c7gn.8xlarge
dl1.24xlarge
g4dn.metal
g5.48xlarge
+g6.48xlarge
+g6e.12xlarge
i3en.24xlarge
i3en.metal
im4gn.16xlarge
inf1.24xlarge
+inf2.48xlarge
m5dn.24xlarge
m5dn.metal
m5n.24xlarge
m5n.metal
m5zn.12xlarge
-m5zn.metal
-p3dn.24xlarge
-p4d.24xlarge
-r5dn.24xlarge
-r5dn.metal
+NOTE: 19 entries were truncated, increase --max-results to see more
```
**Short Table Output**
```
$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r us-east-1 -o table
-Instance Type VCPUs Mem (GiB)
-------------- ----- ---------
-c5.large 2 4
-c5a.large 2 4
-c5ad.large 2 4
-c5d.large 2 4
-c6a.large 2 4
-c6i.large 2 4
-t2.medium 2 4
-t3.medium 2 4
-t3a.medium 2 4
+Instance Type VCPUs Mem (GiB)
+------------- ----- ---------
+c5.large 2 4
+c5a.large 2 4
+c5ad.large 2 4
+c5d.large 2 4
+c6a.large 2 4
+c6i.large 2 4
+c6id.large 2 4
+c6in.large 2 4
+c7a.large 2 4
+c7i-flex.large 2 4
+c7i.large 2 4
+t2.medium 2 4
+t3.medium 2 4
+t3a.medium 2 4
```
**Wide Table Output**
```
$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r us-east-1 -o table-wide
-Instance Type VCPUs Mem (GiB) Hypervisor Current Gen Hibernation Support CPU Arch Network Performance ENIs GPUs GPU Mem (GiB) GPU Info On-Demand Price/Hr Spot Price/Hr (30d avg)
-------------- ----- --------- ---------- ----------- ------------------- -------- ------------------- ---- ---- ------------- -------- ------------------ -----------------------
-c5.large 2 4 nitro true true x86_64 Up to 10 Gigabit 3 0 0 none -Not Fetched- $0.03932
-c5a.large 2 4 nitro true false x86_64 Up to 10 Gigabit 3 0 0 none -Not Fetched- $0.03822
-c5ad.large 2 4 nitro true false x86_64 Up to 10 Gigabit 3 0 0 none -Not Fetched- $0.03449
-c5d.large 2 4 nitro true true x86_64 Up to 10 Gigabit 3 0 0 none $0.096 $0.03983
-c6a.large 2 4 nitro true false x86_64 Up to 12.5 Gigabit 3 0 0 none $0.0765 $0.034
-c6i.large 2 4 nitro true false x86_64 Up to 12.5 Gigabit 3 0 0 none $0.085 $0.03605
-c6id.large 2 4 nitro true false x86_64 Up to 12.5 Gigabit 3 0 0 none -Not Fetched- $0.034
-t2.medium 2 4 xen true true i386, x86_64 Low to Moderate 3 0 0 none $0.0464 $0.0139
-t3.medium 2 4 nitro true true x86_64 Up to 5 Gigabit 3 0 0 none $0.0416 $0.0125
-t3a.medium 2 4 nitro true true x86_64 Up to 5 Gigabit 3 0 0 none -Not Fetched- $0.01246
+Instance Type VCPUs Mem (GiB) Hypervisor Current Gen Hibernation Support CPU Arch Network Performance ENIs GPUs GPU Mem (GiB) GPU Info On-Demand Price/Hr Spot Price/Hr
+------------- ----- --------- ---------- ----------- ------------------- -------- ------------------- ---- ---- ------------- -------- ------------------ -------------
+c5.large 2 4 nitro true true x86_64 Up to 10 Gigabit 3 0 0 none $0.085 $0.0405
+c5a.large 2 4 nitro true false x86_64 Up to 10 Gigabit 3 0 0 none $0.077 $0.0308
+c5ad.large 2 4 nitro true false x86_64 Up to 10 Gigabit 3 0 0 none $0.086 $0.0415
+c5d.large 2 4 nitro true true x86_64 Up to 10 Gigabit 3 0 0 none $0.096 $0.0281
+c6a.large 2 4 nitro true false x86_64 Up to 12.5 Gigabit 3 0 0 none $0.0765 $0.0285
+c6i.large 2 4 nitro true true x86_64 Up to 12.5 Gigabit 3 0 0 none $0.085 $0.0292
+c6id.large 2 4 nitro true true x86_64 Up to 12.5 Gigabit 3 0 0 none $0.1008 $0.0391
+c6in.large 2 4 nitro true false x86_64 Up to 25 Gigabit 3 0 0 none $0.1134 $0.0403
+c7a.large 2 4 nitro true true x86_64 Up to 12.5 Gigabit 3 0 0 none $0.10264 $0.0457
+c7i-flex.large 2 4 nitro true true x86_64 Up to 12.5 Gigabit 3 0 0 none $0.08479 $0.022
+c7i.large 2 4 nitro true true x86_64 Up to 12.5 Gigabit 3 0 0 none $0.08925 $0.0359
+t2.medium 2 4 xen true true i386, x86_64 Low to Moderate 3 0 0 none $0.0464 $0.0156
+t3.medium 2 4 nitro true true x86_64 Up to 5 Gigabit 3 0 0 none $0.0416 $0.015
+t3a.medium 2 4 nitro true true x86_64 Up to 5 Gigabit 3 0 0 none $0.0376 $0.0106
```
**Interactive Output**
@@ -156,44 +174,99 @@ https://user-images.githubusercontent.com/68402662/184218343-6b236d4a-3fe6-42ae-
**Sort by memory in ascending order using shorthand**
```
$ ec2-instance-selector -r us-east-1 -o table-wide --max-results 10 --sort-by memory --sort-direction asc
-Instance Type VCPUs Mem (GiB) Hypervisor Current Gen Hibernation Support CPU Arch Network Performance ENIs GPUs GPU Mem (GiB) GPU Info On-Demand Price/Hr Spot Price/Hr (30d avg)
-------------- ----- --------- ---------- ----------- ------------------- -------- ------------------- ---- ---- ------------- -------- ------------------ -----------------------
-t2.nano 1 0.5 xen true true i386, x86_64 Low to Moderate 2 0 0 none $0.0058 -Not Fetched-
-t4g.nano 2 0.5 nitro true false arm64 Up to 5 Gigabit 2 0 0 none $0.0042 $0.0013
-t3a.nano 2 0.5 nitro true true x86_64 Up to 5 Gigabit 2 0 0 none -Not Fetched- $0.00328
-t3.nano 2 0.5 nitro true true x86_64 Up to 5 Gigabit 2 0 0 none $0.0052 $0.0016
-t1.micro 1 0.6123 xen false false i386, x86_64 Very Low 2 0 0 none -Not Fetched- $0.00205
-t3a.micro 2 1 nitro true true x86_64 Up to 5 Gigabit 2 0 0 none -Not Fetched- $0.00284
-t3.micro 2 1 nitro true true x86_64 Up to 5 Gigabit 2 0 0 none $0.0104 $0.0031
-t2.micro 1 1 xen true true i386, x86_64 Low to Moderate 2 0 0 none -Not Fetched- $0.0035
-t4g.micro 2 1 nitro true false arm64 Up to 5 Gigabit 2 0 0 none -Not Fetched- $0.0025
-m1.small 1 1.69922 xen false false i386, x86_64 Low 2 0 0 none -Not Fetched- $0.01876
-NOTE: 547 entries were truncated, increase --max-results to see more
+Instance Type VCPUs Mem (GiB) Hypervisor Current Gen Hibernation Support CPU Arch Network Performance ENIs GPUs GPU Mem (GiB) GPU Info On-Demand Price/Hr Spot Price/Hr
+------------- ----- --------- ---------- ----------- ------------------- -------- ------------------- ---- ---- ------------- -------- ------------------ -------------
+t3a.nano 2 0.5 nitro true true x86_64 Up to 5 Gigabit 2 0 0 none $0.0047 $0.0018
+t2.nano 1 0.5 xen true true i386, x86_64 Low to Moderate 2 0 0 none $0.0058 -Not Fetched-
+t4g.nano 2 0.5 nitro true true arm64 Up to 5 Gigabit 2 0 0 none $0.0042 $0.0018
+t3.nano 2 0.5 nitro true true x86_64 Up to 5 Gigabit 2 0 0 none $0.0052 $0.0006
+t1.micro 1 0.6123 xen false false i386, x86_64 Very Low 2 0 0 none $0.02 $0.0021
+t3.micro 2 1 nitro true true x86_64 Up to 5 Gigabit 2 0 0 none $0.0104 $0.0029
+t2.micro 1 1 xen true true i386, x86_64 Low to Moderate 2 0 0 none $0.0116 $0.0016
+t4g.micro 2 1 nitro true true arm64 Up to 5 Gigabit 2 0 0 none $0.0084 $0.0024
+t3a.micro 2 1 nitro true true x86_64 Up to 5 Gigabit 2 0 0 none $0.0094 $0.0031
+m1.small 1 1.69922 xen false false i386, x86_64 Low 2 0 0 none $0.044 $0.0048
+NOTE: 832 entries were truncated, increase --max-results to see more
```
Available shorthand flags: vcpus, memory, gpu-memory-total, network-interfaces, spot-price, on-demand-price, instance-storage, ebs-optimized-baseline-bandwidth, ebs-optimized-baseline-throughput, ebs-optimized-baseline-iops, gpus, inference-accelerators
**Sort by memory in descending order using JSON path**
```
$ ec2-instance-selector -r us-east-1 -o table-wide --max-results 10 --sort-by .MemoryInfo.SizeInMiB --sort-direction desc
-Instance Type VCPUs Mem (GiB) Hypervisor Current Gen Hibernation Support CPU Arch Network Performance ENIs GPUs GPU Mem (GiB) GPU Info On-Demand Price/Hr Spot Price/Hr (30d avg)
-------------- ----- --------- ---------- ----------- ------------------- -------- ------------------- ---- ---- ------------- -------- ------------------ -----------------------
-u-12tb1.112xlarge 448 12,288 nitro true false x86_64 100 Gigabit 15 0 0 none $109.2 -Not Fetched-
-u-9tb1.112xlarge 448 9,216 nitro true false x86_64 100 Gigabit 15 0 0 none -Not Fetched- -Not Fetched-
-u-6tb1.112xlarge 448 6,144 nitro true false x86_64 100 Gigabit 15 0 0 none $54.6 -Not Fetched-
-u-6tb1.56xlarge 224 6,144 nitro true false x86_64 100 Gigabit 15 0 0 none $46.40391 -Not Fetched-
-x2iedn.metal 128 4,096 none true false x86_64 100 Gigabit 15 0 0 none $26.676 $20.92296
-x2iedn.32xlarge 128 4,096 nitro true false x86_64 100 Gigabit 15 0 0 none $26.676 $8.70294
-x1e.32xlarge 128 3,904 xen true false x86_64 25 Gigabit 8 0 0 none $26.688 $8.0064
-x2iedn.24xlarge 96 3,072 nitro true false x86_64 75 Gigabit 15 0 0 none $20.007 $6.0021
-u-3tb1.56xlarge 224 3,072 nitro true false x86_64 50 Gigabit 8 0 0 none $27.3 -Not Fetched-
-x2idn.metal 128 2,048 none true false x86_64 100 Gigabit 15 0 0 none $13.338 $7.46603
-NOTE: 547 entries were truncated, increase --max-results to see more
+Instance Type VCPUs Mem (GiB) Hypervisor Current Gen Hibernation Support CPU Arch Network Performance ENIs GPUs GPU Mem (GiB) GPU Info On-Demand Price/Hr Spot Price/Hr
+------------- ----- --------- ---------- ----------- ------------------- -------- ------------------- ---- ---- ------------- -------- ------------------ -------------
+u7in-32tb.224xlarge 896 32,768 nitro true false x86_64 200 Gigabit 16 0 0 none $407.68 -Not Fetched-
+u7in-24tb.224xlarge 896 24,576 nitro true false x86_64 200 Gigabit 16 0 0 none $305.76 -Not Fetched-
+u-24tb1.112xlarge 448 24,576 nitro true false x86_64 100 Gigabit 15 0 0 none $218.4 -Not Fetched-
+u-18tb1.112xlarge 448 18,432 nitro true false x86_64 100 Gigabit 15 0 0 none $163.8 -Not Fetched-
+u7in-16tb.224xlarge 896 16,384 nitro true false x86_64 200 Gigabit 16 0 0 none $203.84 -Not Fetched-
+u7i-12tb.224xlarge 896 12,288 nitro true false x86_64 100 Gigabit 15 0 0 none $152.88 -Not Fetched-
+u-12tb1.112xlarge 448 12,288 nitro true false x86_64 100 Gigabit 15 0 0 none $109.2 -Not Fetched-
+u-9tb1.112xlarge 448 9,216 nitro true false x86_64 100 Gigabit 15 0 0 none $81.9 -Not Fetched-
+u-6tb1.56xlarge 224 6,144 nitro true false x86_64 100 Gigabit 15 0 0 none $46.40391 -Not Fetched-
+u-6tb1.112xlarge 448 6,144 nitro true false x86_64 100 Gigabit 15 0 0 none $54.6 -Not Fetched-
+NOTE: 832 entries were truncated, increase --max-results to see more
```
JSON path must point to a field in the [instancetype.Details struct](https://github.com/aws/amazon-ec2-instance-selector/blob/5bffbf2750ee09f5f1308bdc8d4b635a2c6e2721/pkg/instancetypes/instancetypes.go#L37).
**Example output of instance type object using Verbose output**
```
$ ec2-instance-selector --max-results 1 -v
+NOTE:
+
+"Filters": {
+ "AllowList": null,
+ "DenyList": null,
+ "AvailabilityZones": [],
+ "BareMetal": null,
+ "Burstable": null,
+ "AutoRecovery": null,
+ "FreeTier": null,
+ "CPUArchitecture": null,
+ "CPUManufacturer": null,
+ "CurrentGeneration": null,
+ "EnaSupport": null,
+ "EfaSupport": null,
+ "Fpga": null,
+ "GpusRange": null,
+ "GpuMemoryRange": null,
+ "GPUManufacturer": null,
+ "GPUModel": null,
+ "InferenceAcceleratorsRange": null,
+ "InferenceAcceleratorManufacturer": null,
+ "InferenceAcceleratorModel": null,
+ "HibernationSupported": null,
+ "Hypervisor": null,
+ "MaxResults": 1,
+ "MemoryRange": null,
+ "NetworkInterfaces": null,
+ "NetworkPerformance": null,
+ "NetworkEncryption": null,
+ "IPv6": null,
+ "PlacementGroupStrategy": null,
+ "Region": "us-east-1",
+ "RootDeviceType": null,
+ "UsageClass": null,
+ "VCpusRange": null,
+ "VCpusToMemoryRatio": null,
+ "InstanceTypeBase": null,
+ "Flexible": null,
+ "Service": null,
+ "InstanceTypes": null,
+ "VirtualizationType": null,
+ "PricePerHour": null,
+ "InstanceStorageRange": null,
+ "DiskType": null,
+ "NVME": null,
+ "EBSOptimized": null,
+ "DiskEncryption": null,
+ "EBSOptimizedBaselineBandwidth": null,
+ "EBSOptimizedBaselineThroughput": null,
+ "EBSOptimizedBaselineIOPS": null,
+ "DedicatedHosts": null,
+ "Generation": null
+}
+NOTE: There were no transformations on the filters to display
[
{
"AutoRecoverySupported": true,
@@ -223,6 +296,7 @@ $ ec2-instance-selector --max-results 1 -v
"InstanceStorageInfo": null,
"InstanceStorageSupported": false,
"InstanceType": "a1.2xlarge",
+ "MediaAcceleratorInfo": null,
"MemoryInfo": {
"SizeInMiB": 16384
},
@@ -230,6 +304,7 @@ $ ec2-instance-selector --max-results 1 -v
"DefaultNetworkCardIndex": 0,
"EfaInfo": null,
"EfaSupported": false,
+ "EnaSrdSupported": false,
"EnaSupport": "required",
"EncryptionInTransitSupported": false,
"Ipv4AddressesPerInterface": 15,
@@ -239,13 +314,20 @@ $ ec2-instance-selector --max-results 1 -v
"MaximumNetworkInterfaces": 4,
"NetworkCards": [
{
+ "BaselineBandwidthInGbps": 2.5,
"MaximumNetworkInterfaces": 4,
"NetworkCardIndex": 0,
- "NetworkPerformance": "Up to 10 Gigabit"
+ "NetworkPerformance": "Up to 10 Gigabit",
+ "PeakBandwidthInGbps": 10
}
],
"NetworkPerformance": "Up to 10 Gigabit"
},
+ "NeuronInfo": null,
+ "NitroEnclavesSupport": "unsupported",
+ "NitroTpmInfo": null,
+ "NitroTpmSupport": "unsupported",
+ "PhcSupport": "unsupported",
"PlacementGroupInfo": {
"SupportedStrategies": [
"cluster",
@@ -254,9 +336,11 @@ $ ec2-instance-selector --max-results 1 -v
]
},
"ProcessorInfo": {
+ "Manufacturer": "AWS",
"SupportedArchitectures": [
"arm64"
],
+ "SupportedFeatures": null,
"SustainedClockSpeedInGhz": 2.3
},
"SupportedBootModes": [
@@ -279,11 +363,11 @@ $ ec2-instance-selector --max-results 1 -v
"ValidCores": null,
"ValidThreadsPerCore": null
},
- "OndemandPricePerHour": 0.204,
- "SpotPrice": 0.03939999999999999
+ "OndemandPricePerHour": null,
+ "SpotPrice": null
}
]
-NOTE: 497 entries were truncated, increase --max-results to see more
+NOTE: 841 entries were truncated, increase --max-results to see more
```
NOTE: Use this JSON format as reference when finding JSON paths for sorting
@@ -294,7 +378,7 @@ $ ec2-instance-selector --help
```
```bash#help
-ec2-instance-selector is a CLI tool to filter EC2 instance types based on resource criteria.
+ec2-instance-selector is a CLI tool to filter EC2 instance types based on resource criteria.
Filtering allows you to select all the instance types that match your application requirements.
Full docs can be found at github.com/aws/amazon-ec2-instance-selector
@@ -311,7 +395,7 @@ Filter Flags:
-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]
+ -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
@@ -332,14 +416,17 @@ Filter Flags:
-e, --ena-support Instance types where ENA is supported or required
-f, --fpga-support FPGA instance types
--free-tier Free Tier supported
+ --generation int Generation of the instance type (i.e. c7i.xlarge is 7) (sets --generation-min and -max to the same value)
+ --generation-max int Maximum Generation of the instance type (i.e. c7i.xlarge is 7) If --generation-min is not specified, the lower bound will be 0
+ --generation-min int Minimum Generation of the instance type (i.e. c7i.xlarge is 7) If --generation-max is not specified, the upper bound will be infinity
--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
+ -g, --gpus int32 Total Number of GPUs (Example: 4) (sets --gpus-min and -max to the same value)
+ --gpus-max int32 Maximum Total Number of GPUs (Example: 4) If --gpus-min is not specified, the lower bound will be 0
+ --gpus-min int32 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)
@@ -355,9 +442,9 @@ Filter Flags:
--memory-max string Maximum Amount of Memory available (Example: 4 GiB) If --memory-min is not specified, the lower bound will be 0
--memory-min string Minimum Amount of Memory available (Example: 4 GiB) If --memory-max is not specified, the upper bound will be infinity
--network-encryption Instance Types that support automatic network encryption in-transit
- --network-interfaces int Number of network interfaces (ENIs) that can be attached to the instance (sets --network-interfaces-min and -max to the same value)
- --network-interfaces-max int Maximum Number of network interfaces (ENIs) that can be attached to the instance If --network-interfaces-min is not specified, the lower bound will be 0
- --network-interfaces-min int Minimum Number of network interfaces (ENIs) that can be attached to the instance If --network-interfaces-max is not specified, the upper bound will be infinity
+ --network-interfaces int32 Number of network interfaces (ENIs) that can be attached to the instance (sets --network-interfaces-min and -max to the same value)
+ --network-interfaces-max int32 Maximum Number of network interfaces (ENIs) that can be attached to the instance If --network-interfaces-min is not specified, the lower bound will be 0
+ --network-interfaces-min int32 Minimum Number of network interfaces (ENIs) that can be attached to the instance If --network-interfaces-max is not specified, the upper bound will be infinity
--network-performance int Bandwidth in Gib/s of network performance (Example: 100) (sets --network-performance-min and -max to the same value)
--network-performance-max int Maximum Bandwidth in Gib/s of network performance (Example: 100) If --network-performance-min is not specified, the lower bound will be 0
--network-performance-min int Minimum Bandwidth in Gib/s of network performance (Example: 100) If --network-performance-max is not specified, the upper bound will be infinity
@@ -368,9 +455,9 @@ Filter Flags:
--price-per-hour-min float Minimum Price/hour in USD (Example: 0.09) If --price-per-hour-max is not specified, the upper bound will be infinity
--root-device-type string Supported root device types: [ebs or instance-store]
-u, --usage-class string Usage class: [spot or on-demand]
- -c, --vcpus int Number of vcpus available to the instance type. (sets --vcpus-min and -max to the same value)
- --vcpus-max int Maximum Number of vcpus available to the instance type. If --vcpus-min is not specified, the lower bound will be 0
- --vcpus-min int Minimum Number of vcpus available to the instance type. If --vcpus-max is not specified, the upper bound will be infinity
+ -c, --vcpus int32 Number of vcpus available to the instance type. (sets --vcpus-min and -max to the same value)
+ --vcpus-max int32 Maximum Number of vcpus available to the instance type. If --vcpus-min is not specified, the lower bound will be 0
+ --vcpus-min int32 Minimum Number of vcpus available to the instance type. If --vcpus-max is not specified, the upper bound will be infinity
--vcpus-to-memory-ratio string The ratio of vcpus to GiBs of memory. (Example: 1:2)
--virtualization-type string Virtualization Type supported: [hvm or pv]
@@ -378,12 +465,13 @@ Filter Flags:
Suite Flags:
--base-instance-type string Instance Type used to retrieve similarly spec'd instance types
--flexible Retrieves a group of instance types spanning multiple generations based on opinionated defaults and user overridden resource filters
- --service string Filter instance types based on service support (Example: eks, eks-20201211, or emr-5.20.0)
+ --service string Filter instance types based on service support (Example: emr-5.20.0)
Global Flags:
--cache-dir string Directory to save the pricing and instance type caches (default "~/.ec2-instance-selector/")
- --cache-ttl int Cache TTLs in hours for pricing and instance type caches. Setting the cache to 0 will turn off caching and cleanup any on-disk caches. (default 168)
+ --cache-ttl int Cache TTLs in hours for pricing and instance type caches. Setting the cache to 0 will turn off caching and cleanup any on-disk caches.
+ --debug Debug - prints debug log messages
-h, --help Help
--max-results int The maximum number of instance types that match your criteria to return (default 20)
-o, --output string Specify the output format (table, table-wide, one-line, interactive)
@@ -405,30 +493,36 @@ This is a minimal example of using the instance selector go package directly:
package main
import (
+ "context"
"fmt"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ "github.com/aws/aws-sdk-go-v2/config"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
func main() {
+ // Initialize a context for the application
+ ctx := context.Background()
+
// Load an AWS session by looking at shared credentials or environment variables
- // https://docs.aws.amazon.com/sdk-for-go/api/aws/session/
- sess, err := session.NewSession(&aws.Config{
- Region: aws.String("us-east-2"),
- })
+ // https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk
+ cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-2"))
if err != nil {
fmt.Printf("Oh no, AWS session credentials cannot be found: %v", err)
return
}
// Instantiate a new instance of a selector with the AWS session
- instanceSelector := selector.New(sess)
+ instanceSelector, err := selector.New(ctx, cfg)
+ if err != nil {
+ fmt.Printf("Oh no, there was an error :( %v", err)
+ return
+ }
// Instantiate an int range filter to specify min and max vcpus
- vcpusRange := selector.IntRangeFilter{
+ vcpusRange := selector.Int32RangeFilter{
LowerBound: 2,
UpperBound: 4,
}
@@ -437,9 +531,9 @@ func main() {
LowerBound: bytequantity.FromGiB(2),
UpperBound: bytequantity.FromGiB(4),
}
- // Create a string for the CPU Architecture so that it can be passed as a pointer
+ // Create a variable for the CPU Architecture so that it can be passed as a pointer
// when creating the Filter struct
- cpuArch := "x86_64"
+ cpuArch := ec2types.ArchitectureTypeX8664
// Create a Filter struct with criteria you would like to filter
// The full struct definition can be found here for all of the supported filters:
@@ -451,7 +545,7 @@ func main() {
}
// Pass the Filter struct to the Filter function of your selector instance
- instanceTypesSlice, err := instanceSelector.Filter(filters)
+ instanceTypesSlice, err := instanceSelector.Filter(ctx, filters)
if err != nil {
fmt.Printf("Oh no, there was an error :( %v", err)
return
@@ -468,7 +562,7 @@ func main() {
$ git clone https://github.com/aws/amazon-ec2-instance-selector.git
$ cd amazon-ec2-instance-selector/
$ go run cmd/examples/example1.go
-[c4.large c5.large c5a.large c5ad.large c5d.large c6i.large t2.medium t3.medium t3.small t3a.medium t3a.small]
+[c4.large c5.large c5a.large c5ad.large c5d.large c6a.large c6i.large c6id.large c6in.large c7a.large c7i-flex.large c7i.large t2.medium t3.medium t3.small t3a.medium t3a.small]
```
## Building
diff --git a/THIRD_PARTY_LICENSES b/THIRD_PARTY_LICENSES
index 8760d32f..a6b00ed2 100644
--- a/THIRD_PARTY_LICENSES
+++ b/THIRD_PARTY_LICENSES
@@ -5,6 +5,24 @@ github.com/aws/amazon-ec2-instance-selector (Apache-2.0) Third Party Licenses:
AWS SDK for Go
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copyright 2014-2015 Stripe, Inc.
+** aws-sdk-go-v2; version v1.29.33 -- https://github.com/aws/aws-sdk-go-v2
+AWS SDK for Go
+Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+Copyright 2014-2015 Stripe, Inc.
+** smithy-go; version v1.13.3 -- https://github.com/aws/smithy-go
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+** configsources; version v1.1.22 -- https://github.com/aws/aws-sdk-go-v2/internal/configsources
+** ini; version v1.13.23 -- https://github.com/aws/aws-sdk-go-v2/internal/ini
+** endpoints; version v2.4.16 -- https://github.com/aws/aws-sdk-go-v2/internal/endpoints
+** config; version v1.17.6 -- https://github.com/aws/aws-sdk-go-v2/config
+** ec2; version v1.57.0 -- https://github.com/aws/aws-sdk-go-v2/service/ec2
+** presigned-url; version v1.9.16 -- https://github.com/aws/aws-sdk-go-v2/service/internal/presigned-url
+** ssooidc; version v1.13.4 -- https://github.com/aws/aws-sdk-go-v2/service/ssooidc
+** credentials; version v1.12.19 -- https://github.com/aws/aws-sdk-go-v2/credentials
+** imds; version v1.12.16 -- https://github.com/aws/aws-sdk-go-v2/feature/ec2/imds
+** pricing; version v1.17.0 -- https://github.com/aws/aws-sdk-go-v2/service/pricing
+** sso; version v1.11.22 -- https://github.com/aws/aws-sdk-go-v2/service/sso
+** sts; version v1.16.18 -- https://github.com/aws/aws-sdk-go-v2/service/sts
** cobra; version 0.0.6 -- https://github.com/spf13/cobra
Copyright © 2015 Steve Francia
** go-yaml/yaml; version v2.2.2 -- https://github.com/go-yaml/yaml
@@ -318,6 +336,10 @@ Copyright (c) 2012-2019 Patrick Mylund Nielsen and the go-cache contributors
Copyright (c) 2017-2021 Uber Technologies, Inc.
** atomic; version v1.4.0 -- https://go.uber.org/atomic
Copyright (c) 2017-2021 Uber Technologies, Inc.
+** go-localereader; version v0.0.1 -- https://github.com/mattn/go-localereader
+Copyright (c) 2022 Yasuhiro Matsumoto
+** go-osc52; version v1.0.3 -- https://github.com/aymanbagabas/go-osc52
+Copyright (c) 2022 Ayman Bagabas
The MIT License (MIT)
@@ -796,8 +818,8 @@ THE SOFTWARE.
------
-** github.com/imdario/mergo; version v0.3.11 --
-https://github.com/imdario/mergo
+** dario.cat/mergo; version v1.0.1 --
+dario.cat/mergo
Copyright (c) 2013 Dario Castañé. All rights reserved.
Copyright (c) 2012 The Go Authors. All rights reserved.
@@ -1067,6 +1089,7 @@ SOFTWARE.
** golang.org/x/sys; v0.0.0-20220727055044-e65921a090b8 --
https://cs.opensource.google/go/x/sys
+** golang.org/x/sync; v0.1.0 -- https://cs.opensource.google/go/x/sync
Copyright (c) 2009 The Go Authors. All rights reserved.
@@ -1478,4 +1501,266 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+SOFTWARE.
+
+------
+
+** github.com/charmbracelet/x/term; v0.2.0 --
+github.com/charmbracelet/x/term
+
+MIT License
+
+Copyright (c) 2023 Charmbracelet, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+------
+
+** github.com/charmbracelet/x/ansi; v0.2.3 --
+github.com/charmbracelet/x/ansi
+
+MIT License
+
+Copyright (c) 2023 Charmbracelet, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+------
+
+** github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding; v1.12.0 --
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
\ No newline at end of file
diff --git a/cmd/examples/example1.go b/cmd/examples/example1.go
index 31b44b09..b4760361 100644
--- a/cmd/examples/example1.go
+++ b/cmd/examples/example1.go
@@ -1,30 +1,36 @@
package main
import (
+ "context"
"fmt"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ "github.com/aws/aws-sdk-go-v2/config"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
func main() {
+ // Initialize a context for the application
+ ctx := context.Background()
+
// Load an AWS session by looking at shared credentials or environment variables
- // https://docs.aws.amazon.com/sdk-for-go/api/aws/session/
- sess, err := session.NewSession(&aws.Config{
- Region: aws.String("us-east-2"),
- })
+ // https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk
+ cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-2"))
if err != nil {
fmt.Printf("Oh no, AWS session credentials cannot be found: %v", err)
return
}
// Instantiate a new instance of a selector with the AWS session
- instanceSelector := selector.New(sess)
+ instanceSelector, err := selector.New(ctx, cfg)
+ if err != nil {
+ fmt.Printf("Oh no, there was an error :( %v", err)
+ return
+ }
// Instantiate an int range filter to specify min and max vcpus
- vcpusRange := selector.IntRangeFilter{
+ vcpusRange := selector.Int32RangeFilter{
LowerBound: 2,
UpperBound: 4,
}
@@ -33,9 +39,9 @@ func main() {
LowerBound: bytequantity.FromGiB(2),
UpperBound: bytequantity.FromGiB(4),
}
- // Create a string for the CPU Architecture so that it can be passed as a pointer
+ // Create a variable for the CPU Architecture so that it can be passed as a pointer
// when creating the Filter struct
- cpuArch := "x86_64"
+ cpuArch := ec2types.ArchitectureTypeX8664
// Create a Filter struct with criteria you would like to filter
// The full struct definition can be found here for all of the supported filters:
@@ -47,7 +53,7 @@ func main() {
}
// Pass the Filter struct to the Filter function of your selector instance
- instanceTypesSlice, err := instanceSelector.Filter(filters)
+ instanceTypesSlice, err := instanceSelector.Filter(ctx, filters)
if err != nil {
fmt.Printf("Oh no, there was an error :( %v", err)
return
diff --git a/cmd/main.go b/cmd/main.go
index c2dfede4..a7cacce3 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -14,6 +14,7 @@
package main
import (
+ "context"
"fmt"
"log"
"os"
@@ -23,18 +24,18 @@ import (
"syscall"
"time"
- commandline "github.com/aws/amazon-ec2-instance-selector/v2/pkg/cli"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/env"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector/outputs"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/sorter"
- "github.com/aws/aws-sdk-go/aws/session"
+ commandline "github.com/aws/amazon-ec2-instance-selector/v3/pkg/cli"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/env"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/config"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
tea "github.com/charmbracelet/bubbletea"
- homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"go.uber.org/multierr"
- "gopkg.in/ini.v1"
)
const (
@@ -43,7 +44,9 @@ const (
defaultRegionEnvVar = "AWS_DEFAULT_REGION"
defaultProfile = "default"
awsConfigFile = "~/.aws/config"
- spotPricingDaysBack = 30
+ // 0 means the last price
+ // increasing this results in a lot more API calls to EC2 which can slow things down
+ spotPricingDaysBack = 0
tableOutput = "table"
tableWideOutput = "table-wide"
@@ -99,6 +102,8 @@ const (
freeTier = "free-tier"
autoRecovery = "auto-recovery"
dedicatedHosts = "dedicated-hosts"
+ debug = "debug"
+ generation = "generation"
)
// Aggregate Filter Flags
@@ -162,12 +167,12 @@ Full docs can be found at github.com/aws/amazon-` + binName
// Registers flags with specific input types from the cli pkg
// Filter Flags - These will be grouped at the top of the help flags
- cli.IntMinMaxRangeFlags(vcpus, cli.StringMe("c"), nil, "Number of vcpus available to the instance type.")
+ cli.Int32MinMaxRangeFlags(vcpus, cli.StringMe("c"), nil, "Number of vcpus available to the instance type.")
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(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.Int32MinMaxRangeFlags(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)
@@ -186,7 +191,7 @@ Full docs can be found at github.com/aws/amazon-` + binName
cli.StringOptionsFlag(hypervisor, nil, nil, "Hypervisor: [xen or nitro]", []string{"xen", "nitro"})
cli.StringSliceFlag(availabilityZones, cli.StringMe("z"), nil, "Availability zones or zone ids to check EC2 capacity offered in specific AZs")
cli.BoolFlag(currentGeneration, nil, nil, "Current generation instance types (explicitly set this to false to not return current generation instance types)")
- cli.IntMinMaxRangeFlags(networkInterfaces, nil, nil, "Number of network interfaces (ENIs) that can be attached to the instance")
+ cli.Int32MinMaxRangeFlags(networkInterfaces, nil, nil, "Number of network interfaces (ENIs) that can be attached to the instance")
cli.IntMinMaxRangeFlags(networkPerformance, nil, nil, "Bandwidth in Gib/s of network performance (Example: 100)")
cli.BoolFlag(networkEncryption, nil, nil, "Instance Types that support automatic network encryption in-transit")
cli.BoolFlag(ipv6, nil, nil, "Instance Types that support IPv6")
@@ -205,12 +210,13 @@ Full docs can be found at github.com/aws/amazon-` + binName
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")
+ cli.IntMinMaxRangeFlags(generation, nil, nil, "Generation of the instance type (i.e. c7i.xlarge is 7)")
// Suite Flags - higher level aggregate filters that return opinionated result
cli.SuiteStringFlag(instanceTypeBase, nil, nil, "Instance Type used to retrieve similarly spec'd instance types", nil)
cli.SuiteBoolFlag(flexible, nil, nil, "Retrieves a group of instance types spanning multiple generations based on opinionated defaults and user overridden resource filters")
- cli.SuiteStringFlag(service, nil, nil, "Filter instance types based on service support (Example: eks, eks-20201211, or emr-5.20.0)", nil)
+ cli.SuiteStringFlag(service, nil, nil, "Filter instance types based on service support (Example: emr-5.20.0)", nil)
// Configuration Flags - These will be grouped at the bottom of the help flags
@@ -218,9 +224,10 @@ Full docs can be found at github.com/aws/amazon-` + binName
cli.ConfigStringFlag(profile, nil, nil, "AWS CLI profile to use for credentials and config", nil)
cli.ConfigStringFlag(region, cli.StringMe("r"), nil, "AWS Region to use for API requests (NOTE: if not passed in, uses AWS SDK default precedence)", nil)
cli.ConfigStringFlag(output, cli.StringMe("o"), nil, fmt.Sprintf("Specify the output format (%s)", strings.Join(cliOutputTypes, ", ")), nil)
- cli.ConfigIntFlag(cacheTTL, nil, env.WithDefaultInt("EC2_INSTANCE_SELECTOR_CACHE_TTL", 168), "Cache TTLs in hours for pricing and instance type caches. Setting the cache to 0 will turn off caching and cleanup any on-disk caches.")
+ cli.ConfigIntFlag(cacheTTL, nil, env.WithDefaultInt("EC2_INSTANCE_SELECTOR_CACHE_TTL", 0), "Cache TTLs in hours for pricing and instance type caches. Setting the cache to 0 will turn off caching and cleanup any on-disk caches.")
cli.ConfigPathFlag(cacheDir, nil, env.WithDefaultString("EC2_INSTANCE_SELECTOR_CACHE_DIR", "~/.ec2-instance-selector/"), "Directory to save the pricing and instance type caches")
cli.ConfigBoolFlag(verbose, cli.StringMe("v"), nil, "Verbose - will print out full instance specs")
+ cli.ConfigBoolFlag("debug", nil, nil, "Debug - prints debug log messages")
cli.ConfigBoolFlag(help, cli.StringMe("h"), nil, "Help")
cli.ConfigBoolFlag(version, nil, nil, "Prints CLI version")
cli.ConfigStringOptionsFlag(sortDirection, nil, cli.StringMe(sorter.SortAscending), fmt.Sprintf("Specify the direction to sort in (%s)", strings.Join(cliSortDirections, ", ")), cliSortDirections)
@@ -242,14 +249,40 @@ Full docs can be found at github.com/aws/amazon-` + binName
os.Exit(0)
}
- sess, err := getRegionAndProfileAWSSession(cli.StringMe(flags[region]), cli.StringMe(flags[profile]))
+ if flags[service] != nil {
+ log.Println("--service eks is deprecated. EKS generally supports all instance types")
+ }
+
+ ctx := context.Background()
+ cfg, err := config.LoadDefaultConfig(ctx,
+ config.WithSharedConfigProfile(
+ aws.ToString(
+ cli.StringMe(flags[profile]),
+ ),
+ ),
+ config.WithRegion(
+ aws.ToString(
+ cli.StringMe(flags[region]),
+ ),
+ ),
+ )
if err != nil {
- fmt.Println(err)
+ fmt.Printf("Failed to load default AWS configuration: %s\n", err.Error())
os.Exit(1)
}
- flags[region] = sess.Config.Region
+
+ flags[region] = cfg.Region
+
cacheTTLDuration := time.Hour * time.Duration(*cli.IntMe(flags[cacheTTL]))
- instanceSelector := selector.NewWithCache(sess, cacheTTLDuration, *cli.StringMe(flags[cacheDir]))
+ instanceSelector, err := selector.NewWithCache(ctx, cfg, cacheTTLDuration, *cli.StringMe(flags[cacheDir]))
+ if err != nil {
+ fmt.Printf("An error occurred when initialising the ec2 selector: %v", err)
+ os.Exit(1)
+ }
+ if flags[debug] != nil {
+ debugLogger := log.New(os.Stdout, time.Now().UTC().Format(time.RFC3339)+" DEBUG ", 0)
+ instanceSelector.SetLogger(debugLogger)
+ }
shutdown := func() {
if err := instanceSelector.Save(); err != nil {
log.Printf("There was an error saving pricing caches: %v", err)
@@ -264,7 +297,7 @@ Full docs can be found at github.com/aws/amazon-` + binName
// If output type is `table-wide`, simply print both prices for better comparison,
// even if the actual filter is applied on any one of those based on usage class
// Save time by hydrating all caches in parallel
- if err := hydrateCaches(*instanceSelector); err != nil {
+ if err := hydrateCaches(ctx, *instanceSelector); err != nil {
log.Printf("%v", err)
}
} else {
@@ -272,13 +305,13 @@ Full docs can be found at github.com/aws/amazon-` + binName
if flags[pricePerHour] != nil {
if flags[usageClass] == nil || *cli.StringMe(flags[usageClass]) == "on-demand" {
if instanceSelector.EC2Pricing.OnDemandCacheCount() == 0 {
- if err := instanceSelector.EC2Pricing.RefreshOnDemandCache(); err != nil {
+ if err := instanceSelector.EC2Pricing.RefreshOnDemandCache(ctx); err != nil {
log.Printf("There was a problem refreshing the on-demand pricing cache: %v", err)
}
}
} else {
if instanceSelector.EC2Pricing.SpotCacheCount() == 0 {
- if err := instanceSelector.EC2Pricing.RefreshSpotCache(spotPricingDaysBack); err != nil {
+ if err := instanceSelector.EC2Pricing.RefreshSpotCache(ctx, spotPricingDaysBack); err != nil {
log.Printf("There was a problem refreshing the spot pricing cache: %v", err)
}
}
@@ -289,13 +322,13 @@ Full docs can be found at github.com/aws/amazon-` + binName
if strings.Contains(lowercaseSortField, "price") {
if strings.Contains(lowercaseSortField, "spot") {
if instanceSelector.EC2Pricing.SpotCacheCount() == 0 {
- if err := instanceSelector.EC2Pricing.RefreshSpotCache(spotPricingDaysBack); err != nil {
+ if err := instanceSelector.EC2Pricing.RefreshSpotCache(ctx, spotPricingDaysBack); err != nil {
log.Printf("There was a problem refreshing the spot pricing cache: %v", err)
}
}
} else {
if instanceSelector.EC2Pricing.OnDemandCacheCount() == 0 {
- if err := instanceSelector.EC2Pricing.RefreshOnDemandCache(); err != nil {
+ if err := instanceSelector.EC2Pricing.RefreshOnDemandCache(ctx); err != nil {
log.Printf("There was a problem refreshing the on-demand pricing cache: %v", err)
}
}
@@ -303,13 +336,55 @@ Full docs can be found at github.com/aws/amazon-` + binName
}
}
+ var cpuArchitectureFilterValue *ec2types.ArchitectureType
+
+ if arch, ok := flags[cpuArchitecture].(*string); ok && arch != nil {
+ value := ec2types.ArchitectureType(*arch)
+ cpuArchitectureFilterValue = &value
+ }
+
+ var cpuManufacturerFilterValue *selector.CPUManufacturer
+
+ if cpuMan, ok := flags[cpuManufacturer].(*string); ok && cpuMan != nil {
+ value := selector.CPUManufacturer(*cpuMan)
+ cpuManufacturerFilterValue = &value
+ }
+
+ var virtualizationTypeFilterValue *ec2types.VirtualizationType
+
+ if virtType, ok := flags[virtualizationType].(*string); ok && virtType != nil {
+ value := ec2types.VirtualizationType(*virtType)
+ virtualizationTypeFilterValue = &value
+ }
+
+ var deviceTypeFilterValue *ec2types.RootDeviceType
+
+ if rootDev, ok := flags[rootDeviceType].(*string); ok && rootDev != nil {
+ value := ec2types.RootDeviceType(*rootDev)
+ deviceTypeFilterValue = &value
+ }
+
+ var usageClassFilterValue *ec2types.UsageClassType
+
+ if useClass, ok := flags[usageClass].(*string); ok && useClass != nil {
+ value := ec2types.UsageClassType(*useClass)
+ usageClassFilterValue = &value
+ }
+
+ var hypervisorFilterValue *ec2types.InstanceTypeHypervisor
+
+ if hype, ok := flags[hypervisor].(*string); ok && hype != nil {
+ value := ec2types.InstanceTypeHypervisor(*hype)
+ hypervisorFilterValue = &value
+ }
+
filters := selector.Filters{
- VCpusRange: cli.IntRangeMe(flags[vcpus]),
+ VCpusRange: cli.Int32RangeMe(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]),
+ CPUArchitecture: cpuArchitectureFilterValue,
+ CPUManufacturer: cpuManufacturerFilterValue,
+ GpusRange: cli.Int32RangeMe(flags[gpus]),
GpuMemoryRange: cli.ByteQuantityRangeMe(flags[gpuMemoryTotal]),
GPUManufacturer: cli.StringMe(flags[gpuManufacturer]),
GPUModel: cli.StringMe(flags[gpuModel]),
@@ -317,12 +392,12 @@ Full docs can be found at github.com/aws/amazon-` + binName
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]),
+ UsageClass: usageClassFilterValue,
+ RootDeviceType: deviceTypeFilterValue,
EnaSupport: cli.BoolMe(flags[enaSupport]),
EfaSupport: cli.BoolMe(flags[efaSupport]),
HibernationSupported: cli.BoolMe(flags[hibernationSupport]),
- Hypervisor: cli.StringMe(flags[hypervisor]),
+ Hypervisor: hypervisorFilterValue,
BareMetal: cli.BoolMe(flags[baremetal]),
Fpga: cli.BoolMe(flags[fpgaSupport]),
Burstable: cli.BoolMe(flags[burstSupport]),
@@ -330,7 +405,7 @@ Full docs can be found at github.com/aws/amazon-` + binName
AvailabilityZones: cli.StringSliceMe(flags[availabilityZones]),
CurrentGeneration: cli.BoolMe(flags[currentGeneration]),
MaxResults: cli.IntMe(flags[maxResults]),
- NetworkInterfaces: cli.IntRangeMe(flags[networkInterfaces]),
+ NetworkInterfaces: cli.Int32RangeMe(flags[networkInterfaces]),
NetworkPerformance: cli.IntRangeMe(flags[networkPerformance]),
NetworkEncryption: cli.BoolMe(flags[networkEncryption]),
IPv6: cli.BoolMe(flags[ipv6]),
@@ -339,7 +414,7 @@ Full docs can be found at github.com/aws/amazon-` + binName
InstanceTypeBase: cli.StringMe(flags[instanceTypeBase]),
Flexible: cli.BoolMe(flags[flexible]),
Service: cli.StringMe(flags[service]),
- VirtualizationType: cli.StringMe(flags[virtualizationType]),
+ VirtualizationType: virtualizationTypeFilterValue,
PricePerHour: cli.Float64RangeMe(flags[pricePerHour]),
InstanceStorageRange: cli.ByteQuantityRangeMe(flags[instanceStorage]),
DiskType: cli.StringMe(flags[diskType]),
@@ -352,11 +427,12 @@ Full docs can be found at github.com/aws/amazon-` + binName
FreeTier: cli.BoolMe(flags[freeTier]),
AutoRecovery: cli.BoolMe(flags[autoRecovery]),
DedicatedHosts: cli.BoolMe(flags[dedicatedHosts]),
+ Generation: cli.IntRangeMe(flags[generation]),
}
if flags[verbose] != nil {
resultsOutputFn = outputs.VerboseInstanceTypeOutput
- transformedFilters, err := instanceSelector.AggregateFilterTransform(filters)
+ transformedFilters, err := instanceSelector.AggregateFilterTransform(ctx, filters)
if err != nil {
fmt.Printf("An error occurred while transforming the aggregate filters")
os.Exit(1)
@@ -382,7 +458,7 @@ Full docs can be found at github.com/aws/amazon-` + binName
// fetch instance types without truncating results
prevMaxResults := filters.MaxResults
filters.MaxResults = nil
- instanceTypesDetails, err := instanceSelector.FilterVerbose(filters)
+ instanceTypesDetails, err := instanceSelector.FilterVerbose(ctx, filters)
if err != nil {
fmt.Printf("An error occurred when filtering instance types: %v", err)
os.Exit(1)
@@ -433,13 +509,13 @@ Full docs can be found at github.com/aws/amazon-` + binName
shutdown()
}
-func hydrateCaches(instanceSelector selector.Selector) (errs error) {
+func hydrateCaches(ctx context.Context, instanceSelector selector.Selector) (errs error) {
wg := &sync.WaitGroup{}
hydrateTasks := []func(*sync.WaitGroup) error{
func(waitGroup *sync.WaitGroup) error {
defer waitGroup.Done()
if instanceSelector.EC2Pricing.OnDemandCacheCount() == 0 {
- if err := instanceSelector.EC2Pricing.RefreshOnDemandCache(); err != nil {
+ if err := instanceSelector.EC2Pricing.RefreshOnDemandCache(ctx); err != nil {
return multierr.Append(errs, fmt.Errorf("There was a problem refreshing the on-demand pricing cache: %w", err))
}
}
@@ -448,7 +524,7 @@ func hydrateCaches(instanceSelector selector.Selector) (errs error) {
func(waitGroup *sync.WaitGroup) error {
defer waitGroup.Done()
if instanceSelector.EC2Pricing.SpotCacheCount() == 0 {
- if err := instanceSelector.EC2Pricing.RefreshSpotCache(spotPricingDaysBack); err != nil {
+ if err := instanceSelector.EC2Pricing.RefreshSpotCache(ctx, spotPricingDaysBack); err != nil {
return multierr.Append(errs, fmt.Errorf("There was a problem refreshing the spot pricing cache: %w", err))
}
}
@@ -457,7 +533,7 @@ func hydrateCaches(instanceSelector selector.Selector) (errs error) {
func(waitGroup *sync.WaitGroup) error {
defer waitGroup.Done()
if instanceSelector.InstanceTypesProvider.CacheCount() == 0 {
- if _, err := instanceSelector.InstanceTypesProvider.Get(nil); err != nil {
+ if _, err := instanceSelector.InstanceTypesProvider.Get(ctx, nil); err != nil {
return multierr.Append(errs, fmt.Errorf("There was a problem refreshing the instance types cache: %w", err))
}
}
@@ -487,71 +563,6 @@ func getOutputFn(outputFlag *string, currentFn selector.InstanceTypesOutputFn) s
return outputFn
}
-func getRegionAndProfileAWSSession(regionName *string, profileName *string) (*session.Session, error) {
- sessOpts := session.Options{SharedConfigState: session.SharedConfigEnable}
- if regionName != nil {
- sessOpts.Config.Region = regionName
- }
-
- if profileName != nil {
- sessOpts.Profile = *profileName
- if sessOpts.Config.Region == nil {
- if profileRegion, err := getProfileRegion(*profileName); err != nil {
- log.Println(err)
- } else {
- sessOpts.Config.Region = &profileRegion
- }
- }
- }
-
- sess := session.Must(session.NewSessionWithOptions(sessOpts))
- if sess.Config.Region != nil && *sess.Config.Region != "" {
- return sess, nil
- }
- if defaultProfileRegion, err := getProfileRegion(defaultProfile); err == nil {
- sess.Config.Region = &defaultProfileRegion
- return sess, nil
- }
-
- if defaultRegion, ok := os.LookupEnv(defaultRegionEnvVar); ok && defaultRegion != "" {
- sess.Config.Region = &defaultRegion
- return sess, nil
- }
-
- errorMsg := "Unable to find a region in the usual places: \n"
- errorMsg = errorMsg + "\t - --region flag\n"
- errorMsg = errorMsg + fmt.Sprintf("\t - %s environment variable\n", awsRegionEnvVar)
- if profileName != nil {
- errorMsg = errorMsg + fmt.Sprintf("\t - profile region in %s\n", awsConfigFile)
- }
- errorMsg = errorMsg + fmt.Sprintf("\t - default profile region in %s\n", awsConfigFile)
- errorMsg = errorMsg + fmt.Sprintf("\t - %s environment variable\n", defaultRegionEnvVar)
- return sess, fmt.Errorf(errorMsg)
-}
-
-func getProfileRegion(profileName string) (string, error) {
- if profileName != defaultProfile {
- profileName = fmt.Sprintf("profile %s", profileName)
- }
- awsConfigPath, err := homedir.Expand(awsConfigFile)
- if err != nil {
- return "", fmt.Errorf("Warning: unable to find home directory to parse aws config file")
- }
- awsConfigIni, err := ini.Load(awsConfigPath)
- if err != nil {
- return "", fmt.Errorf("Warning: unable to load aws config file for profile at path: %s", awsConfigPath)
- }
- section, err := awsConfigIni.GetSection(profileName)
- if err != nil {
- return "", fmt.Errorf("Warning: there is no configuration for the specified aws profile %s at %s", profileName, awsConfigPath)
- }
- regionConfig, err := section.GetKey("region")
- if err != nil || regionConfig.String() == "" {
- return "", fmt.Errorf("Warning: there is no region configured for the specified aws profile %s at %s", profileName, awsConfigPath)
- }
- return regionConfig.String(), nil
-}
-
func registerShutdown(shutdown func()) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
diff --git a/go.mod b/go.mod
index 94097483..e3163bbb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,40 +1,55 @@
-module github.com/aws/amazon-ec2-instance-selector/v2
+module github.com/aws/amazon-ec2-instance-selector/v3
-go 1.18
+go 1.23
require (
- github.com/aws/aws-sdk-go v1.44.59
+ dario.cat/mergo v1.0.1
+ github.com/aws/aws-sdk-go-v2 v1.32.2
+ github.com/aws/aws-sdk-go-v2/config v1.27.43
+ github.com/aws/aws-sdk-go-v2/service/ec2 v1.182.0
+ github.com/aws/aws-sdk-go-v2/service/pricing v1.32.2
github.com/blang/semver/v4 v4.0.0
- github.com/charmbracelet/bubbles v0.13.0
- github.com/charmbracelet/bubbletea v0.21.0
- github.com/charmbracelet/lipgloss v0.5.0
- github.com/evertras/bubble-table v0.14.4
- github.com/imdario/mergo v0.3.11
+ github.com/charmbracelet/bubbles v0.20.0
+ github.com/charmbracelet/bubbletea v1.1.1
+ github.com/charmbracelet/lipgloss v0.13.0
+ github.com/evertras/bubble-table v0.17.0
github.com/mitchellh/go-homedir v1.1.0
- github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739
+ github.com/muesli/termenv v0.15.2
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
github.com/patrickmn/go-cache v2.1.0+incompatible
- github.com/spf13/cobra v0.0.7
- github.com/spf13/pflag v1.0.3
- go.uber.org/multierr v1.1.0
- gopkg.in/ini.v1 v1.57.0
+ github.com/spf13/cobra v1.8.1
+ github.com/spf13/pflag v1.0.5
+ go.uber.org/multierr v1.11.0
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
- github.com/containerd/console v1.0.3 // indirect
- github.com/inconshreveable/mousetrap v1.0.0 // indirect
- github.com/jmespath/go-jmespath v0.4.0 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect
+ github.com/aws/smithy-go v1.22.0 // indirect
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
+ github.com/charmbracelet/x/ansi v0.2.3 // indirect
+ github.com/charmbracelet/x/term v0.2.0 // indirect
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
- github.com/mattn/go-isatty v0.0.14 // indirect
- github.com/mattn/go-runewidth v0.0.13 // indirect
- github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
- github.com/muesli/cancelreader v0.2.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-localereader v0.0.1 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
+ github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
- github.com/rivo/uniseg v0.2.0 // indirect
- github.com/sahilm/fuzzy v0.1.0 // indirect
- github.com/smartystreets/goconvey v1.6.4 // indirect
- go.uber.org/atomic v1.4.0 // indirect
- golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
- golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/sahilm/fuzzy v0.1.1 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/sys v0.24.0 // indirect
+ golang.org/x/text v0.16.0 // indirect
)
diff --git a/go.sum b/go.sum
index 3e5ec054..debeb046 100644
--- a/go.sum
+++ b/go.sum
@@ -1,216 +1,110 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
+dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/aws/aws-sdk-go v1.44.59 h1:bkdnNsMvMhFmNLqKDAJ6rKR+S0hjOt/3AIJp2mxOK9o=
-github.com/aws/aws-sdk-go v1.44.59/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI=
+github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
+github.com/aws/aws-sdk-go-v2/config v1.27.43 h1:p33fDDihFC390dhhuv8nOmX419wjOSDQRb+USt20RrU=
+github.com/aws/aws-sdk-go-v2/config v1.27.43/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
+github.com/aws/aws-sdk-go-v2/service/ec2 v1.182.0 h1:LaeziEhHZ/SJZYBK223QVzl3ucHvA9IP4tQMcxGrc9I=
+github.com/aws/aws-sdk-go-v2/service/ec2 v1.182.0/go.mod h1:kYXaB4FzyhEJjvrJ84oPnMElLiEAjGxxUunVW2tBSng=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0=
+github.com/aws/aws-sdk-go-v2/service/pricing v1.32.2 h1:eBKzA9Te6JHD1TfVjuja7pa8iEdXVzW5z0QPcbrPhNs=
+github.com/aws/aws-sdk-go-v2/service/pricing v1.32.2/go.mod h1:2Sg8KGFKp9zzUbY+XdUUEn7xjCzuRt8Zx4PHMwGzRvs=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk=
+github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI=
+github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo=
+github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo=
+github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM=
+github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q=
-github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
-github.com/charmbracelet/bubbles v0.13.0 h1:zP/ROH3wJEBqZWKIsD50ZKKlx3ydLInq3LdD/Nrlb8w=
-github.com/charmbracelet/bubbles v0.13.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
-github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI=
-github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
-github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
-github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
-github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
-github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
+github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
+github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
+github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
+github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
+github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
+github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
+github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
+github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
+github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/evertras/bubble-table v0.14.4 h1:UHUiPfsJ+lqbPSHIM1n7O8Ie2tbK0r9ReicXFnLg44I=
-github.com/evertras/bubble-table v0.14.4/go.mod h1:SPOZKbIpyYWPHBNki3fyNpiPBQkvkULAtOT7NTD5fKY=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
-github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
-github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
+github.com/evertras/bubble-table v0.17.0 h1:qQU4bi3IRxuZ5+Fvm3esyU/ucH9ufRXWhWL0fFuMn9c=
+github.com/evertras/bubble-table v0.17.0/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
-github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
-github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
-github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
-github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q=
-github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
-github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
-github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
-github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
-github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
+github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
-github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU=
-github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
+github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
+github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
+github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
-gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/awsapi/selectorec2.go b/pkg/awsapi/selectorec2.go
new file mode 100644
index 00000000..8523d291
--- /dev/null
+++ b/pkg/awsapi/selectorec2.go
@@ -0,0 +1,12 @@
+package awsapi
+
+import (
+ "context"
+ "github.com/aws/aws-sdk-go-v2/service/ec2"
+)
+
+type SelectorInterface interface {
+ ec2.DescribeInstanceTypeOfferingsAPIClient
+ ec2.DescribeInstanceTypesAPIClient
+ DescribeAvailabilityZones(ctx context.Context, params *ec2.DescribeAvailabilityZonesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeAvailabilityZonesOutput, error)
+}
diff --git a/pkg/bytequantity/bytequantity_test.go b/pkg/bytequantity/bytequantity_test.go
index 7a110873..2cae9b7e 100644
--- a/pkg/bytequantity/bytequantity_test.go
+++ b/pkg/bytequantity/bytequantity_test.go
@@ -4,8 +4,8 @@ import (
"fmt"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
)
func TestParseToByteQuantity(t *testing.T) {
diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go
index a86d3257..5b244dae 100644
--- a/pkg/cli/cli.go
+++ b/pkg/cli/cli.go
@@ -21,8 +21,8 @@ import (
"reflect"
"strings"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -178,6 +178,10 @@ func (cl *CommandLineInterface) SetUntouchedFlagValuesToNil() error {
return
}
switch v := cl.Flags[f.Name].(type) {
+ case *int32:
+ if reflect.ValueOf(*v).IsZero() {
+ cl.Flags[f.Name] = nil
+ }
case *int:
if reflect.ValueOf(*v).IsZero() {
cl.Flags[f.Name] = nil
@@ -234,6 +238,8 @@ func (cl *CommandLineInterface) ProcessRangeFilterFlags() error {
switch cl.Flags[rangeHelperMax].(type) {
case *int:
cl.Flags[rangeHelperMin] = cl.IntMe(0)
+ case *int32:
+ cl.Flags[rangeHelperMin] = cl.Int32Me(0)
case *bytequantity.ByteQuantity:
cl.Flags[rangeHelperMin] = cl.ByteQuantityMe(bytequantity.ByteQuantity{Quantity: 0})
case *float64:
@@ -245,6 +251,8 @@ func (cl *CommandLineInterface) ProcessRangeFilterFlags() error {
switch cl.Flags[rangeHelperMin].(type) {
case *int:
cl.Flags[rangeHelperMax] = cl.IntMe(maxInt)
+ case *int32:
+ cl.Flags[rangeHelperMax] = cl.Int32Me(max32Int)
case *bytequantity.ByteQuantity:
cl.Flags[rangeHelperMax] = cl.ByteQuantityMe(bytequantity.ByteQuantity{Quantity: maxUint64})
case *float64:
@@ -260,6 +268,11 @@ func (cl *CommandLineInterface) ProcessRangeFilterFlags() error {
LowerBound: *cl.IntMe(cl.Flags[rangeHelperMin]),
UpperBound: *cl.IntMe(cl.Flags[rangeHelperMax]),
}
+ case *int32:
+ cl.Flags[flagName] = &selector.Int32RangeFilter{
+ LowerBound: *cl.Int32Me(cl.Flags[rangeHelperMin]),
+ UpperBound: *cl.Int32Me(cl.Flags[rangeHelperMax]),
+ }
case *bytequantity.ByteQuantity:
cl.Flags[flagName] = &selector.ByteQuantityRangeFilter{
LowerBound: *cl.ByteQuantityMe(cl.Flags[rangeHelperMin]),
diff --git a/pkg/cli/cli_internal_test.go b/pkg/cli/cli_internal_test.go
index 019100bf..34ab1916 100644
--- a/pkg/cli/cli_internal_test.go
+++ b/pkg/cli/cli_internal_test.go
@@ -17,7 +17,7 @@ import (
"os"
"testing"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
"github.com/spf13/pflag"
)
diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go
index a3ed2535..8e7155c1 100644
--- a/pkg/cli/cli_test.go
+++ b/pkg/cli/cli_test.go
@@ -20,10 +20,10 @@ import (
"reflect"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/cli"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/cli"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
"github.com/spf13/cobra"
)
diff --git a/pkg/cli/flags.go b/pkg/cli/flags.go
index cc727ba5..1e235a17 100644
--- a/pkg/cli/flags.go
+++ b/pkg/cli/flags.go
@@ -7,13 +7,14 @@ import (
"strconv"
"strings"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
"github.com/mitchellh/go-homedir"
"github.com/spf13/pflag"
)
const (
maxInt = int(^uint(0) >> 1)
+ max32Int = int(^uint32(0) >> 1)
maxUint64 = math.MaxUint64
)
@@ -55,6 +56,11 @@ func (cl *CommandLineInterface) IntMinMaxRangeFlags(name string, shorthand *stri
cl.IntMinMaxRangeFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description)
}
+// Int32MinMaxRangeFlags creates and registers a min, max, and helper flag each accepting an int
+func (cl *CommandLineInterface) Int32MinMaxRangeFlags(name string, shorthand *string, defaultValue *int32, description string) {
+ cl.Int32MinMaxRangeFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description)
+}
+
// ByteQuantityMinMaxRangeFlags creates and registers a min, max, and helper flag each accepting a byte quantity like 512mb
func (cl *CommandLineInterface) ByteQuantityMinMaxRangeFlags(name string, shorthand *string, defaultValue *bytequantity.ByteQuantity, description string) {
cl.ByteQuantityMinMaxRangeFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description)
@@ -200,6 +206,27 @@ func (cl *CommandLineInterface) IntMinMaxRangeFlagOnFlagSet(flagSet *pflag.FlagS
cl.rangeFlags[name] = true
}
+// Int32MinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting an int
+func (cl *CommandLineInterface) Int32MinMaxRangeFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *int32, description string) {
+ cl.Int32FlagOnFlagSet(flagSet, name, shorthand, defaultValue, fmt.Sprintf("%s (sets --%s-min and -max to the same value)", description, name))
+ cl.Int32FlagOnFlagSet(flagSet, name+"-min", nil, nil, fmt.Sprintf("Minimum %s If --%s-max is not specified, the upper bound will be infinity", description, name))
+ cl.Int32FlagOnFlagSet(flagSet, name+"-max", nil, nil, fmt.Sprintf("Maximum %s If --%s-min is not specified, the lower bound will be 0", description, name))
+ cl.validators[name] = func(val interface{}) error {
+ if cl.Flags[name+"-min"] == nil || cl.Flags[name+"-max"] == nil {
+ return nil
+ }
+ minArg := name + "-min"
+ maxArg := name + "-max"
+ minVal := cl.Flags[minArg].(*int32)
+ maxVal := cl.Flags[maxArg].(*int32)
+ if *minVal > *maxVal {
+ return fmt.Errorf("Invalid input for --%s and --%s. %s must be less than or equal to %s", minArg, maxArg, minArg, maxArg)
+ }
+ return nil
+ }
+ cl.rangeFlags[name] = true
+}
+
// Float64MinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting a float64
func (cl *CommandLineInterface) Float64MinMaxRangeFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *float64, description string) {
cl.Float64FlagOnFlagSet(flagSet, name, shorthand, defaultValue, fmt.Sprintf("%s (sets --%s-min and -max to the same value)", description, name))
@@ -296,6 +323,19 @@ func (cl *CommandLineInterface) IntFlagOnFlagSet(flagSet *pflag.FlagSet, name st
cl.Flags[name] = flagSet.Int(name, *defaultValue, description)
}
+// Int32FlagOnFlagSet creates and registers a flag accepting an int
+func (cl *CommandLineInterface) Int32FlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *int32, description string) {
+ if defaultValue == nil {
+ cl.nilDefaults[name] = true
+ defaultValue = cl.Int32Me(0)
+ }
+ if shorthand != nil {
+ cl.Flags[name] = flagSet.Int32P(name, string(*shorthand), *defaultValue, description)
+ return
+ }
+ cl.Flags[name] = flagSet.Int32(name, *defaultValue, description)
+}
+
// Float64FlagOnFlagSet creates and registers a flag accepting a float64
func (cl *CommandLineInterface) Float64FlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *float64, description string) {
if defaultValue == nil {
diff --git a/pkg/cli/flags_test.go b/pkg/cli/flags_test.go
index ab737f93..6f15aa0b 100644
--- a/pkg/cli/flags_test.go
+++ b/pkg/cli/flags_test.go
@@ -17,8 +17,8 @@ import (
"fmt"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
)
// Tests
diff --git a/pkg/cli/types.go b/pkg/cli/types.go
index d2d059aa..ea23a992 100644
--- a/pkg/cli/types.go
+++ b/pkg/cli/types.go
@@ -18,8 +18,8 @@ import (
"log"
"regexp"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -105,6 +105,29 @@ func (*CommandLineInterface) IntMe(i interface{}) *int {
}
}
+// Int32Me takes an interface and returns a pointer to an int value
+// If the underlying interface kind is not int or *int then nil is returned
+func (*CommandLineInterface) Int32Me(i interface{}) *int32 {
+ if i == nil {
+ return nil
+ }
+ switch v := i.(type) {
+ case *int:
+ val := int32(*v)
+ return &val
+ case int:
+ val := int32(v)
+ return &val
+ case *int32:
+ return v
+ case int32:
+ return &v
+ default:
+ log.Printf("%s cannot be converted to an int32", i)
+ return nil
+ }
+}
+
// IntRangeMe takes an interface and returns a pointer to an IntRangeFilter value
// If the underlying interface kind is not IntRangeFilter or *IntRangeFilter then nil is returned
func (*CommandLineInterface) IntRangeMe(i interface{}) *selector.IntRangeFilter {
@@ -122,6 +145,23 @@ func (*CommandLineInterface) IntRangeMe(i interface{}) *selector.IntRangeFilter
}
}
+// Int32RangeMe takes an interface and returns a pointer to an Int32RangeFilter value
+// If the underlying interface kind is not Int32RangeFilter or *Int32RangeFilter then nil is returned
+func (*CommandLineInterface) Int32RangeMe(i interface{}) *selector.Int32RangeFilter {
+ if i == nil {
+ return nil
+ }
+ switch v := i.(type) {
+ case *selector.Int32RangeFilter:
+ return v
+ case selector.Int32RangeFilter:
+ return &v
+ default:
+ log.Printf("%s cannot be converted to an Int32Range", i)
+ return nil
+ }
+}
+
// ByteQuantityRangeMe takes an interface and returns a pointer to a ByteQuantityRangeFilter value
// If the underlying interface kind is not ByteQuantityRangeFilter or *ByteQuantityRangeFilter then nil is returned
func (*CommandLineInterface) ByteQuantityRangeMe(i interface{}) *selector.ByteQuantityRangeFilter {
diff --git a/pkg/cli/types_test.go b/pkg/cli/types_test.go
index 20ebaef5..2763181f 100644
--- a/pkg/cli/types_test.go
+++ b/pkg/cli/types_test.go
@@ -18,9 +18,9 @@ import (
"regexp"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
)
// Tests
diff --git a/pkg/ec2pricing/ec2pricing.go b/pkg/ec2pricing/ec2pricing.go
index d13c941d..527713bf 100644
--- a/pkg/ec2pricing/ec2pricing.go
+++ b/pkg/ec2pricing/ec2pricing.go
@@ -14,12 +14,14 @@
package ec2pricing
import (
+ "context"
+ "log"
"time"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/session"
- "github.com/aws/aws-sdk-go/service/ec2"
- "github.com/aws/aws-sdk-go/service/pricing"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/service/ec2"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
+ "github.com/aws/aws-sdk-go-v2/service/pricing"
"go.uber.org/multierr"
)
@@ -36,36 +38,47 @@ var (
type EC2Pricing struct {
ODPricing *OnDemandPricing
SpotPricing *SpotPricing
+ logger *log.Logger
}
// EC2PricingIface is the EC2Pricing interface mainly used to mock out ec2pricing during testing
type EC2PricingIface interface {
- GetOnDemandInstanceTypeCost(instanceType string) (float64, error)
- GetSpotInstanceTypeNDayAvgCost(instanceType string, availabilityZones []string, days int) (float64, error)
- RefreshOnDemandCache() error
- RefreshSpotCache(days int) error
+ GetOnDemandInstanceTypeCost(ctx context.Context, instanceType ec2types.InstanceType) (float64, error)
+ GetSpotInstanceTypeNDayAvgCost(ctx context.Context, instanceType ec2types.InstanceType, availabilityZones []string, days int) (float64, error)
+ RefreshOnDemandCache(ctx context.Context) error
+ RefreshSpotCache(ctx context.Context, days int) error
OnDemandCacheCount() int
SpotCacheCount() int
Save() error
+ SetLogger(*log.Logger)
+}
+
+// use us-east-1 since pricing only has endpoints in us-east-1 and ap-south-1
+// TODO: In the future we may want to allow the client to select which endpoint is used through some mechanism
+//
+// but that would likely happen through overriding this entire function as its signature is fixed
+func modifyPricingRegion(opt *pricing.Options) {
+ opt.Region = "us-east-1"
}
// New creates an instance of instance-selector EC2Pricing
-func New(sess *session.Session) *EC2Pricing {
- // use us-east-1 since pricing only has endpoints in us-east-1 and ap-south-1
- pricingClient := pricing.New(sess.Copy(aws.NewConfig().WithRegion("us-east-1")))
- return &EC2Pricing{
- ODPricing: LoadODCacheOrNew(pricingClient, *sess.Config.Region, 0, ""),
- SpotPricing: LoadSpotCacheOrNew(ec2.New(sess), *sess.Config.Region, 0, "", DefaultSpotDaysBack),
- }
+func New(ctx context.Context, cfg aws.Config) (*EC2Pricing, error) {
+ return NewWithCache(ctx, cfg, 0, "")
}
-func NewWithCache(sess *session.Session, ttl time.Duration, cacheDir string) *EC2Pricing {
- // use us-east-1 since pricing only has endpoints in us-east-1 and ap-south-1
- pricingClient := pricing.New(sess.Copy(aws.NewConfig().WithRegion("us-east-1")))
+func NewWithCache(ctx context.Context, cfg aws.Config, ttl time.Duration, cacheDir string) (*EC2Pricing, error) {
+ pricingClient := pricing.NewFromConfig(cfg, modifyPricingRegion)
+ ec2Client := ec2.NewFromConfig(cfg)
return &EC2Pricing{
- ODPricing: LoadODCacheOrNew(pricingClient, *sess.Config.Region, ttl, cacheDir),
- SpotPricing: LoadSpotCacheOrNew(ec2.New(sess), *sess.Config.Region, ttl, cacheDir, DefaultSpotDaysBack),
- }
+ ODPricing: LoadODCacheOrNew(ctx, pricingClient, cfg.Region, ttl, cacheDir),
+ SpotPricing: LoadSpotCacheOrNew(ctx, ec2Client, cfg.Region, ttl, cacheDir, DefaultSpotDaysBack),
+ }, nil
+}
+
+func (p *EC2Pricing) SetLogger(logger *log.Logger) {
+ p.logger = logger
+ p.ODPricing.SetLogger(logger)
+ p.SpotPricing.SetLogger(logger)
}
// OnDemandCacheCount returns the number of items in the OD cache
@@ -80,14 +93,14 @@ func (p *EC2Pricing) SpotCacheCount() int {
// GetSpotInstanceTypeNDayAvgCost retrieves the spot price history for a given AZ from the past N days and averages the price
// Passing an empty list for availabilityZones will retrieve avg cost for all AZs in the current AWSSession's region
-func (p *EC2Pricing) GetSpotInstanceTypeNDayAvgCost(instanceType string, availabilityZones []string, days int) (float64, error) {
+func (p *EC2Pricing) GetSpotInstanceTypeNDayAvgCost(ctx context.Context, instanceType ec2types.InstanceType, availabilityZones []string, days int) (float64, error) {
if len(availabilityZones) == 0 {
- return p.SpotPricing.Get(instanceType, "", days)
+ return p.SpotPricing.Get(ctx, instanceType, "", days)
}
costs := []float64{}
var errs error
for _, zone := range availabilityZones {
- cost, err := p.SpotPricing.Get(instanceType, zone, days)
+ cost, err := p.SpotPricing.Get(ctx, instanceType, zone, days)
if err != nil {
errs = multierr.Append(errs, err)
}
@@ -101,18 +114,18 @@ func (p *EC2Pricing) GetSpotInstanceTypeNDayAvgCost(instanceType string, availab
}
// GetOnDemandInstanceTypeCost retrieves the on-demand hourly cost for the specified instance type
-func (p *EC2Pricing) GetOnDemandInstanceTypeCost(instanceType string) (float64, error) {
- return p.ODPricing.Get(instanceType)
+func (p *EC2Pricing) GetOnDemandInstanceTypeCost(ctx context.Context, instanceType ec2types.InstanceType) (float64, error) {
+ return p.ODPricing.Get(ctx, instanceType)
}
// RefreshOnDemandCache makes a bulk request to the pricing api to retrieve all instance type pricing and stores them in a local cache
-func (p *EC2Pricing) RefreshOnDemandCache() error {
- return p.ODPricing.Refresh()
+func (p *EC2Pricing) RefreshOnDemandCache(ctx context.Context) error {
+ return p.ODPricing.Refresh(ctx)
}
// RefreshSpotCache makes a bulk request to the ec2 api to retrieve all spot instance type pricing and stores them in a local cache
-func (p *EC2Pricing) RefreshSpotCache(days int) error {
- return p.SpotPricing.Refresh(days)
+func (p *EC2Pricing) RefreshSpotCache(ctx context.Context, days int) error {
+ return p.SpotPricing.Refresh(ctx, days)
}
func (p *EC2Pricing) Save() error {
diff --git a/pkg/ec2pricing/ec2pricing_test.go b/pkg/ec2pricing/ec2pricing_test.go
index f4dfbac2..72d2c64e 100644
--- a/pkg/ec2pricing/ec2pricing_test.go
+++ b/pkg/ec2pricing/ec2pricing_test.go
@@ -14,128 +14,133 @@
package ec2pricing_test
import (
+ "context"
"encoding/json"
"fmt"
- "io/ioutil"
+ "os"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/ec2pricing"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/session"
- "github.com/aws/aws-sdk-go/service/ec2"
- "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
- "github.com/aws/aws-sdk-go/service/pricing"
- "github.com/aws/aws-sdk-go/service/pricing/pricingiface"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
+
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/ec2pricing"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
+ "github.com/aws/aws-sdk-go-v2/service/ec2"
+ "github.com/aws/aws-sdk-go-v2/service/pricing"
)
const (
- getProductsPages = "GetProductsPages"
- describeSpotPriceHistoryPages = "DescribeSpotPriceHistoryPages"
- mockFilesPath = "../../test/static"
+ getProducts = "GetProducts"
+ describeSpotPriceHistory = "DescribeSpotPriceHistory"
+ mockFilesPath = "../../test/static"
)
-var sess = session.Session{
- Config: &aws.Config{
- Region: aws.String("us-east-1"),
- },
-}
-
// Mocking helpers
-type gpFn = func(page *pricing.GetProductsOutput, lastPage bool) bool
-type dspFn = func(page *ec2.DescribeSpotPriceHistoryOutput, lastPage bool) bool
-
type mockedPricing struct {
- pricingiface.PricingAPI
- ec2iface.EC2API
- GetProductsPagesResp pricing.GetProductsOutput
- GetProductsPagesErr error
- DescribeSpotPriceHistoryPagesResp ec2.DescribeSpotPriceHistoryOutput
- DescribeSpotPriceHistoryPagesErr error
+ pricing.GetProductsAPIClient
+ GetProductsResp pricing.GetProductsOutput
+ GetProductsErr error
}
-func (m mockedPricing) GetProductsPages(input *pricing.GetProductsInput, fn gpFn) error {
- fn(&m.GetProductsPagesResp, true)
- return m.GetProductsPagesErr
+func (m mockedPricing) GetProducts(ctx context.Context, input *pricing.GetProductsInput, optFns ...func(*pricing.Options)) (*pricing.GetProductsOutput, error) {
+ return &m.GetProductsResp, m.GetProductsErr
}
-func (m mockedPricing) DescribeSpotPriceHistoryPages(input *ec2.DescribeSpotPriceHistoryInput, fn dspFn) error {
- fn(&m.DescribeSpotPriceHistoryPagesResp, true)
- return m.DescribeSpotPriceHistoryPagesErr
+type mockedSpotEC2 struct {
+ ec2.DescribeSpotPriceHistoryAPIClient
+ DescribeSpotPriceHistoryPagesResp ec2.DescribeSpotPriceHistoryOutput
+ DescribeSpotPriceHistoryPagesErr error
}
-func setupMock(t *testing.T, api string, file string) mockedPricing {
+func (m mockedSpotEC2) DescribeSpotPriceHistory(ctx context.Context, input *ec2.DescribeSpotPriceHistoryInput, optFns ...func(*ec2.Options)) (*ec2.DescribeSpotPriceHistoryOutput, error) {
+ return &m.DescribeSpotPriceHistoryPagesResp, m.DescribeSpotPriceHistoryPagesErr
+}
+
+func setupOdMock(t *testing.T, api string, file string) mockedPricing {
mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, api, file)
- mockFile, err := ioutil.ReadFile(mockFilename)
+ mockFile, err := os.ReadFile(mockFilename)
h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename))
switch api {
- case getProductsPages:
- var productsMap map[string]interface{}
- err = json.Unmarshal(mockFile, &productsMap)
- h.Assert(t, err == nil, "Error parsing mock json file contents "+mockFilename)
+ case getProducts:
+ priceList := []string{string(mockFile)}
productsOutput := pricing.GetProductsOutput{
- PriceList: []aws.JSONValue{productsMap},
+ PriceList: priceList,
}
return mockedPricing{
- GetProductsPagesResp: productsOutput,
+ GetProductsResp: productsOutput,
}
- case describeSpotPriceHistoryPages:
+
+ default:
+ h.Assert(t, false, "Unable to mock the provided API type "+api)
+ }
+ return mockedPricing{}
+}
+
+func setupEc2Mock(t *testing.T, api string, file string) mockedSpotEC2 {
+ mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, api, file)
+ mockFile, err := os.ReadFile(mockFilename)
+ h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename))
+ switch api {
+ case describeSpotPriceHistory:
dspho := ec2.DescribeSpotPriceHistoryOutput{}
err = json.Unmarshal(mockFile, &dspho)
h.Assert(t, err == nil, "Error parsing mock json file contents"+mockFilename)
- return mockedPricing{
+ return mockedSpotEC2{
DescribeSpotPriceHistoryPagesResp: dspho,
}
default:
h.Assert(t, false, "Unable to mock the provided API type "+api)
}
- return mockedPricing{}
+ return mockedSpotEC2{}
}
func TestGetOndemandInstanceTypeCost_m5large(t *testing.T) {
- pricingMock := setupMock(t, getProductsPages, "m5_large.json")
+ pricingMock := setupOdMock(t, getProducts, "m5_large.json")
+ ctx := context.Background()
ec2pricingClient := ec2pricing.EC2Pricing{
- ODPricing: ec2pricing.LoadODCacheOrNew(pricingMock, "us-east-1", 0, ""),
+ ODPricing: ec2pricing.LoadODCacheOrNew(ctx, pricingMock, "us-east-1", 0, ""),
}
- price, err := ec2pricingClient.GetOnDemandInstanceTypeCost("m5.large")
+ price, err := ec2pricingClient.GetOnDemandInstanceTypeCost(ctx, ec2types.InstanceTypeM5Large)
h.Ok(t, err)
h.Equals(t, float64(0.096), price)
}
func TestRefreshOnDemandCache(t *testing.T) {
- pricingMock := setupMock(t, getProductsPages, "m5_large.json")
+ pricingMock := setupOdMock(t, getProducts, "m5_large.json")
+ ctx := context.Background()
ec2pricingClient := ec2pricing.EC2Pricing{
- ODPricing: ec2pricing.LoadODCacheOrNew(pricingMock, "us-east-1", 0, ""),
+ ODPricing: ec2pricing.LoadODCacheOrNew(ctx, pricingMock, "us-east-1", 0, ""),
}
- err := ec2pricingClient.RefreshOnDemandCache()
+ err := ec2pricingClient.RefreshOnDemandCache(ctx)
h.Ok(t, err)
- price, err := ec2pricingClient.GetOnDemandInstanceTypeCost("m5.large")
+ price, err := ec2pricingClient.GetOnDemandInstanceTypeCost(ctx, ec2types.InstanceTypeM5Large)
h.Ok(t, err)
h.Equals(t, float64(0.096), price)
}
func TestGetSpotInstanceTypeNDayAvgCost(t *testing.T) {
- ec2Mock := setupMock(t, describeSpotPriceHistoryPages, "m5_large.json")
+ ec2Mock := setupEc2Mock(t, describeSpotPriceHistory, "m5_large.json")
+ ctx := context.Background()
ec2pricingClient := ec2pricing.EC2Pricing{
- SpotPricing: ec2pricing.LoadSpotCacheOrNew(ec2Mock, "us-east-1", 0, "", 30),
+ SpotPricing: ec2pricing.LoadSpotCacheOrNew(ctx, ec2Mock, "us-east-1", 0, "", 30),
}
- price, err := ec2pricingClient.GetSpotInstanceTypeNDayAvgCost("m5.large", []string{"us-east-1a"}, 30)
+ price, err := ec2pricingClient.GetSpotInstanceTypeNDayAvgCost(ctx, ec2types.InstanceTypeM5Large, []string{"us-east-1a"}, 30)
h.Ok(t, err)
h.Equals(t, float64(0.041486231229302666), price)
}
func TestRefreshSpotCache(t *testing.T) {
- ec2Mock := setupMock(t, describeSpotPriceHistoryPages, "m5_large.json")
+ ec2Mock := setupEc2Mock(t, describeSpotPriceHistory, "m5_large.json")
+ ctx := context.Background()
ec2pricingClient := ec2pricing.EC2Pricing{
- SpotPricing: ec2pricing.LoadSpotCacheOrNew(ec2Mock, "us-east-1", 0, "", 30),
+ SpotPricing: ec2pricing.LoadSpotCacheOrNew(ctx, ec2Mock, "us-east-1", 0, "", 30),
}
- err := ec2pricingClient.RefreshSpotCache(30)
+ err := ec2pricingClient.RefreshSpotCache(ctx, 30)
h.Ok(t, err)
- price, err := ec2pricingClient.GetSpotInstanceTypeNDayAvgCost("m5.large", []string{"us-east-1a"}, 30)
+ price, err := ec2pricingClient.GetSpotInstanceTypeNDayAvgCost(ctx, ec2types.InstanceTypeM5Large, []string{"us-east-1a"}, 30)
h.Ok(t, err)
h.Equals(t, float64(0.041486231229302666), price)
}
diff --git a/pkg/ec2pricing/odpricing.go b/pkg/ec2pricing/odpricing.go
index 1cc99a6a..7d1a0eeb 100644
--- a/pkg/ec2pricing/odpricing.go
+++ b/pkg/ec2pricing/odpricing.go
@@ -14,22 +14,21 @@
package ec2pricing
import (
+ "context"
"encoding/json"
"errors"
"fmt"
- "io/ioutil"
+ "io"
"log"
"os"
"path/filepath"
"strconv"
- "strings"
"sync"
"time"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/endpoints"
- "github.com/aws/aws-sdk-go/service/pricing"
- "github.com/aws/aws-sdk-go/service/pricing/pricingiface"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
+ "github.com/aws/aws-sdk-go-v2/service/pricing"
+ pricingtypes "github.com/aws/aws-sdk-go-v2/service/pricing/types"
"github.com/mitchellh/go-homedir"
"github.com/patrickmn/go-cache"
"go.uber.org/multierr"
@@ -44,11 +43,49 @@ type OnDemandPricing struct {
FullRefreshTTL time.Duration
DirectoryPath string
cache *cache.Cache
- pricingClient pricingiface.PricingAPI
+ pricingClient pricing.GetProductsAPIClient
+ logger *log.Logger
sync.RWMutex
}
-func LoadODCacheOrNew(pricingClient pricingiface.PricingAPI, region string, fullRefreshTTL time.Duration, directoryPath string) *OnDemandPricing {
+type PricingList struct {
+ Product PricingListProduct `json:"product"`
+ ServiceCode string `json:"serviceCode"`
+ Terms ProductTerms `json:"terms"`
+ Version string `json:"version"`
+ PublicationDate string `json:"publicationDate"`
+}
+
+type PricingListProduct struct {
+ ProductFamily string `json:"productFamily"`
+ ProductAttributes map[string]string `json:"attributes"`
+ SKU string `json:"sku"`
+}
+
+type ProductTerms struct {
+ OnDemand map[string]ProductPricingInfo `json:"OnDemand"`
+ Reserved map[string]ProductPricingInfo `json:"Reserved"`
+}
+
+type ProductPricingInfo struct {
+ PriceDimensions map[string]PriceDimensionInfo `json:"priceDimensions"`
+ SKU string `json:"sku"`
+ EffectiveDate string `json:"effectiveDate"`
+ OfferTermCode string `json:"offerTermCode"`
+ TermAttributes map[string]string `json:"termAttributes"`
+}
+
+type PriceDimensionInfo struct {
+ Unit string `json:"unit"`
+ EndRange string `json:"endRange"`
+ Description string `json:"description"`
+ AppliesTo []string `json:"appliesTo"`
+ RateCode string `json:"rateCode"`
+ BeginRange string `json:"beginRange"`
+ PricePerUnit map[string]string `json:"pricePerUnit"`
+}
+
+func LoadODCacheOrNew(ctx context.Context, pricingClient pricing.GetProductsAPIClient, region string, fullRefreshTTL time.Duration, directoryPath string) *OnDemandPricing {
expandedDirPath, err := homedir.Expand(directoryPath)
if err != nil {
log.Printf("Unable to load on-demand pricing cache directory %s: %v", expandedDirPath, err)
@@ -58,6 +95,7 @@ func LoadODCacheOrNew(pricingClient pricingiface.PricingAPI, region string, full
DirectoryPath: directoryPath,
cache: cache.New(fullRefreshTTL, fullRefreshTTL),
pricingClient: pricingClient,
+ logger: log.New(io.Discard, "", 0),
}
}
odPricing := &OnDemandPricing{
@@ -66,13 +104,14 @@ func LoadODCacheOrNew(pricingClient pricingiface.PricingAPI, region string, full
DirectoryPath: expandedDirPath,
pricingClient: pricingClient,
cache: cache.New(fullRefreshTTL, fullRefreshTTL),
+ logger: log.New(io.Discard, "", 0),
}
if fullRefreshTTL <= 0 {
odPricing.Clear()
return odPricing
}
// Start the cache refresh job
- go odCacheRefreshJob(odPricing)
+ go odCacheRefreshJob(ctx, odPricing)
odCache, err := loadODCacheFrom(fullRefreshTTL, region, expandedDirPath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
@@ -102,22 +141,26 @@ func getODCacheFilePath(region string, directoryPath string) string {
return filepath.Join(directoryPath, fmt.Sprintf("%s-%s", region, ODCacheFileName))
}
-func odCacheRefreshJob(odPricing *OnDemandPricing) {
+func odCacheRefreshJob(ctx context.Context, odPricing *OnDemandPricing) {
if odPricing.FullRefreshTTL <= 0 {
return
}
refreshTicker := time.NewTicker(odPricing.FullRefreshTTL)
for range refreshTicker.C {
- if err := odPricing.Refresh(); err != nil {
+ if err := odPricing.Refresh(ctx); err != nil {
log.Println(err)
}
}
}
-func (c *OnDemandPricing) Refresh() error {
+func (c *OnDemandPricing) SetLogger(logger *log.Logger) {
+ c.logger = logger
+}
+
+func (c *OnDemandPricing) Refresh(ctx context.Context) error {
c.Lock()
defer c.Unlock()
- odInstanceTypeCosts, err := c.fetchOnDemandPricing("")
+ odInstanceTypeCosts, err := c.fetchOnDemandPricing(ctx, "")
if err != nil {
return fmt.Errorf("there was a problem refreshing the on-demand instance type pricing cache: %v", err)
}
@@ -130,18 +173,18 @@ func (c *OnDemandPricing) Refresh() error {
return nil
}
-func (c *OnDemandPricing) Get(instanceType string) (float64, error) {
- if cost, ok := c.cache.Get(instanceType); ok {
+func (c *OnDemandPricing) Get(ctx context.Context, instanceType ec2types.InstanceType) (float64, error) {
+ if cost, ok := c.cache.Get(string(instanceType)); ok {
return cost.(float64), nil
}
c.RLock()
defer c.RUnlock()
- costs, err := c.fetchOnDemandPricing(instanceType)
+ costs, err := c.fetchOnDemandPricing(ctx, instanceType)
if err != nil {
return 0, fmt.Errorf("there was a problem fetching on-demand instance type pricing for %s: %v", instanceType, err)
}
- c.cache.SetDefault(instanceType, costs[instanceType])
- return costs[instanceType], nil
+ c.cache.SetDefault(string(instanceType), costs[string(instanceType)])
+ return costs[string(instanceType)], nil
}
// Count of items in the cache
@@ -157,8 +200,10 @@ func (c *OnDemandPricing) Save() error {
if err != nil {
return err
}
- os.Mkdir(c.DirectoryPath, 0755)
- return ioutil.WriteFile(getODCacheFilePath(c.Region, c.DirectoryPath), cacheBytes, 0644)
+ if err := os.Mkdir(c.DirectoryPath, 0755); err != nil && !errors.Is(err, os.ErrExist) {
+ return err
+ }
+ return os.WriteFile(getODCacheFilePath(c.Region, c.DirectoryPath), cacheBytes, 0644)
}
func (c *OnDemandPricing) Clear() error {
@@ -169,15 +214,30 @@ func (c *OnDemandPricing) Clear() error {
}
// fetchOnDemandPricing makes a bulk request to the pricing api to retrieve all instance type pricing if the instanceType is the empty string
-// or, if instanceType is specified, it can request a specific instance type pricing
-func (c *OnDemandPricing) fetchOnDemandPricing(instanceType string) (map[string]float64, error) {
+//
+// or, if instanceType is specified, it can request a specific instance type pricing
+func (c *OnDemandPricing) fetchOnDemandPricing(ctx context.Context, instanceType ec2types.InstanceType) (map[string]float64, error) {
+ start := time.Now()
+ calls := 0
+ defer func() {
+ c.logger.Printf("Took %s and %d calls to collect OD pricing", time.Since(start), calls)
+ }()
odPricing := map[string]float64{}
productInput := pricing.GetProductsInput{
- ServiceCode: aws.String(serviceCode),
+ ServiceCode: c.StringMe(serviceCode),
Filters: c.getProductsInputFilters(instanceType),
}
var processingErr error
- errAPI := c.pricingClient.GetProductsPages(&productInput, func(pricingOutput *pricing.GetProductsOutput, nextPage bool) bool {
+
+ p := pricing.NewGetProductsPaginator(c.pricingClient, &productInput)
+
+ for p.HasMorePages() {
+ calls++
+ pricingOutput, err := p.NextPage(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get next OD pricing page, %w", err)
+ }
+
for _, priceDoc := range pricingOutput.PriceList {
instanceTypeName, price, errParse := c.parseOndemandUnitPrice(priceDoc)
if errParse != nil {
@@ -186,92 +246,62 @@ func (c *OnDemandPricing) fetchOnDemandPricing(instanceType string) (map[string]
}
odPricing[instanceTypeName] = price
}
- return true
- })
- if errAPI != nil {
- return odPricing, errAPI
}
return odPricing, processingErr
}
-func (c *OnDemandPricing) getProductsInputFilters(instanceType string) []*pricing.Filter {
- regionDescription := c.getRegionForPricingAPI()
- filters := []*pricing.Filter{
- {Type: aws.String(pricing.FilterTypeTermMatch), Field: aws.String("ServiceCode"), Value: aws.String(serviceCode)},
- {Type: aws.String(pricing.FilterTypeTermMatch), Field: aws.String("operatingSystem"), Value: aws.String("linux")},
- {Type: aws.String(pricing.FilterTypeTermMatch), Field: aws.String("location"), Value: aws.String(regionDescription)},
- {Type: aws.String(pricing.FilterTypeTermMatch), Field: aws.String("capacitystatus"), Value: aws.String("used")},
- {Type: aws.String(pricing.FilterTypeTermMatch), Field: aws.String("preInstalledSw"), Value: aws.String("NA")},
- {Type: aws.String(pricing.FilterTypeTermMatch), Field: aws.String("tenancy"), Value: aws.String("shared")},
+// StringMe takes an interface and returns a pointer to a string value
+// If the underlying interface kind is not string or *string then nil is returned
+func (c *OnDemandPricing) StringMe(i interface{}) *string {
+ if i == nil {
+ return nil
}
- if instanceType != "" {
- filters = append(filters, &pricing.Filter{Type: aws.String(pricing.FilterTypeTermMatch), Field: aws.String("instanceType"), Value: aws.String(instanceType)})
+ switch v := i.(type) {
+ case *string:
+ return v
+ case string:
+ return &v
+ default:
+ c.logger.Printf("%s cannot be converted to a string", i)
+ return nil
}
- return filters
}
-// getRegionForPricingAPI attempts to retrieve the region description based on the AWS session used to create
-// the ec2pricing struct. It then uses the endpoints package in the aws sdk to retrieve the region description
-// This is necessary because the pricing API uses the region description rather than a region ID
-func (c *OnDemandPricing) getRegionForPricingAPI() string {
- endpointResolver := endpoints.DefaultResolver()
- partitions := endpointResolver.(endpoints.EnumPartitions).Partitions()
-
- // use us-east-1 as the default
- regionDescription := "US East (N. Virginia)"
- for _, partition := range partitions {
- regions := partition.Regions()
- if region, ok := regions[c.Region]; ok {
- regionDescription = region.Description()
- }
+func (c *OnDemandPricing) getProductsInputFilters(instanceType ec2types.InstanceType) []pricingtypes.Filter {
+ filters := []pricingtypes.Filter{
+ {Type: pricingtypes.FilterTypeTermMatch, Field: c.StringMe("ServiceCode"), Value: c.StringMe(serviceCode)},
+ {Type: pricingtypes.FilterTypeTermMatch, Field: c.StringMe("operatingSystem"), Value: c.StringMe("linux")},
+ {Type: pricingtypes.FilterTypeTermMatch, Field: c.StringMe("regionCode"), Value: c.StringMe(c.Region)},
+ {Type: pricingtypes.FilterTypeTermMatch, Field: c.StringMe("capacitystatus"), Value: c.StringMe("used")},
+ {Type: pricingtypes.FilterTypeTermMatch, Field: c.StringMe("preInstalledSw"), Value: c.StringMe("NA")},
+ {Type: pricingtypes.FilterTypeTermMatch, Field: c.StringMe("tenancy"), Value: c.StringMe("shared")},
}
-
- // endpoints package returns European regions with the word "Europe," but the pricing API expects the word "EU."
- // This formatting mismatch is only present with European regions.
- // So replace "Europe" with "EU" if it exists in the regionDescription string.
- regionDescription = strings.ReplaceAll(regionDescription, "Europe", "EU")
-
- return regionDescription
+ if instanceType != "" {
+ filters = append(filters, pricingtypes.Filter{Type: pricingtypes.FilterTypeTermMatch, Field: c.StringMe("instanceType"), Value: c.StringMe(string(instanceType))})
+ }
+ return filters
}
// parseOndemandUnitPrice takes a priceList from the pricing API and parses its weirdness
-func (c *OnDemandPricing) parseOndemandUnitPrice(priceList aws.JSONValue) (string, float64, error) {
- // TODO: this could probably be cleaned up a bit by adding a couple structs with json tags
- // We still need to some weird for-loops to get at elements under json keys that are IDs...
- // But it would probably be cleaner than this.
- attributes, ok := priceList["product"].(map[string]interface{})["attributes"]
- if !ok {
- return "", float64(-1.0), fmt.Errorf("unable to find product attributes")
- }
- instanceTypeName, ok := attributes.(map[string]interface{})["instanceType"].(string)
- if !ok {
- return "", float64(-1.0), fmt.Errorf("unable to find instance type name from product attributes")
- }
- terms, ok := priceList["terms"]
- if !ok {
- return instanceTypeName, float64(-1.0), fmt.Errorf("unable to find pricing terms")
- }
- ondemandTerms, ok := terms.(map[string]interface{})["OnDemand"]
- if !ok {
- return instanceTypeName, float64(-1.0), fmt.Errorf("unable to find on-demand pricing terms")
+func (c *OnDemandPricing) parseOndemandUnitPrice(priceList string) (string, float64, error) {
+ var productPriceList PricingList
+ err := json.Unmarshal([]byte(priceList), &productPriceList)
+ if err != nil {
+ return "", float64(-1.0), fmt.Errorf("unable to parse pricing doc: %w", err)
}
- for _, priceDimensions := range ondemandTerms.(map[string]interface{}) {
- dim, ok := priceDimensions.(map[string]interface{})["priceDimensions"]
- if !ok {
- return instanceTypeName, float64(-1.0), fmt.Errorf("unable to find on-demand pricing dimensions")
- }
- for _, dimension := range dim.(map[string]interface{}) {
- dims := dimension.(map[string]interface{})
- pricePerUnit, ok := dims["pricePerUnit"]
- if !ok {
- return instanceTypeName, float64(-1.0), fmt.Errorf("unable to find on-demand price per unit in pricing dimensions")
- }
- pricePerUnitInUSDStr, ok := pricePerUnit.(map[string]interface{})["USD"]
+ attributes := productPriceList.Product.ProductAttributes
+ instanceTypeName := attributes["instanceType"]
+
+ for _, priceDimensions := range productPriceList.Terms.OnDemand {
+ dim := priceDimensions.PriceDimensions
+ for _, dimension := range dim {
+ pricePerUnit := dimension.PricePerUnit
+ pricePerUnitInUSDStr, ok := pricePerUnit["USD"]
if !ok {
return instanceTypeName, float64(-1.0), fmt.Errorf("unable to find on-demand price per unit in USD")
}
var err error
- pricePerUnitInUSD, err := strconv.ParseFloat(pricePerUnitInUSDStr.(string), 64)
+ pricePerUnitInUSD, err := strconv.ParseFloat(pricePerUnitInUSDStr, 64)
if err != nil {
return instanceTypeName, float64(-1.0), fmt.Errorf("could not convert price per unit in USD to a float64")
}
diff --git a/pkg/ec2pricing/spotpricing.go b/pkg/ec2pricing/spotpricing.go
index 6f4bc6d5..1f4d85d1 100644
--- a/pkg/ec2pricing/spotpricing.go
+++ b/pkg/ec2pricing/spotpricing.go
@@ -14,9 +14,11 @@
package ec2pricing
import (
+ "context"
"encoding/gob"
"errors"
"fmt"
+ "io"
"log"
"math"
"os"
@@ -26,9 +28,8 @@ import (
"sync"
"time"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/service/ec2"
- "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
+ "github.com/aws/aws-sdk-go-v2/service/ec2"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/mitchellh/go-homedir"
"github.com/patrickmn/go-cache"
"go.uber.org/multierr"
@@ -43,7 +44,8 @@ type SpotPricing struct {
FullRefreshTTL time.Duration
DirectoryPath string
cache *cache.Cache
- ec2Client ec2iface.EC2API
+ ec2Client ec2.DescribeSpotPriceHistoryAPIClient
+ logger *log.Logger
sync.RWMutex
}
@@ -53,7 +55,7 @@ type spotPricingEntry struct {
Zone string
}
-func LoadSpotCacheOrNew(ec2Client ec2iface.EC2API, region string, fullRefreshTTL time.Duration, directoryPath string, days int) *SpotPricing {
+func LoadSpotCacheOrNew(ctx context.Context, ec2Client ec2.DescribeSpotPriceHistoryAPIClient, region string, fullRefreshTTL time.Duration, directoryPath string, days int) *SpotPricing {
expandedDirPath, err := homedir.Expand(directoryPath)
if err != nil {
log.Printf("Unable to load spot pricing cache directory %s: %v", expandedDirPath, err)
@@ -63,6 +65,7 @@ func LoadSpotCacheOrNew(ec2Client ec2iface.EC2API, region string, fullRefreshTTL
DirectoryPath: directoryPath,
cache: cache.New(fullRefreshTTL, fullRefreshTTL),
ec2Client: ec2Client,
+ logger: log.New(io.Discard, "", 0),
}
}
spotPricing := &SpotPricing{
@@ -71,6 +74,7 @@ func LoadSpotCacheOrNew(ec2Client ec2iface.EC2API, region string, fullRefreshTTL
DirectoryPath: expandedDirPath,
ec2Client: ec2Client,
cache: cache.New(fullRefreshTTL, fullRefreshTTL),
+ logger: log.New(io.Discard, "", 0),
}
if fullRefreshTTL <= 0 {
spotPricing.Clear()
@@ -78,7 +82,7 @@ func LoadSpotCacheOrNew(ec2Client ec2iface.EC2API, region string, fullRefreshTTL
}
gob.Register([]*spotPricingEntry{})
// Start the cache refresh job
- go spotCacheRefreshJob(spotPricing, days)
+ go spotCacheRefreshJob(ctx, spotPricing, days)
spotCache, err := loadSpotCacheFrom(fullRefreshTTL, region, expandedDirPath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
@@ -109,22 +113,26 @@ func getSpotCacheFilePath(region string, directoryPath string) string {
return filepath.Join(directoryPath, fmt.Sprintf("%s-%s", region, SpotCacheFileName))
}
-func spotCacheRefreshJob(spotPricing *SpotPricing, days int) {
+func spotCacheRefreshJob(ctx context.Context, spotPricing *SpotPricing, days int) {
if spotPricing.FullRefreshTTL <= 0 {
return
}
refreshTicker := time.NewTicker(spotPricing.FullRefreshTTL)
for range refreshTicker.C {
- if err := spotPricing.Refresh(days); err != nil {
+ if err := spotPricing.Refresh(ctx, days); err != nil {
log.Println(err)
}
}
}
-func (c *SpotPricing) Refresh(days int) error {
+func (c *SpotPricing) SetLogger(logger *log.Logger) {
+ c.logger = logger
+}
+
+func (c *SpotPricing) Refresh(ctx context.Context, days int) error {
c.Lock()
defer c.Unlock()
- spotInstanceTypeCosts, err := c.fetchSpotPricingTimeSeries("", days)
+ spotInstanceTypeCosts, err := c.fetchSpotPricingTimeSeries(ctx, "", days)
if err != nil {
return fmt.Errorf("there was a problem refreshing the spot instance type pricing cache: %v", err)
}
@@ -137,8 +145,8 @@ func (c *SpotPricing) Refresh(days int) error {
return nil
}
-func (c *SpotPricing) Get(instanceType string, zone string, days int) (float64, error) {
- entries, ok := c.cache.Get(instanceType)
+func (c *SpotPricing) Get(ctx context.Context, instanceType ec2types.InstanceType, zone string, days int) (float64, error) {
+ entries, ok := c.cache.Get(string(instanceType))
if zone != "" && ok {
if !c.contains(zone, entries.([]*spotPricingEntry)) {
ok = false
@@ -147,7 +155,7 @@ func (c *SpotPricing) Get(instanceType string, zone string, days int) (float64,
if !ok {
c.RLock()
defer c.RUnlock()
- zonalSpotPricing, err := c.fetchSpotPricingTimeSeries(instanceType, days)
+ zonalSpotPricing, err := c.fetchSpotPricingTimeSeries(ctx, instanceType, days)
if err != nil {
return -1, fmt.Errorf("there was a problem fetching spot instance type pricing for %s: %v", instanceType, err)
}
@@ -156,7 +164,7 @@ func (c *SpotPricing) Get(instanceType string, zone string, days int) (float64,
}
}
- entries, ok = c.cache.Get(instanceType)
+ entries, ok = c.cache.Get(string(instanceType))
if !ok {
return -1, fmt.Errorf("unable to get spot pricing for %s in zone %s for %d days back", instanceType, zone, days)
}
@@ -240,37 +248,48 @@ func (c *SpotPricing) Clear() error {
// fetchSpotPricingTimeSeries makes a bulk request to the ec2 api to retrieve all spot instance type pricing for the past n days
// If instanceType is empty, it will fetch for all instance types
-func (c *SpotPricing) fetchSpotPricingTimeSeries(instanceType string, days int) (map[string][]*spotPricingEntry, error) {
+func (c *SpotPricing) fetchSpotPricingTimeSeries(ctx context.Context, instanceType ec2types.InstanceType, days int) (map[string][]*spotPricingEntry, error) {
+ start := time.Now()
+ calls := 0
+ defer func() {
+ c.logger.Printf("Took %s and %d calls to collect Spot pricing", time.Since(start), calls)
+ }()
spotTimeSeries := map[string][]*spotPricingEntry{}
endTime := time.Now().UTC()
startTime := endTime.Add(time.Hour * time.Duration(24*-1*days))
spotPriceHistInput := ec2.DescribeSpotPriceHistoryInput{
- ProductDescriptions: []*string{aws.String(productDescription)},
+ ProductDescriptions: []string{productDescription},
StartTime: &startTime,
EndTime: &endTime,
}
if instanceType != "" {
- spotPriceHistInput.InstanceTypes = append(spotPriceHistInput.InstanceTypes, &instanceType)
+ spotPriceHistInput.InstanceTypes = append(spotPriceHistInput.InstanceTypes, instanceType)
}
var processingErr error
- errAPI := c.ec2Client.DescribeSpotPriceHistoryPages(&spotPriceHistInput, func(dspho *ec2.DescribeSpotPriceHistoryOutput, b bool) bool {
- for _, history := range dspho.SpotPriceHistory {
+
+ p := ec2.NewDescribeSpotPriceHistoryPaginator(c.ec2Client, &spotPriceHistInput)
+
+ // Iterate through the Amazon S3 object pages.
+ for p.HasMorePages() {
+ calls++
+ spotHistoryOutput, err := p.NextPage(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get a spot pricing page, %w", err)
+ }
+
+ for _, history := range spotHistoryOutput.SpotPriceHistory {
spotPrice, errFloat := strconv.ParseFloat(*history.SpotPrice, 64)
if errFloat != nil {
processingErr = multierr.Append(processingErr, errFloat)
continue
}
- instanceType := *history.InstanceType
- spotTimeSeries[instanceType] = append(spotTimeSeries[instanceType], &spotPricingEntry{
+ spotTimeSeries[string(history.InstanceType)] = append(spotTimeSeries[string(history.InstanceType)], &spotPricingEntry{
Timestamp: *history.Timestamp,
SpotPrice: spotPrice,
Zone: *history.AvailabilityZone,
})
}
- return true
- })
- if errAPI != nil {
- return spotTimeSeries, errAPI
}
+
return spotTimeSeries, processingErr
}
diff --git a/pkg/instancetypes/instancetypes.go b/pkg/instancetypes/instancetypes.go
index 4e83a846..fa44f538 100644
--- a/pkg/instancetypes/instancetypes.go
+++ b/pkg/instancetypes/instancetypes.go
@@ -14,17 +14,18 @@
package instancetypes
import (
+ "context"
"encoding/json"
"errors"
"fmt"
- "io/ioutil"
+ "io"
"log"
"os"
"path/filepath"
"time"
- "github.com/aws/aws-sdk-go/service/ec2"
- "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
+ "github.com/aws/aws-sdk-go-v2/service/ec2"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/mitchellh/go-homedir"
"github.com/patrickmn/go-cache"
)
@@ -35,7 +36,7 @@ var (
// Details hold all the information on an ec2 instance type
type Details struct {
- ec2.InstanceTypeInfo
+ ec2types.InstanceTypeInfo
OndemandPricePerHour *float64
SpotPrice *float64
}
@@ -45,11 +46,13 @@ type Provider struct {
DirectoryPath string
FullRefreshTTL time.Duration
lastFullRefresh *time.Time
- ec2Client ec2iface.EC2API
+ ec2Client ec2.DescribeInstanceTypesAPIClient
cache *cache.Cache
+ logger *log.Logger
}
-func NewProvider(directoryPath string, region string, ttl time.Duration, ec2Client ec2iface.EC2API) *Provider {
+// NewProvider creates a new Instance Types provider used to fetch Instance Type information from EC2
+func NewProvider(directoryPath string, region string, ttl time.Duration, ec2Client ec2.DescribeInstanceTypesAPIClient) *Provider {
expandedDirPath, err := homedir.Expand(directoryPath)
if err != nil {
log.Printf("Unable to expand instance type cache directory %s: %v", directoryPath, err)
@@ -60,10 +63,12 @@ func NewProvider(directoryPath string, region string, ttl time.Duration, ec2Clie
FullRefreshTTL: ttl,
ec2Client: ec2Client,
cache: cache.New(ttl, ttl),
+ logger: log.New(io.Discard, "", 0),
}
}
-func LoadFromOrNew(directoryPath string, region string, ttl time.Duration, ec2Client ec2iface.EC2API) *Provider {
+// NewProvider creates a new Instance Types provider used to fetch Instance Type information from EC2 and optionally cache
+func LoadFromOrNew(directoryPath string, region string, ttl time.Duration, ec2Client ec2.DescribeInstanceTypesAPIClient) *Provider {
expandedDirPath, err := homedir.Expand(directoryPath)
if err != nil {
log.Printf("Unable to load instance-type cache directory %s: %v", expandedDirPath, err)
@@ -86,6 +91,7 @@ func LoadFromOrNew(directoryPath string, region string, ttl time.Duration, ec2Cl
DirectoryPath: expandedDirPath,
ec2Client: ec2Client,
cache: itCache,
+ logger: log.New(io.Discard, "", 0),
}
}
@@ -106,36 +112,55 @@ func getCacheFilePath(region string, expandedDirPath string) string {
return filepath.Join(expandedDirPath, fmt.Sprintf("%s-%s", region, CacheFileName))
}
-func (p *Provider) Get(instanceTypes []string) ([]*Details, error) {
+func (p *Provider) SetLogger(logger *log.Logger) {
+ p.logger = logger
+}
+
+func (p *Provider) Get(ctx context.Context, instanceTypes []ec2types.InstanceType) ([]*Details, error) {
+ p.logger.Printf("Getting instance types %v", instanceTypes)
+ start := time.Now()
+ calls := 0
+ defer func() {
+ p.logger.Printf("Took %s and %d calls to collect Instance Types", time.Since(start), calls)
+ }()
instanceTypeDetails := []*Details{}
describeInstanceTypeOpts := &ec2.DescribeInstanceTypesInput{}
if len(instanceTypes) != 0 {
for _, it := range instanceTypes {
- if cachedIT, ok := p.cache.Get(it); ok {
+ if cachedIT, ok := p.cache.Get(string(it)); ok {
instanceTypeDetails = append(instanceTypeDetails, cachedIT.(*Details))
} else {
- // need to reassign so we're not sharing the loop iterators memory space
+ // need to reassign, so we're not sharing the loop iterators memory space
instanceType := it
- describeInstanceTypeOpts.InstanceTypes = append(describeInstanceTypeOpts.InstanceTypes, &instanceType)
+ describeInstanceTypeOpts.InstanceTypes = append(describeInstanceTypeOpts.InstanceTypes, instanceType)
}
}
+ // if we were able to retrieve all from cache, return here, else continue to do a remote lookup
+ if len(describeInstanceTypeOpts.InstanceTypes) == 0 {
+ return instanceTypeDetails, nil
+ }
} else if p.lastFullRefresh != nil && !p.isFullRefreshNeeded() {
for _, item := range p.cache.Items() {
instanceTypeDetails = append(instanceTypeDetails, item.Object.(*Details))
}
return instanceTypeDetails, nil
}
- if err := p.ec2Client.DescribeInstanceTypesPages(&ec2.DescribeInstanceTypesInput{}, func(page *ec2.DescribeInstanceTypesOutput, lastPage bool) bool {
- for _, instanceTypeInfo := range page.InstanceTypes {
- itDetails := &Details{InstanceTypeInfo: *instanceTypeInfo}
+
+ s := ec2.NewDescribeInstanceTypesPaginator(p.ec2Client, describeInstanceTypeOpts)
+
+ for s.HasMorePages() {
+ calls++
+ instanceTypeOutput, err := s.NextPage(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get next instance types page, %w", err)
+ }
+ for _, instanceTypeInfo := range instanceTypeOutput.InstanceTypes {
+ itDetails := &Details{InstanceTypeInfo: instanceTypeInfo}
instanceTypeDetails = append(instanceTypeDetails, itDetails)
- p.cache.SetDefault(*instanceTypeInfo.InstanceType, itDetails)
+ p.cache.SetDefault(string(instanceTypeInfo.InstanceType), itDetails)
}
- // continue paging through instance types
- return true
- }); err != nil {
- return instanceTypeDetails, err
}
+
if len(instanceTypes) == 0 {
now := time.Now().UTC()
p.lastFullRefresh = &now
@@ -158,8 +183,10 @@ func (p *Provider) Save() error {
if err != nil {
return err
}
- os.Mkdir(p.DirectoryPath, 0755)
- return ioutil.WriteFile(getCacheFilePath(p.Region, p.DirectoryPath), cacheBytes, 0644)
+ if err := os.Mkdir(p.DirectoryPath, 0755); err != nil && !errors.Is(err, os.ErrExist) {
+ return err
+ }
+ return os.WriteFile(getCacheFilePath(p.Region, p.DirectoryPath), cacheBytes, 0644)
}
func (p *Provider) Clear() error {
diff --git a/pkg/selector/aggregates.go b/pkg/selector/aggregates.go
index b17bf062..16e8a49f 100644
--- a/pkg/selector/aggregates.go
+++ b/pkg/selector/aggregates.go
@@ -1,12 +1,13 @@
package selector
import (
+ "context"
"fmt"
"regexp"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/service/ec2"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/aws-sdk-go-v2/service/ec2"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
const (
@@ -18,25 +19,27 @@ const (
// FiltersTransform can be implemented to provide custom transforms
type FiltersTransform interface {
- Transform(Filters) (Filters, error)
+ Transform(context.Context, Filters) (Filters, error)
}
// TransformFn is the func type definition for a FiltersTransform
-type TransformFn func(Filters) (Filters, error)
+type TransformFn func(context.Context, Filters) (Filters, error)
// Transform implements FiltersTransform interface on TransformFn
// This allows any TransformFn to be passed into funcs accepting FiltersTransform interface
-func (fn TransformFn) Transform(filters Filters) (Filters, error) {
- return fn(filters)
+func (fn TransformFn) Transform(ctx context.Context, filters Filters) (Filters, error) {
+ return fn(ctx, filters)
}
// TransformBaseInstanceType transforms lower level filters based on the instanceTypeBase specs
-func (itf Selector) TransformBaseInstanceType(filters Filters) (Filters, error) {
+func (itf Selector) TransformBaseInstanceType(ctx context.Context, filters Filters) (Filters, error) {
if filters.InstanceTypeBase == nil {
return filters, nil
}
- instanceTypesOutput, err := itf.EC2.DescribeInstanceTypes(&ec2.DescribeInstanceTypesInput{
- InstanceTypes: []*string{filters.InstanceTypeBase},
+ instanceTypesOutput, err := itf.EC2.DescribeInstanceTypes(ctx, &ec2.DescribeInstanceTypesInput{
+ InstanceTypes: []ec2types.InstanceType{
+ ec2types.InstanceType(*filters.InstanceTypeBase),
+ },
})
if err != nil {
return filters, err
@@ -49,18 +52,18 @@ func (itf Selector) TransformBaseInstanceType(filters Filters) (Filters, error)
filters.BareMetal = instanceTypeInfo.BareMetal
}
if filters.CPUArchitecture == nil && len(instanceTypeInfo.ProcessorInfo.SupportedArchitectures) == 1 {
- filters.CPUArchitecture = instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0]
+ filters.CPUArchitecture = &instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0]
}
if filters.Fpga == nil {
isFpgaSupported := instanceTypeInfo.FpgaInfo != nil
filters.Fpga = &isFpgaSupported
}
if filters.GpusRange == nil {
- gpuCount := 0
+ gpuCount := int32(0)
if instanceTypeInfo.GpuInfo != nil {
- gpuCount = int(*getTotalGpusCount(instanceTypeInfo.GpuInfo))
+ gpuCount = *getTotalGpusCount(instanceTypeInfo.GpuInfo)
}
- filters.GpusRange = &IntRangeFilter{LowerBound: gpuCount, UpperBound: gpuCount}
+ filters.GpusRange = &Int32RangeFilter{LowerBound: gpuCount, UpperBound: gpuCount}
}
if filters.MemoryRange == nil {
lowerBound := bytequantity.ByteQuantity{Quantity: uint64(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * AggregateLowPercentile)}
@@ -68,12 +71,12 @@ func (itf Selector) TransformBaseInstanceType(filters Filters) (Filters, error)
filters.MemoryRange = &ByteQuantityRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
}
if filters.VCpusRange == nil {
- lowerBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * AggregateLowPercentile)
- upperBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * AggregateHighPercentile)
- filters.VCpusRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
+ lowerBound := int32(float32(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * AggregateLowPercentile)
+ upperBound := int32(float32(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * AggregateHighPercentile)
+ filters.VCpusRange = &Int32RangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
}
if filters.VirtualizationType == nil && len(instanceTypeInfo.SupportedVirtualizationTypes) == 1 {
- filters.VirtualizationType = instanceTypeInfo.SupportedVirtualizationTypes[0]
+ filters.VirtualizationType = &instanceTypeInfo.SupportedVirtualizationTypes[0]
}
filters.InstanceTypeBase = nil
@@ -81,18 +84,21 @@ func (itf Selector) TransformBaseInstanceType(filters Filters) (Filters, error)
}
// TransformFlexible transforms lower level filters based on a set of opinions
-func (itf Selector) TransformFlexible(filters Filters) (Filters, error) {
+func (itf Selector) TransformFlexible(ctx context.Context, filters Filters) (Filters, error) {
if filters.Flexible == nil {
return filters, nil
}
if filters.CPUArchitecture == nil {
- filters.CPUArchitecture = aws.String("x86_64")
+ defaultArchitecture := ec2types.ArchitectureTypeX8664
+ filters.CPUArchitecture = &defaultArchitecture
}
if filters.BareMetal == nil {
- filters.BareMetal = aws.Bool(false)
+ bareMetalDefault := false
+ filters.BareMetal = &bareMetalDefault
}
if filters.Fpga == nil {
- filters.Fpga = aws.Bool(false)
+ fpgaDefault := false
+ filters.Fpga = &fpgaDefault
}
if filters.AllowList == nil {
@@ -104,14 +110,14 @@ func (itf Selector) TransformFlexible(filters Filters) (Filters, error) {
}
if filters.VCpusRange == nil && filters.MemoryRange == nil {
- defaultVcpus := 4
- filters.VCpusRange = &IntRangeFilter{LowerBound: defaultVcpus, UpperBound: defaultVcpus}
+ defaultVcpus := int32(4)
+ filters.VCpusRange = &Int32RangeFilter{LowerBound: defaultVcpus, UpperBound: defaultVcpus}
}
return filters, nil
}
// TransformForService transforms lower level filters based on the service
-func (itf Selector) TransformForService(filters Filters) (Filters, error) {
+func (itf Selector) TransformForService(ctx context.Context, filters Filters) (Filters, error) {
return itf.ServiceRegistry.ExecuteTransforms(filters)
}
diff --git a/pkg/selector/aggregates_test.go b/pkg/selector/aggregates_test.go
index 17691dde..84d5549f 100644
--- a/pkg/selector/aggregates_test.go
+++ b/pkg/selector/aggregates_test.go
@@ -14,10 +14,11 @@
package selector_test
import (
+ "context"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
)
// Tests
@@ -25,7 +26,6 @@ import (
func TestTransformBaseInstanceType(t *testing.T) {
ec2Mock := mockedEC2{
DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "c4_large.json").DescribeInstanceTypesResp,
- DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "25_instances.json").DescribeInstanceTypesPagesResp,
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
}
itf := selector.Selector{
@@ -35,7 +35,8 @@ func TestTransformBaseInstanceType(t *testing.T) {
filters := selector.Filters{
InstanceTypeBase: &instanceTypeBase,
}
- filters, err := itf.TransformBaseInstanceType(filters)
+ ctx := context.Background()
+ filters, err := itf.TransformBaseInstanceType(ctx, filters)
h.Ok(t, err)
h.Assert(t, *filters.BareMetal == false, " should filter out bare metal instances")
h.Assert(t, *filters.Fpga == false, "should filter out FPGA instances")
@@ -46,7 +47,6 @@ func TestTransformBaseInstanceType(t *testing.T) {
func TestTransformBaseInstanceTypeWithGPU(t *testing.T) {
ec2Mock := mockedEC2{
DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "g2_2xlarge.json").DescribeInstanceTypesResp,
- DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "g2_2xlarge_group.json").DescribeInstanceTypesPagesResp,
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
}
itf := selector.Selector{
@@ -56,7 +56,8 @@ func TestTransformBaseInstanceTypeWithGPU(t *testing.T) {
filters := selector.Filters{
InstanceTypeBase: &instanceTypeBase,
}
- filters, err := itf.TransformBaseInstanceType(filters)
+ ctx := context.Background()
+ filters, err := itf.TransformBaseInstanceType(ctx, filters)
h.Ok(t, err)
h.Assert(t, *filters.BareMetal == false, " should filter out bare metal instances")
h.Assert(t, *filters.Fpga == false, "should filter out FPGA instances")
@@ -70,7 +71,8 @@ func TestTransformFamilyFlexibile(t *testing.T) {
filters := selector.Filters{
Flexible: &flexible,
}
- filters, err := itf.TransformFlexible(filters)
+ ctx := context.Background()
+ filters, err := itf.TransformFlexible(ctx, filters)
h.Ok(t, err)
h.Assert(t, *filters.BareMetal == false, " should filter out bare metal instances")
h.Assert(t, *filters.Fpga == false, "should filter out FPGA instances")
diff --git a/pkg/selector/comparators.go b/pkg/selector/comparators.go
index d1070bae..94236bf8 100644
--- a/pkg/selector/comparators.go
+++ b/pkg/selector/comparators.go
@@ -14,14 +14,14 @@
package selector
import (
- "log"
"math"
+ "reflect"
"regexp"
"strconv"
"strings"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/service/ec2"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
const (
@@ -29,7 +29,11 @@ const (
required = "required"
)
-var amdRegex = regexp.MustCompile("[a-zA-Z0-9]+a\\.[a-zA-Z0-9]")
+var (
+ amdRegex = regexp.MustCompile(`[a-zA-Z0-9]+a\\.[a-zA-Z0-9]`)
+ networkPerfRE = regexp.MustCompile(`[0-9]+ Gigabit`)
+ generationRE = regexp.MustCompile(`[a-zA-Z]+([0-9]+)`)
+)
func isSupportedFromString(instanceTypeValue *string, target *string) bool {
if target == nil {
@@ -68,6 +72,106 @@ func isSupportedWithFloat64(instanceTypeValue *float64, target *float64) bool {
return math.Floor(*instanceTypeValue*100)/100 == math.Floor(*target*100)/100
}
+func isSupportedUsageClassType(instanceTypeValue []ec2types.UsageClassType, target *ec2types.UsageClassType) bool {
+ if target == nil {
+ return true
+ }
+ if instanceTypeValue == nil {
+ return false
+ }
+ if reflect.ValueOf(*target).IsZero() {
+ return true
+ }
+
+ for _, potentialType := range instanceTypeValue {
+ if potentialType == *target {
+ return true
+ }
+ }
+ return false
+}
+
+func isSupportedArchitectureType(instanceTypeValue []ec2types.ArchitectureType, target *ec2types.ArchitectureType) bool {
+ if target == nil {
+ return true
+ }
+ if instanceTypeValue == nil {
+ return false
+ }
+ if reflect.ValueOf(*target).IsZero() {
+ return true
+ }
+
+ for _, potentialType := range instanceTypeValue {
+ if potentialType == *target {
+ return true
+ }
+ }
+ return false
+}
+
+func isSupportedVirtualizationType(instanceTypeValue []ec2types.VirtualizationType, target *ec2types.VirtualizationType) bool {
+ if target == nil {
+ return true
+ }
+ if instanceTypeValue == nil {
+ return false
+ }
+ if reflect.ValueOf(*target).IsZero() {
+ return true
+ }
+ for _, potentialType := range instanceTypeValue {
+ if potentialType == *target {
+ return true
+ }
+ }
+ return false
+}
+
+func isSupportedInstanceTypeHypervisorType(instanceTypeValue ec2types.InstanceTypeHypervisor, target *ec2types.InstanceTypeHypervisor) bool {
+ if target == nil {
+ return true
+ }
+ if reflect.ValueOf(*target).IsZero() {
+ return true
+ }
+ if instanceTypeValue == *target {
+ return true
+ }
+ return false
+}
+
+func isSupportedRootDeviceType(instanceTypeValue []ec2types.RootDeviceType, target *ec2types.RootDeviceType) bool {
+ if target == nil {
+ return true
+ }
+ if instanceTypeValue == nil {
+ return false
+ }
+ if reflect.ValueOf(*target).IsZero() {
+ return true
+ }
+ for _, potentialType := range instanceTypeValue {
+ if potentialType == *target {
+ return true
+ }
+ }
+ return false
+}
+
+func isMatchingCpuArchitecture(instanceTypeValue CPUManufacturer, target *CPUManufacturer) bool {
+ if target == nil {
+ return true
+ }
+ if reflect.ValueOf(*target).IsZero() {
+ return true
+ }
+ if instanceTypeValue == *target {
+ return true
+ }
+ return false
+}
+
func isSupportedWithRangeInt64(instanceTypeValue *int64, target *IntRangeFilter) bool {
if target == nil {
return true
@@ -79,6 +183,17 @@ func isSupportedWithRangeInt64(instanceTypeValue *int64, target *IntRangeFilter)
return int(*instanceTypeValue) >= target.LowerBound && int(*instanceTypeValue) <= target.UpperBound
}
+func isSupportedWithRangeInt32(instanceTypeValue *int32, target *Int32RangeFilter) bool {
+ if target == nil {
+ return true
+ } else if instanceTypeValue == nil && target.LowerBound == 0 && target.UpperBound == 0 {
+ return true
+ } else if instanceTypeValue == nil {
+ return false
+ }
+ return *instanceTypeValue >= target.LowerBound && *instanceTypeValue <= target.UpperBound
+}
+
func isSupportedWithRangeUint64(instanceTypeValue *int64, target *Uint64RangeFilter) bool {
if target == nil {
return true
@@ -113,36 +228,36 @@ func isSupportedWithBool(instanceTypeValue *bool, target *bool) bool {
// Helper functions for aggregating data parsed from AWS API calls
-func getTotalAcceleratorsCount(acceleratorInfo *ec2.InferenceAcceleratorInfo) *int64 {
+func getTotalAcceleratorsCount(acceleratorInfo *ec2types.InferenceAcceleratorInfo) *int32 {
if acceleratorInfo == nil {
return nil
}
- total := aws.Int64(0)
+ total := int32(0)
for _, accel := range acceleratorInfo.Accelerators {
- total = aws.Int64(*total + *accel.Count)
+ total = total + *accel.Count
}
- return total
+ return &total
}
-func getTotalGpusCount(gpusInfo *ec2.GpuInfo) *int64 {
+func getTotalGpusCount(gpusInfo *ec2types.GpuInfo) *int32 {
if gpusInfo == nil {
return nil
}
- total := aws.Int64(0)
+ total := int32(0)
for _, gpu := range gpusInfo.Gpus {
- total = aws.Int64(*total + *gpu.Count)
+ total = total + *gpu.Count
}
- return total
+ return &total
}
-func getTotalGpuMemory(gpusInfo *ec2.GpuInfo) *int64 {
+func getTotalGpuMemory(gpusInfo *ec2types.GpuInfo) *int64 {
if gpusInfo == nil {
return nil
}
- return gpusInfo.TotalGpuMemoryInMiB
+ return aws.Int64(int64(*gpusInfo.TotalGpuMemoryInMiB))
}
-func getGPUManufacturers(gpusInfo *ec2.GpuInfo) []*string {
+func getGPUManufacturers(gpusInfo *ec2types.GpuInfo) []*string {
if gpusInfo == nil {
return nil
}
@@ -153,7 +268,7 @@ func getGPUManufacturers(gpusInfo *ec2.GpuInfo) []*string {
return manufacturers
}
-func getGPUModels(gpusInfo *ec2.GpuInfo) []*string {
+func getGPUModels(gpusInfo *ec2types.GpuInfo) []*string {
if gpusInfo == nil {
return nil
}
@@ -164,7 +279,7 @@ func getGPUModels(gpusInfo *ec2.GpuInfo) []*string {
return models
}
-func getInferenceAcceleratorManufacturers(acceleratorInfo *ec2.InferenceAcceleratorInfo) []*string {
+func getInferenceAcceleratorManufacturers(acceleratorInfo *ec2types.InferenceAcceleratorInfo) []*string {
if acceleratorInfo == nil {
return nil
}
@@ -175,7 +290,7 @@ func getInferenceAcceleratorManufacturers(acceleratorInfo *ec2.InferenceAccelera
return manufacturers
}
-func getInferenceAcceleratorModels(acceleratorInfo *ec2.InferenceAcceleratorInfo) []*string {
+func getInferenceAcceleratorModels(acceleratorInfo *ec2types.InferenceAcceleratorInfo) []*string {
if acceleratorInfo == nil {
return nil
}
@@ -190,12 +305,7 @@ func getNetworkPerformance(networkPerformance *string) *int {
if networkPerformance == nil {
return aws.Int(-1)
}
- re, err := regexp.Compile(`[0-9]+ Gigabit`)
- if err != nil {
- log.Printf("Unable to compile regexp to parse network performance: %s\n", *networkPerformance)
- return nil
- }
- networkBandwidth := re.FindString(*networkPerformance)
+ networkBandwidth := networkPerfRE.FindString(*networkPerformance)
if networkBandwidth == "" {
return aws.Int(-1)
}
@@ -210,69 +320,90 @@ func getNetworkPerformance(networkPerformance *string) *int {
return aws.Int(bandwidthNumber)
}
-func getInstanceStorage(instanceStorageInfo *ec2.InstanceStorageInfo) *int64 {
+func getInstanceStorage(instanceStorageInfo *ec2types.InstanceStorageInfo) *int64 {
if instanceStorageInfo == nil {
return aws.Int64(0)
}
return aws.Int64(*instanceStorageInfo.TotalSizeInGB * 1024)
}
-func getDiskType(instanceStorageInfo *ec2.InstanceStorageInfo) *string {
+func getDiskType(instanceStorageInfo *ec2types.InstanceStorageInfo) *string {
if instanceStorageInfo == nil || len(instanceStorageInfo.Disks) == 0 {
return nil
}
- return instanceStorageInfo.Disks[0].Type
+ return aws.String(string(instanceStorageInfo.Disks[0].Type))
}
-func getNVMESupport(instanceStorageInfo *ec2.InstanceStorageInfo, ebsInfo *ec2.EbsInfo) *bool {
+func getNVMESupport(instanceStorageInfo *ec2types.InstanceStorageInfo, ebsInfo *ec2types.EbsInfo) *bool {
if instanceStorageInfo != nil {
- return supportSyntaxToBool(instanceStorageInfo.NvmeSupport)
+ return supportSyntaxToBool(aws.String(string(instanceStorageInfo.NvmeSupport)))
}
if ebsInfo != nil {
- return supportSyntaxToBool(ebsInfo.EbsOptimizedSupport)
+ return supportSyntaxToBool(aws.String(string(ebsInfo.EbsOptimizedSupport)))
}
return aws.Bool(false)
}
-func getDiskEncryptionSupport(instanceStorageInfo *ec2.InstanceStorageInfo, ebsInfo *ec2.EbsInfo) *bool {
+func getDiskEncryptionSupport(instanceStorageInfo *ec2types.InstanceStorageInfo, ebsInfo *ec2types.EbsInfo) *bool {
if instanceStorageInfo != nil {
- return supportSyntaxToBool(instanceStorageInfo.EncryptionSupport)
+ encryptionSupport := string(instanceStorageInfo.EncryptionSupport)
+ return supportSyntaxToBool(&encryptionSupport)
}
if ebsInfo != nil {
- return supportSyntaxToBool(ebsInfo.EncryptionSupport)
+ ebsEncryptionSupport := string(ebsInfo.EncryptionSupport)
+ return supportSyntaxToBool(&ebsEncryptionSupport)
}
return aws.Bool(false)
}
-func getEBSOptimizedBaselineBandwidth(ebsInfo *ec2.EbsInfo) *int64 {
+func getEBSOptimizedBaselineBandwidth(ebsInfo *ec2types.EbsInfo) *int32 {
if ebsInfo == nil || ebsInfo.EbsOptimizedInfo == nil {
return nil
}
return ebsInfo.EbsOptimizedInfo.BaselineBandwidthInMbps
}
-func getEBSOptimizedBaselineThroughput(ebsInfo *ec2.EbsInfo) *float64 {
+func getEBSOptimizedBaselineThroughput(ebsInfo *ec2types.EbsInfo) *float64 {
if ebsInfo == nil || ebsInfo.EbsOptimizedInfo == nil {
return nil
}
return ebsInfo.EbsOptimizedInfo.BaselineThroughputInMBps
}
-func getEBSOptimizedBaselineIOPS(ebsInfo *ec2.EbsInfo) *int64 {
+func getEBSOptimizedBaselineIOPS(ebsInfo *ec2types.EbsInfo) *int32 {
if ebsInfo == nil || ebsInfo.EbsOptimizedInfo == nil {
return nil
}
return ebsInfo.EbsOptimizedInfo.BaselineIops
}
-func getCPUManufacturer(instanceTypeInfo *ec2.InstanceTypeInfo) *string {
- if contains(instanceTypeInfo.ProcessorInfo.SupportedArchitectures, ec2.ArchitectureTypeArm64) {
- return aws.String("aws")
+func getCPUManufacturer(instanceTypeInfo *ec2types.InstanceTypeInfo) CPUManufacturer {
+ for _, it := range instanceTypeInfo.ProcessorInfo.SupportedArchitectures {
+ if it == ec2types.ArchitectureTypeArm64 {
+ return CPUManufacturerAWS
+ }
+ }
+
+ if amdRegex.Match([]byte(instanceTypeInfo.InstanceType)) {
+ return CPUManufacturerAMD
+ }
+ return CPUManufacturerIntel
+}
+
+// getInstanceTypeGeneration returns the generation from an instance type name
+// i.e. c7i.xlarge -> 7
+// if any error occurs, 0 will be returned
+func getInstanceTypeGeneration(instanceTypeName string) *int {
+ zero := 0
+ matches := generationRE.FindStringSubmatch(instanceTypeName)
+ if len(matches) < 2 {
+ return &zero
}
- if amdRegex.Match([]byte(*instanceTypeInfo.InstanceType)) {
- return aws.String("amd")
+ gen, err := strconv.Atoi(matches[1])
+ if err != nil {
+ return &zero
}
- return aws.String("intel")
+ return &gen
}
// supportSyntaxToBool takes an instance spec field that uses ["unsupported", "supported", "required", or "default"]
@@ -287,7 +418,7 @@ func supportSyntaxToBool(instanceTypeSupport *string) *bool {
return aws.Bool(false)
}
-func calculateVCpusToMemoryRatio(vcpusVal *int64, memoryVal *int64) *float64 {
+func calculateVCpusToMemoryRatio(vcpusVal *int32, memoryVal *int64) *float64 {
if vcpusVal == nil || *vcpusVal == 0 || memoryVal == nil {
return nil
}
diff --git a/pkg/selector/comparators_internal_test.go b/pkg/selector/comparators_internal_test.go
index 444f8636..40beb1a6 100644
--- a/pkg/selector/comparators_internal_test.go
+++ b/pkg/selector/comparators_internal_test.go
@@ -17,8 +17,8 @@ import (
"math"
"testing"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
- "github.com/aws/aws-sdk-go/aws"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
+ "github.com/aws/aws-sdk-go-v2/aws"
)
func TestIsSupportedFromStrings_Supported(t *testing.T) {
@@ -261,22 +261,22 @@ func TestSupportSyntaxToBool_Nil(t *testing.T) {
}
func TestCalculateVCpusToMemoryRatio(t *testing.T) {
- vcpus := aws.Int64(4)
+ vcpus := aws.Int32(4)
memory := aws.Int64(4096)
ratio := calculateVCpusToMemoryRatio(vcpus, memory)
h.Assert(t, *ratio == 1.00, "ratio should equal 1:1")
- vcpus = aws.Int64(2)
+ vcpus = aws.Int32(2)
memory = aws.Int64(4096)
ratio = calculateVCpusToMemoryRatio(vcpus, memory)
h.Assert(t, *ratio == 2.00, "ratio should equal 1:2")
- vcpus = aws.Int64(1)
+ vcpus = aws.Int32(1)
memory = aws.Int64(512)
ratio = calculateVCpusToMemoryRatio(vcpus, memory)
h.Assert(t, *ratio == 1.0, "ratio should take the ceiling which equals 1:1")
- vcpus = aws.Int64(0)
+ vcpus = aws.Int32(0)
memory = aws.Int64(512)
ratio = calculateVCpusToMemoryRatio(vcpus, memory)
h.Assert(t, ratio == nil, "ratio should be nil when vcpus is 0")
@@ -287,7 +287,7 @@ func TestCalculateVCpusToMemoryRatio_Nil(t *testing.T) {
ratio := calculateVCpusToMemoryRatio(nil, memory)
h.Assert(t, ratio == nil, "nil vcpus should evaluate to nil")
- vcpus := aws.Int64(2)
+ vcpus := aws.Int32(2)
ratio = calculateVCpusToMemoryRatio(vcpus, nil)
h.Assert(t, ratio == nil, "nil memory should evaluate to nil")
diff --git a/pkg/selector/eks.go b/pkg/selector/eks.go
deleted file mode 100644
index 184131fc..00000000
--- a/pkg/selector/eks.go
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License"). You may
-// not use this file except in compliance with the License. A copy of the
-// License is located at
-//
-// http://aws.amazon.com/apache2.0/
-//
-// or in the "license" file accompanying this file. This file 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 selector
-
-import (
- "archive/zip"
- "bytes"
- "fmt"
- "io/ioutil"
- "log"
- "net/http"
- "strings"
-
- "github.com/aws/aws-sdk-go/aws"
-)
-
-const (
- eksAMIRepoURL = "https://github.com/awslabs/amazon-eks-ami"
- eksFallbackLatestAMIVersion = "v20210125"
- eksInstanceTypesFile = "eni-max-pods.txt"
-)
-
-// EKS is a Service type for a custom service filter transform
-type EKS struct {
- AMIRepoURL string
-}
-
-// Filters implements the Service interface contract for EKS
-func (e *EKS) Filters(version string) (Filters, error) {
- if e.AMIRepoURL == "" {
- e.AMIRepoURL = eksAMIRepoURL
- }
- var filters Filters
-
- if version == "" {
- var err error
- version, err = e.getLatestAMIVersion()
- if err != nil {
- log.Printf("There was a problem fetching the latest EKS AMI version, using hardcoded fallback version %s\n", eksFallbackLatestAMIVersion)
- version = eksFallbackLatestAMIVersion
- }
- }
- supportedInstanceTypes, err := e.getSupportedInstanceTypes(version)
- if err != nil {
- log.Printf("Unable to retrieve EKS supported instance types for version %s: %v", version, err)
- return filters, err
- }
- filters.InstanceTypes = &supportedInstanceTypes
- filters.VirtualizationType = aws.String("hvm")
- return filters, nil
-}
-
-func (e *EKS) getSupportedInstanceTypes(version string) ([]string, error) {
- supportedInstanceTypes := []string{}
- resp, err := http.Get(fmt.Sprintf("%s/archive/%s.zip", e.AMIRepoURL, version))
- if err != nil {
- return supportedInstanceTypes, err
- }
-
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return supportedInstanceTypes, fmt.Errorf("Unable to retrieve EKS supported instance types, got non-200 status code: %d", resp.StatusCode)
- }
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return supportedInstanceTypes, err
- }
-
- zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
- if err != nil {
- return supportedInstanceTypes, err
- }
-
- // Read all the files from zip archive
- for _, zipFile := range zipReader.File {
- filePathParts := strings.Split(zipFile.Name, "/")
- fileName := filePathParts[len(filePathParts)-1]
- if fileName == eksInstanceTypesFile {
- unzippedFileBytes, err := readZipFile(zipFile)
- if err != nil {
- log.Println(err)
- continue
- }
- supportedInstanceTypesFileBody := string(unzippedFileBytes)
- for _, line := range strings.Split(strings.Replace(supportedInstanceTypesFileBody, "\r\n", "\n", -1), "\n") {
- if !strings.HasPrefix(line, "#") {
- instanceType := strings.Split(line, " ")[0]
- supportedInstanceTypes = append(supportedInstanceTypes, instanceType)
- }
- }
- }
- }
- return supportedInstanceTypes, nil
-}
-
-func (e EKS) getLatestAMIVersion() (string, error) {
- client := &http.Client{
- CheckRedirect: func(req *http.Request, via []*http.Request) error {
- return http.ErrUseLastResponse
- },
- }
- // Get latest version
- resp, err := client.Get(fmt.Sprintf("%s/releases/latest", e.AMIRepoURL))
- if err != nil {
- return "", err
- }
- if resp.StatusCode != http.StatusFound {
- return "", fmt.Errorf("Can't retrieve latest release from github because redirect was not sent")
- }
- versionRedirect := resp.Header.Get("location")
- pathParts := strings.Split(versionRedirect, "/")
- return pathParts[len(pathParts)-1], nil
-}
-
-func readZipFile(zf *zip.File) ([]byte, error) {
- f, err := zf.Open()
- if err != nil {
- return nil, err
- }
- defer f.Close()
- return ioutil.ReadAll(f)
-}
diff --git a/pkg/selector/eks_test.go b/pkg/selector/eks_test.go
deleted file mode 100644
index 3da4380d..00000000
--- a/pkg/selector/eks_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License"). You may
-// not use this file except in compliance with the License. A copy of the
-// License is located at
-//
-// http://aws.amazon.com/apache2.0/
-//
-// or in the "license" file accompanying this file. This file 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 selector_test
-
-import (
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
-)
-
-const (
- githubStaticReleasesDir = "GithubEKSAMIRelease"
- githubReleaseVersion = "20210125"
- githubZipFileName = "amazon-eks-ami-20210125.zip"
-)
-
-// Tests
-
-func TestEKSDefaultService(t *testing.T) {
- ghServer := eksGithubReleaseHTTPServer(false, false)
- defer ghServer.Close()
-
- registry := selector.NewRegistry()
- registry.Register("eks", &selector.EKS{
- AMIRepoURL: ghServer.URL,
- })
-
- eks := "eks"
- filters := selector.Filters{
- Service: &eks,
- }
-
- transformedFilters, err := registry.ExecuteTransforms(filters)
- h.Ok(t, err)
- h.Assert(t, transformedFilters != filters, " Filters should have been modified")
- h.Assert(t, len(*transformedFilters.InstanceTypes) == 389, "389 instance types should be supported, but got %d", len(*transformedFilters.InstanceTypes))
- h.Assert(t, *transformedFilters.VirtualizationType == "hvm", "eks should only support hvm")
-
- eks = "eks-v" + githubReleaseVersion
- filters.Service = &eks
- transformedFilters, err = registry.ExecuteTransforms(filters)
- h.Ok(t, err)
- h.Assert(t, transformedFilters != filters, " Filters should have been modified")
- h.Assert(t, len(*transformedFilters.InstanceTypes) == 389, "389 instance types should be supported, but got %d", len(*transformedFilters.InstanceTypes))
- h.Assert(t, *transformedFilters.VirtualizationType == "hvm", "eks should only support hvm")
-}
-
-func TestEKSDefaultService_FailLatestReleaseUseFallbackStaticVersion(t *testing.T) {
- ghServer := eksGithubReleaseHTTPServer(true, false)
- defer ghServer.Close()
-
- registry := selector.NewRegistry()
- registry.Register("eks", &selector.EKS{
- AMIRepoURL: ghServer.URL,
- })
-
- eks := "eks"
- filters := selector.Filters{
- Service: &eks,
- }
-
- transformedFilters, err := registry.ExecuteTransforms(filters)
- h.Ok(t, err)
- h.Assert(t, transformedFilters != filters, " Filters should have been modified")
- h.Assert(t, len(*transformedFilters.InstanceTypes) == 389, "389 instance types should be supported, but got %d", len(*transformedFilters.InstanceTypes))
- h.Assert(t, *transformedFilters.VirtualizationType == "hvm", "eks should only support hvm")
-}
-
-func TestEKSDefaultService_FailLatestReleaseAndFailExactVersionLookup(t *testing.T) {
- ghServer := eksGithubReleaseHTTPServer(true, true)
- defer ghServer.Close()
-
- registry := selector.NewRegistry()
- registry.Register("eks", &selector.EKS{
- AMIRepoURL: ghServer.URL,
- })
-
- eks := "eks"
- filters := selector.Filters{
- Service: &eks,
- }
-
- _, err := registry.ExecuteTransforms(filters)
- h.Nok(t, err)
-}
-
-// Test Helpers Functions
-
-func eksGithubReleaseHTTPServer(failLatestRelease bool, failExactRelease bool) *httptest.Server {
- return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path == "/releases/latest" {
- if failLatestRelease {
- w.WriteHeader(404)
- return
- }
- w.Header().Add("location", "/releases/tag/v"+githubReleaseVersion)
- w.WriteHeader(302)
- return
- }
- if r.URL.Path == "/archive/v"+githubReleaseVersion+".zip" {
- if failExactRelease {
- w.WriteHeader(404)
- return
- }
- ghReleaseZipPath := fmt.Sprintf("%s/%s/%s", mockFilesPath, githubStaticReleasesDir, githubZipFileName)
- eksAMIReleaseZipFile, err := ioutil.ReadFile(ghReleaseZipPath)
- if err != nil {
- panic("Could not read EKS AMI release zip file")
- }
- w.Write(eksAMIReleaseZipFile)
- return
- }
- }))
-}
diff --git a/pkg/selector/emr.go b/pkg/selector/emr.go
index 116f7c49..212f16f6 100644
--- a/pkg/selector/emr.go
+++ b/pkg/selector/emr.go
@@ -17,7 +17,7 @@ import (
"fmt"
"strings"
- "github.com/aws/aws-sdk-go/aws"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/blang/semver/v4"
)
@@ -46,8 +46,10 @@ func (e EMR) Filters(version string) (Filters, error) {
return filters, err
}
filters.InstanceTypes = &instanceTypes
- filters.RootDeviceType = aws.String("ebs")
- filters.VirtualizationType = aws.String("hvm")
+ ebsType := ec2types.RootDeviceTypeEbs
+ filters.RootDeviceType = &ebsType
+ hvmType := ec2types.VirtualizationTypeHvm
+ filters.VirtualizationType = &hvmType
return filters, nil
}
diff --git a/pkg/selector/emr_test.go b/pkg/selector/emr_test.go
index 37f278e9..504951fa 100644
--- a/pkg/selector/emr_test.go
+++ b/pkg/selector/emr_test.go
@@ -16,8 +16,8 @@ package selector_test
import (
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
)
// Tests
diff --git a/pkg/selector/outputs/bubbletea.go b/pkg/selector/outputs/bubbletea.go
index 74076874..e1e82104 100644
--- a/pkg/selector/outputs/bubbletea.go
+++ b/pkg/selector/outputs/bubbletea.go
@@ -14,8 +14,8 @@
package outputs
import (
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/sorter"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/termenv"
diff --git a/pkg/selector/outputs/bubbletea_internal_test.go b/pkg/selector/outputs/bubbletea_internal_test.go
index 7384995d..e2f9f82d 100644
--- a/pkg/selector/outputs/bubbletea_internal_test.go
+++ b/pkg/selector/outputs/bubbletea_internal_test.go
@@ -16,12 +16,12 @@ package outputs
import (
"encoding/json"
"fmt"
- "io/ioutil"
+ "os"
"strings"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
"github.com/evertras/bubble-table/table"
)
@@ -36,7 +36,7 @@ const (
func getInstanceTypeDetails(t *testing.T, file string) []*instancetypes.Details {
folder := "FilterVerbose"
mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, folder, file)
- mockFile, err := ioutil.ReadFile(mockFilename)
+ mockFile, err := os.ReadFile(mockFilename)
h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename))
instanceTypes := []*instancetypes.Details{}
@@ -68,15 +68,6 @@ func TestNewBubbleTeaModel_Hypervisor(t *testing.T) {
actualHypervisor := rows[0].Data["Hypervisor"]
h.Assert(t, actualHypervisor == expectedHypervisor, fmt.Sprintf("Hypervisor should be %s but instead is %s", expectedHypervisor, actualHypervisor))
-
- // test nil Hypervisor
- instanceTypes[0].Hypervisor = nil
- model = NewBubbleTeaModel(instanceTypes)
- rows = model.tableModel.table.GetVisibleRows()
- expectedHypervisor = "none"
- actualHypervisor = rows[0].Data["Hypervisor"]
-
- h.Assert(t, actualHypervisor == expectedHypervisor, fmt.Sprintf("Hypervisor should be %s but instead is %s", expectedHypervisor, actualHypervisor))
}
func TestNewBubbleTeaModel_CPUArchitectures(t *testing.T) {
@@ -142,7 +133,7 @@ func TestNewBubbleTeaModel_SpotPricing(t *testing.T) {
model := NewBubbleTeaModel(instanceTypes)
rows := model.tableModel.table.GetVisibleRows()
expectedODPrice := "$1.368"
- actualODPrice := fmt.Sprintf("%v", rows[0].Data["Spot Price/Hr (30d avg)"])
+ actualODPrice := fmt.Sprintf("%v", rows[0].Data["Spot Price/Hr"])
h.Assert(t, actualODPrice == expectedODPrice, "Actual spot price should be %s, but is actually %s", expectedODPrice, actualODPrice)
@@ -151,7 +142,7 @@ func TestNewBubbleTeaModel_SpotPricing(t *testing.T) {
model = NewBubbleTeaModel(instanceTypes)
rows = model.tableModel.table.GetVisibleRows()
expectedODPrice = "-Not Fetched-"
- actualODPrice = fmt.Sprintf("%v", rows[0].Data["Spot Price/Hr (30d avg)"])
+ actualODPrice = fmt.Sprintf("%v", rows[0].Data["Spot Price/Hr"])
h.Assert(t, actualODPrice == expectedODPrice, "Actual spot price should be %s, but is actually %s", expectedODPrice, actualODPrice)
}
@@ -168,6 +159,6 @@ func TestNewBubbleTeaModel_Rows(t *testing.T) {
currInstanceName := instanceTypes[i].InstanceType
currRowName := rows[i].Data["Instance Type"]
- h.Assert(t, *currInstanceName == currRowName, "Rows should be in following order: %s. Actual order: [%s]", OneLineOutput(instanceTypes), getRowsInstances(rows))
+ h.Assert(t, string(currInstanceName) == currRowName, "Rows should be in following order: %s. Actual order: [%s]", OneLineOutput(instanceTypes), getRowsInstances(rows))
}
}
diff --git a/pkg/selector/outputs/outputs.go b/pkg/selector/outputs/outputs.go
index 468073d2..4fe1da4c 100644
--- a/pkg/selector/outputs/outputs.go
+++ b/pkg/selector/outputs/outputs.go
@@ -24,7 +24,7 @@ import (
"strings"
"text/tabwriter"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
)
const columnTag = "column"
@@ -33,26 +33,26 @@ const columnTag = "column"
// of a wide output row
type wideColumnsData struct {
instanceName string `column:"Instance Type"`
- vcpu int64 `column:"VCPUs"`
+ vcpu int32 `column:"VCPUs"`
memory string `column:"Mem (GiB)"`
hypervisor string `column:"Hypervisor"`
currentGen bool `column:"Current Gen"`
hibernationSupport bool `column:"Hibernation Support"`
cpuArch string `column:"CPU Arch"`
networkPerformance string `column:"Network Performance"`
- eni int64 `column:"ENIs"`
- gpu int64 `column:"GPUs"`
+ eni int32 `column:"ENIs"`
+ gpu int32 `column:"GPUs"`
gpuMemory string `column:"GPU Mem (GiB)"`
gpuInfo string `column:"GPU Info"`
odPrice string `column:"On-Demand Price/Hr"`
- spotPrice string `column:"Spot Price/Hr (30d avg)"`
+ spotPrice string `column:"Spot Price/Hr"`
}
// SimpleInstanceTypeOutput is an OutputFn which outputs a slice of instance type names
func SimpleInstanceTypeOutput(instanceTypeInfoSlice []*instancetypes.Details) []string {
instanceTypeStrings := []string{}
for _, instanceTypeInfo := range instanceTypeInfoSlice {
- instanceTypeStrings = append(instanceTypeStrings, *instanceTypeInfo.InstanceType)
+ instanceTypeStrings = append(instanceTypeStrings, string(instanceTypeInfo.InstanceType))
}
return instanceTypeStrings
}
@@ -97,7 +97,7 @@ func TableOutputShort(instanceTypeInfoSlice []*instancetypes.Details) []string {
for _, instanceTypeInfo := range instanceTypeInfoSlice {
fmt.Fprintf(w, "\n%s\t%d\t%s\t",
- *instanceTypeInfo.InstanceType,
+ instanceTypeInfo.InstanceType,
*instanceTypeInfo.VCpuInfo.DefaultVCpus,
formatFloat(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB)/1024.0),
)
@@ -161,7 +161,7 @@ func TableOutputWide(instanceTypeInfoSlice []*instancetypes.Details) []string {
func OneLineOutput(instanceTypeInfoSlice []*instancetypes.Details) []string {
instanceTypeNames := []string{}
for _, instanceType := range instanceTypeInfoSlice {
- instanceTypeNames = append(instanceTypeNames, *instanceType.InstanceType)
+ instanceTypeNames = append(instanceTypeNames, string(instanceType.InstanceType))
}
if len(instanceTypeNames) == 0 {
return []string{}
@@ -202,18 +202,14 @@ func getWideColumnsData(instanceTypes []*instancetypes.Details) []*wideColumnsDa
for _, instanceType := range instanceTypes {
none := "none"
- hyperisor := instanceType.Hypervisor
- if hyperisor == nil {
- hyperisor = &none
- }
cpuArchitectures := []string{}
for _, cpuArch := range instanceType.ProcessorInfo.SupportedArchitectures {
- cpuArchitectures = append(cpuArchitectures, *cpuArch)
+ cpuArchitectures = append(cpuArchitectures, string(cpuArch))
}
- gpus := int64(0)
- gpuMemory := int64(0)
+ gpus := int32(0)
+ gpuMemory := int32(0)
gpuType := []string{}
if instanceType.GpuInfo != nil {
gpuMemory = *instanceType.GpuInfo.TotalGpuMemoryInMiB
@@ -235,10 +231,10 @@ func getWideColumnsData(instanceTypes []*instancetypes.Details) []*wideColumnsDa
}
newColumn := wideColumnsData{
- instanceName: *instanceType.InstanceType,
+ instanceName: string(instanceType.InstanceType),
vcpu: *instanceType.VCpuInfo.DefaultVCpus,
memory: formatFloat(float64(*instanceType.MemoryInfo.SizeInMiB) / 1024.0),
- hypervisor: *hyperisor,
+ hypervisor: string(instanceType.Hypervisor),
currentGen: *instanceType.CurrentGeneration,
hibernationSupport: *instanceType.HibernationSupported,
cpuArch: strings.Join(cpuArchitectures, ", "),
diff --git a/pkg/selector/outputs/outputs_test.go b/pkg/selector/outputs/outputs_test.go
index 6f484fdd..e7b2a001 100644
--- a/pkg/selector/outputs/outputs_test.go
+++ b/pkg/selector/outputs/outputs_test.go
@@ -16,14 +16,14 @@ package outputs_test
import (
"encoding/json"
"fmt"
- "io/ioutil"
+ "os"
"strings"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector/outputs"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
- "github.com/aws/aws-sdk-go/service/ec2"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
+ "github.com/aws/aws-sdk-go-v2/service/ec2"
)
const (
@@ -33,7 +33,7 @@ const (
func getInstanceTypes(t *testing.T, file string) []*instancetypes.Details {
mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, describeInstanceTypes, file)
- mockFile, err := ioutil.ReadFile(mockFilename)
+ mockFile, err := os.ReadFile(mockFilename)
h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename))
dito := ec2.DescribeInstanceTypesOutput{}
err = json.Unmarshal(mockFile, &dito)
@@ -41,7 +41,7 @@ func getInstanceTypes(t *testing.T, file string) []*instancetypes.Details {
instanceTypesDetails := []*instancetypes.Details{}
for _, it := range dito.InstanceTypes {
odPrice := float64(0.53)
- instanceTypesDetails = append(instanceTypesDetails, &instancetypes.Details{InstanceTypeInfo: *it, OndemandPricePerHour: &odPrice})
+ instanceTypesDetails = append(instanceTypesDetails, &instancetypes.Details{InstanceTypeInfo: it, OndemandPricePerHour: &odPrice})
}
return instanceTypesDetails
}
diff --git a/pkg/selector/outputs/sortingView.go b/pkg/selector/outputs/sortingView.go
index 082a5ed0..f873a26c 100644
--- a/pkg/selector/outputs/sortingView.go
+++ b/pkg/selector/outputs/sortingView.go
@@ -18,8 +18,8 @@ import (
"io"
"strings"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/sorter"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/textinput"
@@ -91,8 +91,11 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
fn := listItemStyle.Render
if index == m.Index() {
- fn = func(s string) string {
- return selectedItemStyle.Render("> " + s)
+ fn = func(s ...string) string {
+ t := make([]string, 0, len(s)+1)
+ t = append(t, "> ")
+ t = append(t, s...)
+ return selectedItemStyle.Render(t...)
}
}
diff --git a/pkg/selector/outputs/tableView.go b/pkg/selector/outputs/tableView.go
index 07e52e81..4c337256 100644
--- a/pkg/selector/outputs/tableView.go
+++ b/pkg/selector/outputs/tableView.go
@@ -18,8 +18,8 @@ import (
"reflect"
"strings"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/sorter"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
@@ -393,7 +393,7 @@ func (m tableModel) sortTable(sortFilter string, sortDirection string) (tableMod
// get sorted rows from sorted instance types
rows := []table.Row{}
for _, instance := range instanceTypes {
- currRow := rowMap[*instance.InstanceType]
+ currRow := rowMap[string(instance.InstanceType)]
rows = append(rows, currRow)
}
@@ -431,7 +431,7 @@ func (m tableModel) getInstanceTypeFromRows() ([]*instancetypes.Details, map[str
}
instanceTypes = append(instanceTypes, currInstance)
- rowMap[*currInstance.InstanceType] = row
+ rowMap[string(currInstance.InstanceType)] = row
}
return instanceTypes, rowMap
diff --git a/pkg/selector/outputs/verboseView.go b/pkg/selector/outputs/verboseView.go
index 721e3a39..7201aaf4 100644
--- a/pkg/selector/outputs/verboseView.go
+++ b/pkg/selector/outputs/verboseView.go
@@ -18,7 +18,8 @@ import (
"math"
"strings"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
@@ -38,7 +39,7 @@ type verboseModel struct {
viewport viewport.Model
// the instance which the verbose output is focused on
- focusedInstanceName *string
+ focusedInstanceName ec2types.InstanceType
}
// styling for viewport
@@ -96,7 +97,7 @@ func (m verboseModel) view() string {
outputStr := strings.Builder{}
// format header for viewport
- instanceName := titleStyle.Render(*m.focusedInstanceName)
+ instanceName := titleStyle.Render(string(m.focusedInstanceName))
line := strings.Repeat("─", int(math.Max(0, float64(m.viewport.Width-lipgloss.Width(instanceName)))))
outputStr.WriteString(lipgloss.JoinHorizontal(lipgloss.Center, instanceName, line))
outputStr.WriteString("\n")
diff --git a/pkg/selector/selector.go b/pkg/selector/selector.go
index 9c8eb36d..1032dfee 100644
--- a/pkg/selector/selector.go
+++ b/pkg/selector/selector.go
@@ -17,6 +17,7 @@ package selector
import (
"context"
"fmt"
+ "io"
"log"
"reflect"
"regexp"
@@ -25,13 +26,13 @@ import (
"sync"
"time"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/ec2pricing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector/outputs"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/request"
- "github.com/aws/aws-sdk-go/aws/session"
- "github.com/aws/aws-sdk-go/service/ec2"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/ec2pricing"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/aws/middleware"
+ "github.com/aws/aws-sdk-go-v2/service/ec2"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"go.uber.org/multierr"
)
@@ -42,9 +43,9 @@ var (
const (
locationFilterKey = "location"
- zoneIDLocationType = "availability-zone-id"
- zoneNameLocationType = "availability-zone"
- regionNameLocationType = "region"
+ zoneIDLocationType = ec2types.LocationTypeAvailabilityZoneId
+ zoneNameLocationType = ec2types.LocationTypeAvailabilityZone
+ regionNameLocationType = ec2types.LocationTypeRegion
sdkName = "instance-selector"
// Filter Keys
@@ -91,86 +92,87 @@ const (
freeTier = "freeTier"
autoRecovery = "autoRecovery"
dedicatedHosts = "dedicatedHosts"
+ generation = "generation"
cpuArchitectureAMD64 = "amd64"
- cpuArchitectureX8664 = "x86_64"
- virtualizationTypeParaVirtual = "paravirtual"
- virtualizationTypePV = "pv"
+ virtualizationTypePV = "pv"
pricePerHour = "pricePerHour"
)
// New creates an instance of Selector provided an aws session
-func New(sess *session.Session) *Selector {
- serviceRegistry := NewRegistry()
- serviceRegistry.RegisterAWSServices()
- ec2Client := ec2.New(userAgentWith(sess))
- return &Selector{
- EC2: ec2Client,
- EC2Pricing: ec2pricing.New(sess),
- InstanceTypesProvider: instancetypes.LoadFromOrNew("", *sess.Config.Region, 0, ec2Client),
- ServiceRegistry: serviceRegistry,
- }
+func New(ctx context.Context, cfg aws.Config) (*Selector, error) {
+ return NewWithCache(ctx, cfg, 0, "")
}
-func NewWithCache(sess *session.Session, ttl time.Duration, cacheDir string) *Selector {
+// NewWithCache creates an instance of Selector backed by an on-disk cache provided an aws session and cache configuration parameters
+func NewWithCache(ctx context.Context, cfg aws.Config, ttl time.Duration, cacheDir string) (*Selector, error) {
serviceRegistry := NewRegistry()
serviceRegistry.RegisterAWSServices()
- ec2Client := ec2.New(userAgentWith(sess))
+ ec2Client := ec2.NewFromConfig(cfg, func(options *ec2.Options) {
+ options.APIOptions = append(options.APIOptions, middleware.AddUserAgentKeyValue(sdkName, versionID))
+ })
+ pricingClient, err := ec2pricing.NewWithCache(ctx, cfg, ttl, cacheDir)
+ if err != nil {
+ return nil, err
+ }
+
return &Selector{
EC2: ec2Client,
- EC2Pricing: ec2pricing.NewWithCache(sess, ttl, cacheDir),
- InstanceTypesProvider: instancetypes.LoadFromOrNew(cacheDir, *sess.Config.Region, ttl, ec2Client),
+ EC2Pricing: pricingClient,
+ InstanceTypesProvider: instancetypes.LoadFromOrNew(cacheDir, cfg.Region, ttl, ec2Client),
ServiceRegistry: serviceRegistry,
- }
+ Logger: log.New(io.Discard, "", 0),
+ }, nil
}
-func (itf Selector) Save() error {
- return multierr.Append(itf.EC2Pricing.Save(), itf.InstanceTypesProvider.Save())
+// SetLogger can be called to log more detailed logs about what selector is doing
+// including things like API timings
+// If SetLogger is not called, no logs will be displayed
+func (s *Selector) SetLogger(logger *log.Logger) {
+ s.Logger = logger
+ s.InstanceTypesProvider.SetLogger(logger)
+ s.EC2Pricing.SetLogger(logger)
+}
+
+// Save persists the selector cache data to disk if caching is configured
+func (s Selector) Save() error {
+ return multierr.Append(s.EC2Pricing.Save(), s.InstanceTypesProvider.Save())
}
// Filter accepts a Filters struct which is used to select the available instance types
// matching the criteria within Filters and returns a simple list of instance type strings
-//
-// Deprecated: This function will be replaced with GetFilteredInstanceTypes() and
-// OutputInstanceTypes() in the next major version.
-func (itf Selector) Filter(filters Filters) ([]string, error) {
+func (s Selector) Filter(ctx context.Context, filters Filters) ([]string, error) {
outputFn := InstanceTypesOutputFn(outputs.SimpleInstanceTypeOutput)
- output, _, err := itf.FilterWithOutput(filters, outputFn)
+ output, _, err := s.FilterWithOutput(ctx, filters, outputFn)
return output, err
}
// FilterVerbose accepts a Filters struct which is used to select the available instance types
// matching the criteria within Filters and returns a list instanceTypeInfo
-//
-// Deprecated: This function will be replaced with GetFilteredInstanceTypes() in the next
-// major version.
-func (itf Selector) FilterVerbose(filters Filters) ([]*instancetypes.Details, error) {
- instanceTypeInfoSlice, err := itf.rawFilter(filters)
+func (s Selector) FilterVerbose(ctx context.Context, filters Filters) ([]*instancetypes.Details, error) {
+ instanceTypeInfoSlice, err := s.rawFilter(ctx, filters)
if err != nil {
return nil, err
}
- instanceTypeInfoSlice, _ = itf.truncateResults(filters.MaxResults, instanceTypeInfoSlice)
+ instanceTypeInfoSlice, _ = s.truncateResults(filters.MaxResults, instanceTypeInfoSlice)
return instanceTypeInfoSlice, nil
}
// FilterWithOutput accepts a Filters struct which is used to select the available instance types
// matching the criteria within Filters and returns a list of strings based on the custom outputFn
-//
-// Deprecated: This function will be replaced with GetFilteredInstanceTypes() and
-// OutputInstanceTypes() in the next major version.
-func (itf Selector) FilterWithOutput(filters Filters, outputFn InstanceTypesOutput) ([]string, int, error) {
- instanceTypeInfoSlice, err := itf.rawFilter(filters)
+func (s Selector) FilterWithOutput(ctx context.Context, filters Filters, outputFn InstanceTypesOutput) ([]string, int, error) {
+ instanceTypeInfoSlice, err := s.rawFilter(ctx, filters)
if err != nil {
return nil, 0, err
}
- instanceTypeInfoSlice, numOfItemsTruncated := itf.truncateResults(filters.MaxResults, instanceTypeInfoSlice)
+ instanceTypeInfoSlice, numOfItemsTruncated := s.truncateResults(filters.MaxResults, instanceTypeInfoSlice)
output := outputFn.Output(instanceTypeInfoSlice)
return output, numOfItemsTruncated, nil
}
-func (itf Selector) truncateResults(maxResults *int, instanceTypeInfoSlice []*instancetypes.Details) ([]*instancetypes.Details, int) {
+func (s Selector) truncateResults(maxResults *int, instanceTypeInfoSlice []*instancetypes.Details) ([]*instancetypes.Details, int) {
if maxResults == nil {
return instanceTypeInfoSlice, 0
}
@@ -182,15 +184,15 @@ func (itf Selector) truncateResults(maxResults *int, instanceTypeInfoSlice []*in
}
// AggregateFilterTransform takes higher level filters which are used to affect multiple raw filters in an opinionated way.
-func (itf Selector) AggregateFilterTransform(filters Filters) (Filters, error) {
+func (s Selector) AggregateFilterTransform(ctx context.Context, filters Filters) (Filters, error) {
transforms := []FiltersTransform{
- TransformFn(itf.TransformBaseInstanceType),
- TransformFn(itf.TransformFlexible),
- TransformFn(itf.TransformForService),
+ TransformFn(s.TransformBaseInstanceType),
+ TransformFn(s.TransformFlexible),
+ TransformFn(s.TransformForService),
}
var err error
for _, transform := range transforms {
- filters, err = transform.Transform(filters)
+ filters, err = transform.Transform(ctx, filters)
if err != nil {
return filters, err
}
@@ -200,18 +202,18 @@ func (itf Selector) AggregateFilterTransform(filters Filters) (Filters, error) {
// rawFilter accepts a Filters struct which is used to select the available instance types
// matching the criteria within Filters and returns the detailed specs of matching instance types
-func (itf Selector) rawFilter(filters Filters) ([]*instancetypes.Details, error) {
- filters, err := itf.AggregateFilterTransform(filters)
+func (s Selector) rawFilter(ctx context.Context, filters Filters) ([]*instancetypes.Details, error) {
+ filters, err := s.AggregateFilterTransform(ctx, filters)
if err != nil {
return nil, err
}
var locations, availabilityZones []string
if filters.CPUArchitecture != nil && *filters.CPUArchitecture == cpuArchitectureAMD64 {
- *filters.CPUArchitecture = cpuArchitectureX8664
+ *filters.CPUArchitecture = ec2types.ArchitectureTypeX8664
}
if filters.VirtualizationType != nil && *filters.VirtualizationType == virtualizationTypePV {
- *filters.VirtualizationType = virtualizationTypeParaVirtual
+ *filters.VirtualizationType = ec2types.VirtualizationTypeParavirtual
}
if filters.AvailabilityZones != nil {
availabilityZones = *filters.AvailabilityZones
@@ -219,12 +221,12 @@ func (itf Selector) rawFilter(filters Filters) ([]*instancetypes.Details, error)
} else if filters.Region != nil {
locations = []string{*filters.Region}
}
- locationInstanceOfferings, err := itf.RetrieveInstanceTypesSupportedInLocations(locations)
+ locationInstanceOfferings, err := s.RetrieveInstanceTypesSupportedInLocations(ctx, locations)
if err != nil {
return nil, err
}
- instanceTypeDetails, err := itf.InstanceTypesProvider.Get(nil)
+ instanceTypeDetails, err := s.InstanceTypesProvider.Get(ctx, nil)
if err != nil {
return nil, err
}
@@ -235,42 +237,52 @@ func (itf Selector) rawFilter(filters Filters) ([]*instancetypes.Details, error)
wg.Add(1)
go func(instanceTypeInfo instancetypes.Details) {
defer wg.Done()
- it, err := itf.prepareFilter(filters, instanceTypeInfo, availabilityZones, locationInstanceOfferings)
+ it, err := s.prepareFilter(ctx, filters, instanceTypeInfo, availabilityZones, locationInstanceOfferings)
if err != nil {
- log.Println(err)
+ s.Logger.Printf("Unable to prepare filter for %s, %v", instanceTypeInfo.InstanceType, err)
}
if it != nil {
instanceTypes <- it
}
}(*instanceTypeInfo)
}
- wg.Wait()
- close(instanceTypes)
+ go func() {
+ 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
+func (s Selector) prepareFilter(ctx context.Context, filters Filters, instanceTypeInfo instancetypes.Details, availabilityZones []string, locationInstanceOfferings map[ec2types.InstanceType]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 s.EC2Pricing.OnDemandCacheCount() > 0 {
+ price, err := s.EC2Pricing.GetOnDemandInstanceTypeCost(ctx, instanceTypeName)
if err != nil {
- log.Printf("Could not retrieve instantaneous hourly on-demand price for instance type %s - %s\n", instanceTypeName, err)
+ s.Logger.Printf("Could not retrieve instantaneous hourly on-demand price for instance type %s - %s\n", instanceTypeName, err)
} else {
instanceTypeHourlyPriceOnDemand = &price
instanceTypeInfo.OndemandPricePerHour = instanceTypeHourlyPriceOnDemand
}
}
- if itf.EC2Pricing.SpotCacheCount() > 0 && contains(instanceTypeInfo.SupportedUsageClasses, "spot") {
- price, err := itf.EC2Pricing.GetSpotInstanceTypeNDayAvgCost(instanceTypeName, availabilityZones, 30)
+
+ isSpotUsageClass := false
+ for _, it := range instanceTypeInfo.SupportedUsageClasses {
+ if it == ec2types.UsageClassTypeSpot {
+ isSpotUsageClass = true
+ }
+ }
+
+ if s.EC2Pricing.SpotCacheCount() > 0 && isSpotUsageClass {
+ price, err := s.EC2Pricing.GetSpotInstanceTypeNDayAvgCost(ctx, instanceTypeName, availabilityZones, 30)
if err != nil {
- log.Printf("Could not retrieve 30 day avg hourly spot price for instance type %s\n", instanceTypeName)
+ s.Logger.Printf("Could not retrieve 30 day avg hourly spot price for instance type %s\n", instanceTypeName)
} else {
instanceTypeHourlyPriceSpot = &price
instanceTypeInfo.SpotPrice = instanceTypeHourlyPriceSpot
@@ -279,12 +291,14 @@ func (itf Selector) prepareFilter(filters Filters, instanceTypeInfo instancetype
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 {
+ if filters.UsageClass != nil && *filters.UsageClass == ec2types.UsageClassTypeSpot && instanceTypeHourlyPriceSpot != nil {
instanceTypeHourlyPriceForFilter = *instanceTypeHourlyPriceSpot
} else if instanceTypeHourlyPriceOnDemand != nil {
instanceTypeHourlyPriceForFilter = *instanceTypeHourlyPriceOnDemand
}
}
+ eneaSupport := string(instanceTypeInfo.NetworkInfo.EnaSupport)
+ ebsOptimizedSupport := string(instanceTypeInfo.EbsInfo.EbsOptimizedSupport)
// 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
@@ -304,7 +318,7 @@ func (itf Selector) prepareFilter(filters Filters, instanceTypeInfo instancetype
baremetal: {filters.BareMetal, instanceTypeInfo.BareMetal},
burstable: {filters.Burstable, instanceTypeInfo.BurstablePerformanceSupported},
fpga: {filters.Fpga, &isFpga},
- enaSupport: {filters.EnaSupport, supportSyntaxToBool(instanceTypeInfo.NetworkInfo.EnaSupport)},
+ enaSupport: {filters.EnaSupport, supportSyntaxToBool(&eneaSupport)},
efaSupport: {filters.EfaSupport, instanceTypeInfo.NetworkInfo.EfaSupported},
vcpusToMemoryRatio: {filters.VCpusToMemoryRatio, calculateVCpusToMemoryRatio(instanceTypeInfo.VCpuInfo.DefaultVCpus, instanceTypeInfo.MemoryInfo.SizeInMiB)},
currentGeneration: {filters.CurrentGeneration, instanceTypeInfo.CurrentGeneration},
@@ -318,7 +332,7 @@ func (itf Selector) prepareFilter(filters Filters, instanceTypeInfo instancetype
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)},
+ ebsOptimized: {filters.EBSOptimized, supportSyntaxToBool(&ebsOptimizedSupport)},
diskEncryption: {filters.DiskEncryption, getDiskEncryptionSupport(instanceTypeInfo.InstanceStorageInfo, instanceTypeInfo.EbsInfo)},
ebsOptimizedBaselineBandwidth: {filters.EBSOptimizedBaselineBandwidth, getEBSOptimizedBaselineBandwidth(instanceTypeInfo.EbsInfo)},
ebsOptimizedBaselineThroughput: {filters.EBSOptimizedBaselineThroughput, getEBSOptimizedBaselineThroughput(instanceTypeInfo.EbsInfo)},
@@ -330,6 +344,7 @@ func (itf Selector) prepareFilter(filters Filters, instanceTypeInfo instancetype
inferenceAcceleratorManufacturer: {filters.InferenceAcceleratorManufacturer, getInferenceAcceleratorManufacturers(instanceTypeInfo.InferenceAcceleratorInfo)},
inferenceAcceleratorModel: {filters.InferenceAcceleratorModel, getInferenceAcceleratorModels(instanceTypeInfo.InferenceAcceleratorInfo)},
dedicatedHosts: {filters.DedicatedHosts, instanceTypeInfo.DedicatedHostsSupported},
+ generation: {filters.Generation, getInstanceTypeGeneration(string(instanceTypeInfo.InstanceType))},
}
if isInDenyList(filters.DenyList, instanceTypeName) || !isInAllowList(filters.AllowList, instanceTypeName) {
@@ -341,7 +356,7 @@ func (itf Selector) prepareFilter(filters Filters, instanceTypeInfo instancetype
}
var isInstanceSupported bool
- isInstanceSupported, err := itf.executeFilters(filterToInstanceSpecMappingPairs, instanceTypeName)
+ isInstanceSupported, err := s.executeFilters(ctx, filterToInstanceSpecMappingPairs, instanceTypeName)
if err != nil {
return nil, err
}
@@ -359,17 +374,17 @@ func sortInstanceTypeInfo(instanceTypeInfoSlice []*instancetypes.Details) []*ins
sort.Slice(instanceTypeInfoSlice, func(i, j int) bool {
iInstanceInfo := instanceTypeInfoSlice[i]
jInstanceInfo := instanceTypeInfoSlice[j]
- return strings.Compare(aws.StringValue(iInstanceInfo.InstanceType), aws.StringValue(jInstanceInfo.InstanceType)) <= 0
+ return strings.Compare(string(iInstanceInfo.InstanceType), string(jInstanceInfo.InstanceType)) <= 0
})
return instanceTypeInfoSlice
}
// 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) {
- verdict := make(chan bool, len(filterToInstanceSpecMapping) + 1)
- errs := make(chan error)
- ctx, cancel := context.WithCancel(context.Background())
+func (s Selector) executeFilters(ctx context.Context, filterToInstanceSpecMapping map[string]filterPair, instanceType ec2types.InstanceType) (bool, error) {
+ verdict := make(chan bool, len(filterToInstanceSpecMapping)+1)
+ errs := make(chan error, len(filterToInstanceSpecMapping))
+ ctx, cancel := context.WithCancel(ctx)
defer cancel()
var wg sync.WaitGroup
for filterName, filter := range filterToInstanceSpecMapping {
@@ -410,17 +425,20 @@ func (itf Selector) executeFilters(filterToInstanceSpecMapping map[string]filter
}
}
-func exec(instanceType string, filterName string, filter filterPair) (bool, error) {
+// exec executes a specific filterPair (user value & instance spec) with a specific instance type
+// If the filterPair matches, true is returned
+func exec(instanceType ec2types.InstanceType, filterName string, filter filterPair) (bool, error) {
filterVal := filter.filterValue
instanceSpec := filter.instanceSpec
+ filterValReflection := reflect.ValueOf(filterVal)
// if filter is nil, user did not specify a filter, so skip evaluation
- if reflect.ValueOf(filterVal).IsNil() {
+ if filterValReflection.IsNil() {
return true, nil
}
instanceSpecType := reflect.ValueOf(instanceSpec).Type()
- filterType := reflect.ValueOf(filterVal).Type()
+ filterType := filterValReflection.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)
+ errInvalidInstanceSpec := fmt.Errorf("unable to process for %s", filterDetailsMsg)
// Determine appropriate filter comparator by switching on filter type
switch filter := filterVal.(type) {
@@ -435,7 +453,7 @@ func exec(instanceType string, filterName string, filter filterPair) (bool, erro
return false, nil
}
default:
- return false, fmt.Errorf(invalidInstanceSpecTypeMsg)
+ return false, errInvalidInstanceSpec
}
case *bool:
switch iSpec := instanceSpec.(type) {
@@ -444,7 +462,7 @@ func exec(instanceType string, filterName string, filter filterPair) (bool, erro
return false, nil
}
default:
- return false, fmt.Errorf(invalidInstanceSpecTypeMsg)
+ return false, errInvalidInstanceSpec
}
case *IntRangeFilter:
switch iSpec := instanceSpec.(type) {
@@ -457,7 +475,16 @@ func exec(instanceType string, filterName string, filter filterPair) (bool, erro
return false, nil
}
default:
- return false, fmt.Errorf(invalidInstanceSpecTypeMsg)
+ return false, errInvalidInstanceSpec
+ }
+ case *Int32RangeFilter:
+ switch iSpec := instanceSpec.(type) {
+ case *int32:
+ if !isSupportedWithRangeInt32(iSpec, filter) {
+ return false, nil
+ }
+ default:
+ return false, errInvalidInstanceSpec
}
case *Float64RangeFilter:
switch iSpec := instanceSpec.(type) {
@@ -466,7 +493,7 @@ func exec(instanceType string, filterName string, filter filterPair) (bool, erro
return false, nil
}
default:
- return false, fmt.Errorf(invalidInstanceSpecTypeMsg)
+ return false, errInvalidInstanceSpec
}
case *ByteQuantityRangeFilter:
mibRange := Uint64RangeFilter{
@@ -496,7 +523,7 @@ func exec(instanceType string, filterName string, filter filterPair) (bool, erro
return false, nil
}
default:
- return false, fmt.Errorf(invalidInstanceSpecTypeMsg)
+ return false, errInvalidInstanceSpec
}
case *float64:
switch iSpec := instanceSpec.(type) {
@@ -505,7 +532,61 @@ func exec(instanceType string, filterName string, filter filterPair) (bool, erro
return false, nil
}
default:
- return false, fmt.Errorf(invalidInstanceSpecTypeMsg)
+ return false, errInvalidInstanceSpec
+ }
+ case *ec2types.ArchitectureType:
+ switch iSpec := instanceSpec.(type) {
+ case []ec2types.ArchitectureType:
+ if !isSupportedArchitectureType(iSpec, filter) {
+ return false, nil
+ }
+ default:
+ return false, errInvalidInstanceSpec
+ }
+ case *ec2types.UsageClassType:
+ switch iSpec := instanceSpec.(type) {
+ case []ec2types.UsageClassType:
+ if !isSupportedUsageClassType(iSpec, filter) {
+ return false, nil
+ }
+ default:
+ return false, errInvalidInstanceSpec
+ }
+ case *CPUManufacturer:
+ switch iSpec := instanceSpec.(type) {
+ case CPUManufacturer:
+ if !isMatchingCpuArchitecture(iSpec, filter) {
+ return false, nil
+ }
+ default:
+ return false, errInvalidInstanceSpec
+ }
+ case *ec2types.VirtualizationType:
+ switch iSpec := instanceSpec.(type) {
+ case []ec2types.VirtualizationType:
+ if !isSupportedVirtualizationType(iSpec, filter) {
+ return false, nil
+ }
+ default:
+ return false, errInvalidInstanceSpec
+ }
+ case *ec2types.InstanceTypeHypervisor:
+ switch iSpec := instanceSpec.(type) {
+ case ec2types.InstanceTypeHypervisor:
+ if !isSupportedInstanceTypeHypervisorType(iSpec, filter) {
+ return false, nil
+ }
+ default:
+ return false, errInvalidInstanceSpec
+ }
+ case *ec2types.RootDeviceType:
+ switch iSpec := instanceSpec.(type) {
+ case []ec2types.RootDeviceType:
+ if !isSupportedRootDeviceType(iSpec, filter) {
+ return false, nil
+ }
+ default:
+ return false, errInvalidInstanceSpec
}
case *[]string:
switch iSpec := instanceSpec.(type) {
@@ -521,10 +602,10 @@ func exec(instanceType string, filterName string, filter filterPair) (bool, erro
return false, nil
}
default:
- return false, fmt.Errorf(invalidInstanceSpecTypeMsg)
+ return false, errInvalidInstanceSpec
}
default:
- return false, fmt.Errorf("No filter handler found for %s", filterDetailsMsg)
+ return false, fmt.Errorf("no filter handler found for %s", filterDetailsMsg)
}
return true, nil
}
@@ -532,41 +613,45 @@ func exec(instanceType string, filterName string, filter filterPair) (bool, erro
// RetrieveInstanceTypesSupportedInLocations returns a map of instance type -> AZ or Region for all instance types supported in the intersected locations passed in
// The location can be a zone-id (ie. use1-az1), a zone-name (us-east-1a), or a region name (us-east-1).
// Note that zone names are not necessarily the same across accounts
-func (itf Selector) RetrieveInstanceTypesSupportedInLocations(locations []string) (map[string]string, error) {
+func (s Selector) RetrieveInstanceTypesSupportedInLocations(ctx context.Context, locations []string) (map[ec2types.InstanceType]string, error) {
if len(locations) == 0 {
return nil, nil
}
- availableInstanceTypes := map[string]int{}
+ availableInstanceTypes := map[ec2types.InstanceType]int{}
for _, location := range locations {
+ locationType, err := s.getLocationType(ctx, location)
+ if err != nil {
+ return nil, err
+ }
+
instanceTypeOfferingsInput := &ec2.DescribeInstanceTypeOfferingsInput{
- Filters: []*ec2.Filter{
+ LocationType: locationType,
+ Filters: []ec2types.Filter{
{
Name: aws.String(locationFilterKey),
- Values: []*string{aws.String(location)},
+ Values: []string{location},
},
},
}
- locationType, err := itf.getLocationType(location)
- if err != nil {
- return nil, err
- }
- instanceTypeOfferingsInput.SetLocationType(locationType)
- err = itf.EC2.DescribeInstanceTypeOfferingsPages(instanceTypeOfferingsInput, func(page *ec2.DescribeInstanceTypeOfferingsOutput, lastPage bool) bool {
- for _, instanceType := range page.InstanceTypeOfferings {
- if i, ok := availableInstanceTypes[*instanceType.InstanceType]; !ok {
- availableInstanceTypes[*instanceType.InstanceType] = 1
+ p := ec2.NewDescribeInstanceTypeOfferingsPaginator(s.EC2, instanceTypeOfferingsInput)
+
+ for p.HasMorePages() {
+ instanceTypeOfferings, err := p.NextPage(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("encountered an error when describing instance type offerings: %w", err)
+ }
+
+ for _, instanceType := range instanceTypeOfferings.InstanceTypeOfferings {
+ if i, ok := availableInstanceTypes[instanceType.InstanceType]; !ok {
+ availableInstanceTypes[instanceType.InstanceType] = 1
} else {
- availableInstanceTypes[*instanceType.InstanceType] = i + 1
+ availableInstanceTypes[instanceType.InstanceType] = i + 1
}
}
- return true
- })
- if err != nil {
- return nil, fmt.Errorf("Encountered an error when describing instance type offerings: %w", err)
}
}
- availableInstanceTypesAllLocations := map[string]string{}
+ availableInstanceTypesAllLocations := map[ec2types.InstanceType]string{}
for instanceType, locationsSupported := range availableInstanceTypes {
if locationsSupported == len(locations) {
availableInstanceTypesAllLocations[instanceType] = ""
@@ -576,8 +661,8 @@ func (itf Selector) RetrieveInstanceTypesSupportedInLocations(locations []string
return availableInstanceTypesAllLocations, nil
}
-func (itf Selector) getLocationType(location string) (string, error) {
- azs, err := itf.EC2.DescribeAvailabilityZones(&ec2.DescribeAvailabilityZonesInput{})
+func (s Selector) getLocationType(ctx context.Context, location string) (ec2types.LocationType, error) {
+ azs, err := s.EC2.DescribeAvailabilityZones(ctx, &ec2.DescribeAvailabilityZonesInput{})
if err != nil {
return "", err
}
@@ -590,10 +675,10 @@ func (itf Selector) getLocationType(location string) (string, error) {
return zoneIDLocationType, nil
}
}
- return "", fmt.Errorf("The location passed in (%s) is not a valid zone-id, zone-name, or region name", location)
+ return "", fmt.Errorf("the location passed in (%s) is not a valid zone-id, zone-name, or region name", location)
}
-func isSupportedInLocation(instanceOfferings map[string]string, instanceType string) bool {
+func isSupportedInLocation(instanceOfferings map[ec2types.InstanceType]string, instanceType ec2types.InstanceType) bool {
if instanceOfferings == nil {
return true
}
@@ -601,22 +686,16 @@ func isSupportedInLocation(instanceOfferings map[string]string, instanceType str
return ok
}
-func isInDenyList(denyRegex *regexp.Regexp, instanceTypeName string) bool {
+func isInDenyList(denyRegex *regexp.Regexp, instanceTypeName ec2types.InstanceType) bool {
if denyRegex == nil {
return false
}
- return denyRegex.MatchString(instanceTypeName)
+ return denyRegex.MatchString(string(instanceTypeName))
}
-func isInAllowList(allowRegex *regexp.Regexp, instanceTypeName string) bool {
+func isInAllowList(allowRegex *regexp.Regexp, instanceTypeName ec2types.InstanceType) bool {
if allowRegex == nil {
return true
}
- return allowRegex.MatchString(instanceTypeName)
-}
-
-func userAgentWith(sess *session.Session) *session.Session {
- userAgentHandler := request.MakeAddToUserAgentFreeFormHandler(fmt.Sprintf("%s-%s", sdkName, versionID))
- sess.Handlers.Build.PushBack(userAgentHandler)
- return sess
+ return allowRegex.MatchString(string(instanceTypeName))
}
diff --git a/pkg/selector/selector_test.go b/pkg/selector/selector_test.go
index a270938d..16fdd838 100644
--- a/pkg/selector/selector_test.go
+++ b/pkg/selector/selector_test.go
@@ -14,26 +14,28 @@
package selector_test
import (
+ "context"
"encoding/json"
"errors"
"fmt"
- "io/ioutil"
+ "log"
+ "os"
"regexp"
"strconv"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
- "github.com/aws/aws-sdk-go/aws"
- "github.com/aws/aws-sdk-go/aws/session"
- "github.com/aws/aws-sdk-go/service/ec2"
- "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/awsapi"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "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"
)
const (
- describeInstanceTypesPages = "DescribeInstanceTypesPages"
describeInstanceTypes = "DescribeInstanceTypes"
describeInstanceTypeOfferings = "DescribeInstanceTypeOfferings"
describeAvailabilityZones = "DescribeAvailabilityZones"
@@ -41,43 +43,42 @@ const (
)
// Mocking helpers
-
-type itFn = func(page *ec2.DescribeInstanceTypesOutput, lastPage bool) bool
-type ioFn = func(page *ec2.DescribeInstanceTypeOfferingsOutput, lastPage bool) bool
-
type mockedEC2 struct {
- ec2iface.EC2API
- DescribeInstanceTypesPagesResp ec2.DescribeInstanceTypesOutput
- DescribeInstanceTypesPagesErr error
+ awsapi.SelectorInterface
DescribeInstanceTypesResp ec2.DescribeInstanceTypesOutput
+ DescribeInstanceTypesRespFn func(instanceType []ec2types.InstanceType) ec2.DescribeInstanceTypesOutput
DescribeInstanceTypesErr error
- DescribeInstanceTypeOfferingsRespFn func(zone string) *ec2.DescribeInstanceTypeOfferingsOutput
+ DescribeInstanceTypeOfferingsRespFn func(zone string) ec2.DescribeInstanceTypeOfferingsOutput
DescribeInstanceTypeOfferingsResp ec2.DescribeInstanceTypeOfferingsOutput
DescribeInstanceTypeOfferingsErr error
DescribeAvailabilityZonesResp ec2.DescribeAvailabilityZonesOutput
DescribeAvailabilityZonesErr error
}
-func (m mockedEC2) DescribeAvailabilityZones(input *ec2.DescribeAvailabilityZonesInput) (*ec2.DescribeAvailabilityZonesOutput, error) {
+func (m mockedEC2) DescribeAvailabilityZones(ctx context.Context, input *ec2.DescribeAvailabilityZonesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeAvailabilityZonesOutput, error) {
return &m.DescribeAvailabilityZonesResp, m.DescribeAvailabilityZonesErr
}
-func (m mockedEC2) DescribeInstanceTypes(input *ec2.DescribeInstanceTypesInput) (*ec2.DescribeInstanceTypesOutput, error) {
- return &m.DescribeInstanceTypesResp, m.DescribeInstanceTypesErr
-}
+func (m mockedEC2) DescribeInstanceTypes(ctx context.Context, input *ec2.DescribeInstanceTypesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypesOutput, error) {
+ var response ec2.DescribeInstanceTypesOutput
+ if m.DescribeInstanceTypesRespFn != nil {
+ response = m.DescribeInstanceTypesRespFn(input.InstanceTypes)
+ } else {
+ response = m.DescribeInstanceTypesResp
+ }
-func (m mockedEC2) DescribeInstanceTypesPages(input *ec2.DescribeInstanceTypesInput, fn itFn) error {
- fn(&m.DescribeInstanceTypesPagesResp, true)
- return m.DescribeInstanceTypesPagesErr
+ return &response, m.DescribeInstanceTypesErr
}
-func (m mockedEC2) DescribeInstanceTypeOfferingsPages(input *ec2.DescribeInstanceTypeOfferingsInput, fn ioFn) error {
+func (m mockedEC2) DescribeInstanceTypeOfferings(ctx context.Context, input *ec2.DescribeInstanceTypeOfferingsInput, optFns ...func(*ec2.Options)) (*ec2.DescribeInstanceTypeOfferingsOutput, error) {
+ var response ec2.DescribeInstanceTypeOfferingsOutput
if m.DescribeInstanceTypeOfferingsRespFn != nil {
- fn(m.DescribeInstanceTypeOfferingsRespFn(*input.Filters[0].Values[0]), true)
+ response = m.DescribeInstanceTypeOfferingsRespFn(input.Filters[0].Values[0])
} else {
- fn(&m.DescribeInstanceTypeOfferingsResp, true)
+ response = m.DescribeInstanceTypeOfferingsResp
}
- return m.DescribeInstanceTypeOfferingsErr
+
+ return &response, m.DescribeInstanceTypeOfferingsErr
}
func mockMultiRespDescribeInstanceTypesOfferings(t *testing.T, locationToFile map[string]string) mockedEC2 {
@@ -85,7 +86,7 @@ func mockMultiRespDescribeInstanceTypesOfferings(t *testing.T, locationToFile ma
locationToResp := map[string]ec2.DescribeInstanceTypeOfferingsOutput{}
for zone, file := range locationToFile {
mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, api, file)
- mockFile, err := ioutil.ReadFile(mockFilename)
+ mockFile, err := os.ReadFile(mockFilename)
h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename))
ditoo := ec2.DescribeInstanceTypeOfferingsOutput{}
err = json.Unmarshal(mockFile, &ditoo)
@@ -93,16 +94,16 @@ func mockMultiRespDescribeInstanceTypesOfferings(t *testing.T, locationToFile ma
locationToResp[zone] = ditoo
}
return mockedEC2{
- DescribeInstanceTypeOfferingsRespFn: func(input string) *ec2.DescribeInstanceTypeOfferingsOutput {
+ DescribeInstanceTypeOfferingsRespFn: func(input string) ec2.DescribeInstanceTypeOfferingsOutput {
resp := locationToResp[input]
- return &resp
+ return resp
},
}
}
func setupMock(t *testing.T, api string, file string) mockedEC2 {
mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, api, file)
- mockFile, err := ioutil.ReadFile(mockFilename)
+ mockFile, err := os.ReadFile(mockFilename)
h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename))
switch api {
case describeInstanceTypes:
@@ -112,13 +113,6 @@ func setupMock(t *testing.T, api string, file string) mockedEC2 {
return mockedEC2{
DescribeInstanceTypesResp: dito,
}
- case describeInstanceTypesPages:
- dito := ec2.DescribeInstanceTypesOutput{}
- err = json.Unmarshal(mockFile, &dito)
- h.Assert(t, err == nil, "Error parsing mock json file contents"+mockFilename)
- return mockedEC2{
- DescribeInstanceTypesPagesResp: dito,
- }
case describeInstanceTypeOfferings:
ditoo := ec2.DescribeInstanceTypeOfferingsOutput{}
err = json.Unmarshal(mockFile, &ditoo)
@@ -150,61 +144,67 @@ func getSelector(ec2Mock mockedEC2) selector.Selector {
// Tests
func TestNew(t *testing.T) {
- itf := selector.New(session.Must(session.NewSession()))
+ ctx := context.Background()
+ cfg, _ := config.LoadDefaultConfig(ctx)
+ itf, _ := selector.New(ctx, cfg)
h.Assert(t, itf != nil, "selector instance created without error")
}
func TestFilterVerbose(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 2, UpperBound: 2},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 2, UpperBound: 2},
}
- results, err := itf.FilterVerbose(filters)
+ ctx := context.Background()
+ results, err := itf.FilterVerbose(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, "Should only return 1 instance type with 2 vcpus but actually returned "+strconv.Itoa(len(results)))
- h.Assert(t, *results[0].InstanceType == "t3.micro", "Should return t3.micro, got %s instead", results[0].InstanceType)
+ h.Assert(t, results[0].InstanceType == "t3.micro", "Should return t3.micro, got %s instead", results[0].InstanceType)
}
func TestFilterVerbose_NoResults(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 4, UpperBound: 4},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 4, UpperBound: 4},
}
- results, err := itf.FilterVerbose(filters)
+ ctx := context.Background()
+ results, err := itf.FilterVerbose(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 0, "Should return 0 instance type with 4 vcpus")
}
func TestFilterVerbose_Failure(t *testing.T) {
- itf := getSelector(mockedEC2{DescribeInstanceTypesPagesErr: errors.New("error")})
+ ctx := context.Background()
+ itf := getSelector(mockedEC2{DescribeInstanceTypesErr: errors.New("error")})
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 4, UpperBound: 4},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 4, UpperBound: 4},
}
- results, err := itf.FilterVerbose(filters)
+ results, err := itf.FilterVerbose(ctx, filters)
h.Assert(t, results == nil, "Results should be nil")
h.Assert(t, err != nil, "An error should be returned")
}
func TestFilterVerbose_AZFilteredIn(t *testing.T) {
ec2Mock := mockedEC2{
- DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "t3_micro.json").DescribeInstanceTypesPagesResp,
+ DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "t3_micro.json").DescribeInstanceTypesResp,
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
DescribeAvailabilityZonesResp: setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp,
}
itf := getSelector(ec2Mock)
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 2, UpperBound: 2},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 2, UpperBound: 2},
AvailabilityZones: &[]string{"us-east-2a"},
}
- results, err := itf.FilterVerbose(filters)
+ ctx := context.Background()
+ results, err := itf.FilterVerbose(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, "Should only return 1 instance type with 2 vcpus but actually returned "+strconv.Itoa(len(results)))
- h.Assert(t, *results[0].InstanceType == "t3.micro", "Should return t3.micro, got %s instead", results[0].InstanceType)
+ h.Assert(t, results[0].InstanceType == "t3.micro", "Should return t3.micro, got %s instead", results[0].InstanceType)
}
func TestFilterVerbose_AZFilteredOut(t *testing.T) {
ec2Mock := mockedEC2{
- DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "t3_micro.json").DescribeInstanceTypesPagesResp,
+ DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "t3_micro.json").DescribeInstanceTypesResp,
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a_only_c5d12x.json").DescribeInstanceTypeOfferingsResp,
DescribeAvailabilityZonesResp: setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp,
}
@@ -212,7 +212,8 @@ func TestFilterVerbose_AZFilteredOut(t *testing.T) {
filters := selector.Filters{
AvailabilityZones: &[]string{"us-east-2a"},
}
- results, err := itf.FilterVerbose(filters)
+ ctx := context.Background()
+ results, err := itf.FilterVerbose(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 0, "Should return 0 instance types in us-east-2a but actually returned "+strconv.Itoa(len(results)))
}
@@ -220,88 +221,96 @@ func TestFilterVerbose_AZFilteredOut(t *testing.T) {
func TestFilterVerboseAZ_FilteredErr(t *testing.T) {
itf := getSelector(mockedEC2{})
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 2, UpperBound: 2},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 2, UpperBound: 2},
AvailabilityZones: &[]string{"blah"},
}
- _, err := itf.FilterVerbose(filters)
+ ctx := context.Background()
+ _, err := itf.FilterVerbose(ctx, filters)
h.Assert(t, err != nil, "Should error since bad zone was passed in")
}
func TestFilterVerbose_Gpus(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro_and_p3_16xl.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro_and_p3_16xl.json"))
gpuMemory, err := bytequantity.ParseToByteQuantity("128g")
h.Ok(t, err)
filters := selector.Filters{
- GpusRange: &selector.IntRangeFilter{LowerBound: 8, UpperBound: 8},
+ GpusRange: &selector.Int32RangeFilter{LowerBound: 8, UpperBound: 8},
GpuMemoryRange: &selector.ByteQuantityRangeFilter{
LowerBound: gpuMemory,
UpperBound: gpuMemory,
},
}
- results, err := itf.FilterVerbose(filters)
+ ctx := context.Background()
+ results, err := itf.FilterVerbose(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, "Should only return 1 instance type with 2 vcpus but actually returned "+strconv.Itoa(len(results)))
- h.Assert(t, *results[0].InstanceType == "p3.16xlarge", "Should return p3.16xlarge, got %s instead", *results[0].InstanceType)
+ h.Assert(t, results[0].InstanceType == "p3.16xlarge", "Should return p3.16xlarge, got %s instead", results[0].InstanceType)
}
func TestFilter(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 2, UpperBound: 2},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 2, UpperBound: 2},
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, "Should only return 1 instance type with 2 vcpus")
h.Assert(t, results[0] == "t3.micro", "Should return t3.micro, got %s instead", results[0])
}
func TestFilter_MoreFilters(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
+ X8664Architecture := ec2types.ArchitectureTypeX8664
+ NitroInstanceType := ec2types.InstanceTypeHypervisorNitro
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 2, UpperBound: 2},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 2, UpperBound: 2},
BareMetal: aws.Bool(false),
- CPUArchitecture: aws.String("x86_64"),
- Hypervisor: aws.String("nitro"),
+ CPUArchitecture: &X8664Architecture,
+ Hypervisor: &NitroInstanceType,
EnaSupport: aws.Bool(true),
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, "Should only return 1 instance type with 2 vcpus")
h.Assert(t, results[0] == "t3.micro", "Should return t3.micro, got %s instead", results[0])
}
func TestFilter_TruncateToMaxResults(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "25_instances.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "25_instances.json"))
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 0, UpperBound: 100},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 0, UpperBound: 100},
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) > 1, "Should return > 1 instance types since max results is not set")
filters = selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 0, UpperBound: 100},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 0, UpperBound: 100},
MaxResults: aws.Int(1),
}
- results, err = itf.Filter(filters)
+ results, err = itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, "Should return 1 instance types since max results is set")
filters = selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 0, UpperBound: 100},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 0, UpperBound: 100},
MaxResults: aws.Int(30),
}
- results, err = itf.Filter(filters)
+ results, err = itf.Filter(ctx, filters)
h.Ok(t, err)
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) {
- itf := getSelector(mockedEC2{DescribeInstanceTypesPagesErr: errors.New("error")})
+ itf := getSelector(mockedEC2{DescribeInstanceTypesErr: errors.New("error")})
filters := selector.Filters{
- VCpusRange: &selector.IntRangeFilter{LowerBound: 4, UpperBound: 4},
+ VCpusRange: &selector.Int32RangeFilter{LowerBound: 4, UpperBound: 4},
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Assert(t, results == nil, "Results should be nil")
h.Assert(t, err != nil, "An error should be returned")
}
@@ -310,7 +319,8 @@ func TestRetrieveInstanceTypesSupportedInAZ_WithZoneName(t *testing.T) {
ec2Mock := setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json")
ec2Mock.DescribeAvailabilityZonesResp = setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp
itf := getSelector(ec2Mock)
- results, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"us-east-2a"})
+ ctx := context.Background()
+ results, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"us-east-2a"})
h.Ok(t, err)
h.Assert(t, len(results) == 228, "Should return 228 entries in us-east-2a golden file w/ no resource filters applied")
}
@@ -319,7 +329,8 @@ func TestRetrieveInstanceTypesSupportedInAZ_WithZoneID(t *testing.T) {
ec2Mock := setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json")
ec2Mock.DescribeAvailabilityZonesResp = setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp
itf := getSelector(ec2Mock)
- results, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"use2-az1"})
+ ctx := context.Background()
+ results, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"use2-az1"})
h.Ok(t, err)
h.Assert(t, len(results) == 228, "Should return 228 entries in use2-az2 golden file w/ no resource filter applied")
}
@@ -328,7 +339,8 @@ func TestRetrieveInstanceTypesSupportedInAZ_WithRegion(t *testing.T) {
ec2Mock := setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json")
ec2Mock.DescribeAvailabilityZonesResp = setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp
itf := getSelector(ec2Mock)
- results, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"us-east-2"})
+ ctx := context.Background()
+ results, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"us-east-2"})
h.Ok(t, err)
h.Assert(t, len(results) == 228, "Should return 228 entries in us-east-2 golden file w/ no resource filter applied")
}
@@ -337,7 +349,8 @@ func TestRetrieveInstanceTypesSupportedInAZ_WithBadZone(t *testing.T) {
ec2Mock := setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json")
ec2Mock.DescribeAvailabilityZonesResp = setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp
itf := getSelector(ec2Mock)
- results, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"blah"})
+ ctx := context.Background()
+ results, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"blah"})
h.Assert(t, err != nil, "Should return an error since a bad zone was passed in")
h.Assert(t, results == nil, "Should return nil results due to error")
}
@@ -348,7 +361,8 @@ func TestRetrieveInstanceTypesSupportedInAZ_Error(t *testing.T) {
DescribeAvailabilityZonesResp: setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp,
}
itf := getSelector(ec2Mock)
- results, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"us-east-2a"})
+ ctx := context.Background()
+ results, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"us-east-2a"})
h.Assert(t, err != nil, "Should return an error since ec2 api mock is configured to return an error")
h.Assert(t, results == nil, "Should return nil results due to error")
}
@@ -359,7 +373,8 @@ func TestAggregateFilterTransform(t *testing.T) {
filters := selector.Filters{
InstanceTypeBase: &g22Xlarge,
}
- filters, err := itf.AggregateFilterTransform(filters)
+ ctx := context.Background()
+ filters, err := itf.AggregateFilterTransform(ctx, filters)
h.Ok(t, err)
h.Assert(t, filters.GpusRange != nil, "g2.2Xlarge as a base instance type should filter out non-GPU instances")
h.Assert(t, *filters.BareMetal == false, "g2.2Xlarge as a base instance type should filter out bare metal instances")
@@ -373,14 +388,20 @@ func TestAggregateFilterTransform_InvalidInstanceType(t *testing.T) {
filters := selector.Filters{
InstanceTypeBase: &t3Micro,
}
- _, err := itf.AggregateFilterTransform(filters)
+ ctx := context.Background()
+ _, err := itf.AggregateFilterTransform(ctx, filters)
h.Nok(t, err)
}
func TestFilter_InstanceTypeBase(t *testing.T) {
ec2Mock := mockedEC2{
- DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "c4_large.json").DescribeInstanceTypesResp,
- DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "25_instances.json").DescribeInstanceTypesPagesResp,
+ DescribeInstanceTypesRespFn: func(instanceTypes []ec2types.InstanceType) ec2.DescribeInstanceTypesOutput {
+ if len(instanceTypes) == 1 {
+ return setupMock(t, describeInstanceTypes, "c4_large.json").DescribeInstanceTypesResp
+ } else {
+ return setupMock(t, describeInstanceTypes, "25_instances.json").DescribeInstanceTypesResp
+ }
+ },
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
}
itf := getSelector(ec2Mock)
@@ -388,7 +409,8 @@ func TestFilter_InstanceTypeBase(t *testing.T) {
filters := selector.Filters{
InstanceTypeBase: &c4Large,
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 3, "c4.large should return 3 similar instance types")
}
@@ -400,12 +422,13 @@ func TestRetrieveInstanceTypesSupportedInAZs_Intersection(t *testing.T) {
})
ec2Mock.DescribeAvailabilityZonesResp = setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp
itf := getSelector(ec2Mock)
- results, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"us-east-2a", "us-east-2b"})
+ ctx := context.Background()
+ results, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"us-east-2a", "us-east-2b"})
h.Ok(t, err)
h.Assert(t, len(results) == 3, "Should return instance types that are included in both files")
// Check reversed zones to ensure order does not matter
- results, err = itf.RetrieveInstanceTypesSupportedInLocations([]string{"us-east-2b", "us-east-2a"})
+ results, err = itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"us-east-2b", "us-east-2a"})
h.Ok(t, err)
h.Assert(t, len(results) == 3, "Should return instance types that are included in both files when passed in reverse order")
}
@@ -416,7 +439,8 @@ func TestRetrieveInstanceTypesSupportedInAZs_Duplicates(t *testing.T) {
DescribeAvailabilityZonesResp: setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp,
}
itf := getSelector(ec2Mock)
- results, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"us-east-2b", "us-east-2b"})
+ ctx := context.Background()
+ results, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"us-east-2b", "us-east-2b"})
h.Ok(t, err)
h.Assert(t, len(results) == 3, "Should return instance types that are included in both files")
}
@@ -427,19 +451,21 @@ func TestRetrieveInstanceTypesSupportedInAZs_GoodAndBadZone(t *testing.T) {
DescribeAvailabilityZonesResp: setupMock(t, describeAvailabilityZones, "us-east-2.json").DescribeAvailabilityZonesResp,
}
itf := getSelector(ec2Mock)
- _, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"us-weast-2k", "us-east-2a"})
+ ctx := context.Background()
+ _, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"us-weast-2k", "us-east-2a"})
h.Nok(t, err)
}
func TestRetrieveInstanceTypesSupportedInAZs_DescribeAZErr(t *testing.T) {
itf := getSelector(mockedEC2{DescribeAvailabilityZonesErr: fmt.Errorf("error")})
- _, err := itf.RetrieveInstanceTypesSupportedInLocations([]string{"us-east-2a"})
+ ctx := context.Background()
+ _, err := itf.RetrieveInstanceTypesSupportedInLocations(ctx, []string{"us-east-2a"})
h.Nok(t, err)
}
func TestFilter_AllowList(t *testing.T) {
ec2Mock := mockedEC2{
- DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "25_instances.json").DescribeInstanceTypesPagesResp,
+ DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "25_instances.json").DescribeInstanceTypesResp,
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
}
itf := getSelector(ec2Mock)
@@ -448,14 +474,15 @@ func TestFilter_AllowList(t *testing.T) {
filters := selector.Filters{
AllowList: allowRegex,
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, "Allow List Regex: 'c4.large' should return 1 instance type")
}
func TestFilter_DenyList(t *testing.T) {
ec2Mock := mockedEC2{
- DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "25_instances.json").DescribeInstanceTypesPagesResp,
+ DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "25_instances.json").DescribeInstanceTypesResp,
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
}
itf := getSelector(ec2Mock)
@@ -464,14 +491,15 @@ func TestFilter_DenyList(t *testing.T) {
filters := selector.Filters{
DenyList: denyRegex,
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 24, "Deny List Regex: 'c4.large' should return 24 instance type matching regex but returned %d", len(results))
}
func TestFilter_AllowAndDenyList(t *testing.T) {
ec2Mock := mockedEC2{
- DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "25_instances.json").DescribeInstanceTypesPagesResp,
+ DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "25_instances.json").DescribeInstanceTypesResp,
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
}
itf := getSelector(ec2Mock)
@@ -483,35 +511,41 @@ func TestFilter_AllowAndDenyList(t *testing.T) {
AllowList: allowRegex,
DenyList: denyRegex,
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 4, "Allow/Deny List Regex: 'c4.large' should return 4 instance types matching the regex but returned %d", len(results))
}
func TestFilter_X8664_AMD64(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
+ ArchitectureType := selector.ArchitectureTypeAMD64
filters := selector.Filters{
- CPUArchitecture: aws.String("amd64"),
+ CPUArchitecture: &ArchitectureType,
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, "Should only return 1 instance type with x86_64/amd64 cpu architecture")
h.Assert(t, results[0] == "t3.micro", "Should return t3.micro, got %s instead", results[0])
}
func TestFilter_VirtType_PV(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "pv_instances.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "pv_instances.json"))
+ pvType := selector.VirtualizationTypePv
filters := selector.Filters{
- VirtualizationType: aws.String("pv"),
+ VirtualizationType: &pvType,
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) > 0, "Should return at least 1 instance type when filtering with VirtualizationType: pv")
+ paravirtualType := ec2types.VirtualizationTypeParavirtual
filters = selector.Filters{
- VirtualizationType: aws.String("paravirtual"),
+ VirtualizationType: ¶virtualType,
}
- results, err = itf.Filter(filters)
+ results, err = itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) > 0, "Should return at least 1 instance type when filtering with VirtualizationType: paravirtual")
}
@@ -527,19 +561,19 @@ type ec2PricingMock struct {
spotCacheCount int
}
-func (p *ec2PricingMock) GetOnDemandInstanceTypeCost(instanceType string) (float64, error) {
+func (p *ec2PricingMock) GetOnDemandInstanceTypeCost(ctx context.Context, instanceType ec2types.InstanceType) (float64, error) {
return p.GetOndemandInstanceTypeCostResp, p.GetOndemandInstanceTypeCostErr
}
-func (p *ec2PricingMock) GetSpotInstanceTypeNDayAvgCost(instanceType string, availabilityZones []string, days int) (float64, error) {
+func (p *ec2PricingMock) GetSpotInstanceTypeNDayAvgCost(ctx context.Context, instanceType ec2types.InstanceType, availabilityZones []string, days int) (float64, error) {
return p.GetSpotInstanceTypeNDayAvgCostResp, p.GetSpotInstanceTypeNDayAvgCostErr
}
-func (p *ec2PricingMock) RefreshOnDemandCache() error {
+func (p *ec2PricingMock) RefreshOnDemandCache(ctx context.Context) error {
return p.RefreshOnDemandCacheErr
}
-func (p *ec2PricingMock) RefreshSpotCache(days int) error {
+func (p *ec2PricingMock) RefreshSpotCache(ctx context.Context, days int) error {
return p.RefreshSpotCacheErr
}
@@ -554,9 +588,10 @@ func (p *ec2PricingMock) SpotCacheCount() int {
func (p *ec2PricingMock) Save() error {
return nil
}
+func (p *ec2PricingMock) SetLogger(_ *log.Logger) {}
func TestFilter_PricePerHour(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
itf.EC2Pricing = &ec2PricingMock{
GetOndemandInstanceTypeCostResp: 0.0104,
onDemandCacheCount: 1,
@@ -567,13 +602,14 @@ func TestFilter_PricePerHour(t *testing.T) {
UpperBound: 0.0104,
},
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, fmt.Sprintf("Should return 1 instance type; got %d", len(results)))
}
func TestFilter_PricePerHour_NoResults(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
itf.EC2Pricing = &ec2PricingMock{
GetOndemandInstanceTypeCostResp: 0.0104,
onDemandCacheCount: 1,
@@ -584,43 +620,48 @@ func TestFilter_PricePerHour_NoResults(t *testing.T) {
UpperBound: 0.0105,
},
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 0, "Should return 0 instance types")
}
func TestFilter_PricePerHour_OD(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
itf.EC2Pricing = &ec2PricingMock{
GetOndemandInstanceTypeCostResp: 0.0104,
onDemandCacheCount: 1,
}
+ onDemandUsage := ec2types.UsageClassTypeOnDemand
filters := selector.Filters{
PricePerHour: &selector.Float64RangeFilter{
LowerBound: 0.0104,
UpperBound: 0.0104,
},
- UsageClass: aws.String("on-demand"),
+ UsageClass: &onDemandUsage,
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, fmt.Sprintf("Should return 1 instance type; got %d", len(results)))
}
func TestFilter_PricePerHour_Spot(t *testing.T) {
- itf := getSelector(setupMock(t, describeInstanceTypesPages, "t3_micro.json"))
+ itf := getSelector(setupMock(t, describeInstanceTypes, "t3_micro.json"))
itf.EC2Pricing = &ec2PricingMock{
GetSpotInstanceTypeNDayAvgCostResp: 0.0104,
spotCacheCount: 1,
}
+ spotUsage := ec2types.UsageClassTypeSpot
filters := selector.Filters{
PricePerHour: &selector.Float64RangeFilter{
LowerBound: 0.0104,
UpperBound: 0.0104,
},
- UsageClass: aws.String("spot"),
+ UsageClass: &spotUsage,
}
- results, err := itf.Filter(filters)
+ ctx := context.Background()
+ results, err := itf.Filter(ctx, filters)
h.Ok(t, err)
h.Assert(t, len(results) == 1, fmt.Sprintf("Should return 1 instance type; got %d", len(results)))
}
diff --git a/pkg/selector/services.go b/pkg/selector/services.go
index b1ddf8af..c9d460ee 100644
--- a/pkg/selector/services.go
+++ b/pkg/selector/services.go
@@ -17,7 +17,7 @@ import (
"fmt"
"strings"
- "github.com/imdario/mergo"
+ "dario.cat/mergo"
)
// Service is used to write custom service filter transforms
@@ -59,14 +59,13 @@ func (sr *ServiceRegistry) Register(name string, service Service) {
// RegisterAWSServices registers the built-in AWS service filter transforms
func (sr *ServiceRegistry) RegisterAWSServices() {
- sr.Register("eks", &EKS{})
sr.Register("emr", &EMR{})
}
// ExecuteTransforms will execute the ServiceRegistry's registered service filter transforms
// Filters.Service will be parsed as - and passed to Service.Filters
func (sr *ServiceRegistry) ExecuteTransforms(filters Filters) (Filters, error) {
- if filters.Service == nil || *filters.Service == "" {
+ if filters.Service == nil || *filters.Service == "" || *filters.Service == "eks" {
return filters, nil
}
serviceAndVersion := strings.ToLower(*filters.Service)
diff --git a/pkg/selector/services_test.go b/pkg/selector/services_test.go
index 70a34311..cd14af9e 100644
--- a/pkg/selector/services_test.go
+++ b/pkg/selector/services_test.go
@@ -16,9 +16,9 @@ package selector_test
import (
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
- "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
+ "github.com/aws/aws-sdk-go-v2/aws"
)
// Tests
diff --git a/pkg/selector/types.go b/pkg/selector/types.go
index 95a634d6..23a017bd 100644
--- a/pkg/selector/types.go
+++ b/pkg/selector/types.go
@@ -15,12 +15,15 @@ package selector
import (
"encoding/json"
+ "log"
"regexp"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/bytequantity"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/ec2pricing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/awsapi"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
+
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/ec2pricing"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
)
// InstanceTypesOutput can be implemented to provide custom output to instance type results
@@ -39,10 +42,11 @@ func (fn InstanceTypesOutputFn) Output(instanceTypes []*instancetypes.Details) [
// Selector is used to filter instance type resource specs
type Selector struct {
- EC2 ec2iface.EC2API
+ EC2 awsapi.SelectorInterface
EC2Pricing ec2pricing.EC2PricingIface
InstanceTypesProvider *instancetypes.Provider
ServiceRegistry ServiceRegistry
+ Logger *log.Logger
}
// IntRangeFilter holds an upper and lower bound int
@@ -52,6 +56,13 @@ type IntRangeFilter struct {
LowerBound int
}
+// Int32RangeFilter holds an upper and lower bound int
+// The lower and upper bound are used to range filter resource specs
+type Int32RangeFilter struct {
+ UpperBound int32
+ LowerBound int32
+}
+
// Uint64RangeFilter holds an upper and lower bound uint64
// The lower and upper bound are used to range filter resource specs
type Uint64RangeFilter struct {
@@ -122,11 +133,10 @@ type Filters struct {
FreeTier *bool
// CPUArchitecture of the EC2 instance type
- // Possible values are: x86_64/amd64 or arm64
- CPUArchitecture *string
+ CPUArchitecture *ec2types.ArchitectureType
// CPUManufacturer is used to filter instance types with a specific CPU manufacturer
- CPUManufacturer *string
+ CPUManufacturer *CPUManufacturer
// CurrentGeneration returns the latest generation of instance types
CurrentGeneration *bool
@@ -141,7 +151,7 @@ type Filters struct {
Fpga *bool
// GpusRange filter is a range of acceptable GPU count available to an EC2 instance type
- GpusRange *IntRangeFilter
+ GpusRange *Int32RangeFilter
// 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
@@ -167,7 +177,7 @@ type Filters struct {
// Hypervisor is used to return only a specific hypervisor backed instance type
// Possibly values are: xen or nitro
- Hypervisor *string
+ Hypervisor *ec2types.InstanceTypeHypervisor
// MaxResults is the maximum number of instance types to return that match the filter criteria
MaxResults *int
@@ -176,7 +186,7 @@ type Filters struct {
MemoryRange *ByteQuantityRangeFilter
// NetworkInterfaces filter is a range of the number of ENI attachments an instance type can support
- NetworkInterfaces *IntRangeFilter
+ NetworkInterfaces *Int32RangeFilter
// NetworkPerformance filter is a range of network bandwidth an instance type can support
NetworkPerformance *IntRangeFilter
@@ -199,14 +209,14 @@ type Filters struct {
// RootDeviceType is the backing device of the root storage volume
// Possible values are: instance-store or ebs
- RootDeviceType *string
+ RootDeviceType *ec2types.RootDeviceType
// UsageClass of the instance EC2 instance type
// Possible values are: spot or on-demand
- UsageClass *string
+ UsageClass *ec2types.UsageClassType
// VCpusRange filter is a range of acceptable VCpus for the instance type
- VCpusRange *IntRangeFilter
+ VCpusRange *Int32RangeFilter
// VcpusToMemoryRatio is a ratio of vcpus to memory expressed as a floating point
VCpusToMemoryRatio *float64
@@ -232,7 +242,7 @@ type Filters struct {
InstanceTypes *[]string
// VirtualizationType is used to return instance types that match either hvm or pv virtualization types
- VirtualizationType *string
+ VirtualizationType *ec2types.VirtualizationType
// PricePerHour is used to return instance types that are equal to or cheaper than the specified price
PricePerHour *Float64RangeFilter
@@ -264,4 +274,41 @@ type Filters struct {
// DedicatedHosts filters on instance types that support dedicated hosts tenancy
DedicatedHosts *bool
+
+ // Generation filters on the instance type generation
+ // i.e. c7i.xlarge is 7
+ // NOTE that generation is only comparable per instance family
+ // For example, i3 and c5 are both 5th generation, but the Generation filter will
+ // only filter on the number in the instance type name.
+ Generation *IntRangeFilter
}
+
+type CPUManufacturer string
+
+// Enum values for CPUManufacturer
+const (
+ CPUManufacturerAWS CPUManufacturer = "aws"
+ CPUManufacturerAMD CPUManufacturer = "amd"
+ CPUManufacturerIntel CPUManufacturer = "intel"
+)
+
+// Values returns all known values for CPUManufacturer. Note that this can be
+// expanded in the future, and so it is only as up to date as the client. The
+// ordering of this slice is not guaranteed to be stable across updates.
+func (CPUManufacturer) Values() []CPUManufacturer {
+ return []CPUManufacturer{
+ CPUManufacturerAWS,
+ CPUManufacturerAMD,
+ CPUManufacturerIntel,
+ }
+}
+
+// ArchitectureTypeAMD64 is a legacy type we support for b/c that isn't in the API
+const (
+ ArchitectureTypeAMD64 ec2types.ArchitectureType = "amd64"
+)
+
+// ArchitectureTypeAMD64 is a legacy type we support for b/c that isn't in the API
+const (
+ VirtualizationTypePv ec2types.VirtualizationType = "pv"
+)
diff --git a/pkg/selector/types_test.go b/pkg/selector/types_test.go
index 5ad35a2e..c61bff8c 100644
--- a/pkg/selector/types_test.go
+++ b/pkg/selector/types_test.go
@@ -18,14 +18,15 @@ import (
"strings"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
+ ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
)
// Tests
func TestMarshalIndent(t *testing.T) {
- cpuArch := "x86_64"
+ cpuArch := ec2types.ArchitectureTypeX8664
allowRegex := "^abc$"
denyRegex := "^zyx$"
diff --git a/pkg/sorter/sorter.go b/pkg/sorter/sorter.go
index 829a74ca..418fc195 100644
--- a/pkg/sorter/sorter.go
+++ b/pkg/sorter/sorter.go
@@ -20,8 +20,7 @@ import (
"sort"
"strings"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
"github.com/oliveagle/jsonpath"
)
@@ -349,34 +348,34 @@ func (s *sorter) instanceTypes() []*instancetypes.Details {
// helper functions for special sorting fields
// getTotalGpusCount calculates the number of gpus in the given instance type
-func getTotalGpusCount(instanceType *instancetypes.Details) *int64 {
+func getTotalGpusCount(instanceType *instancetypes.Details) *int32 {
gpusInfo := instanceType.GpuInfo
if gpusInfo == nil {
return nil
}
- total := aws.Int64(0)
+ total := int32(0)
for _, gpu := range gpusInfo.Gpus {
- total = aws.Int64(*total + *gpu.Count)
+ total = total + *gpu.Count
}
- return total
+ return &total
}
// getTotalAcceleratorsCount calculates the total number of inference accelerators
// in the given instance type
-func getTotalAcceleratorsCount(instanceType *instancetypes.Details) *int64 {
+func getTotalAcceleratorsCount(instanceType *instancetypes.Details) *int32 {
acceleratorInfo := instanceType.InferenceAcceleratorInfo
if acceleratorInfo == nil {
return nil
}
- total := aws.Int64(0)
+ total := int32(0)
for _, accel := range acceleratorInfo.Accelerators {
- total = aws.Int64(*total + *accel.Count)
+ total = total + *accel.Count
}
- return total
+ return &total
}
diff --git a/pkg/sorter/sorter_test.go b/pkg/sorter/sorter_test.go
index 776cf85c..25e8cfa8 100644
--- a/pkg/sorter/sorter_test.go
+++ b/pkg/sorter/sorter_test.go
@@ -16,14 +16,14 @@ package sorter_test
import (
"encoding/json"
"fmt"
- "io/ioutil"
+ "os"
"strings"
"testing"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/instancetypes"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector/outputs"
- "github.com/aws/amazon-ec2-instance-selector/v2/pkg/sorter"
- h "github.com/aws/amazon-ec2-instance-selector/v2/pkg/test"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs"
+ "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter"
+ h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test"
)
const (
@@ -38,7 +38,7 @@ const (
func getInstanceTypeDetails(t *testing.T, file string) []*instancetypes.Details {
folder := "FilterVerbose"
mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, folder, file)
- mockFile, err := ioutil.ReadFile(mockFilename)
+ mockFile, err := os.ReadFile(mockFilename)
h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename))
instanceTypes := []*instancetypes.Details{}
@@ -59,7 +59,7 @@ func checkSortResults(instanceTypes []*instancetypes.Details, expectedResult []s
actualName := instanceTypes[i].InstanceTypeInfo.InstanceType
expectedName := expectedResult[i]
- if actualName == nil || *actualName != expectedName {
+ if string(actualName) != expectedName {
return false
}
}
diff --git a/test/e2e/run-test b/test/e2e/run-test
index 74b6f5dd..23206884 100755
--- a/test/e2e/run-test
+++ b/test/e2e/run-test
@@ -93,7 +93,7 @@ params=(
)
echo "${expected[*]}" | execute_test "24 VCPUs" "${params[@]}"
-expected=(g2.8xlarge g3.16xlarge g4dn.12xlarge p3.8xlarge)
+expected=(g3.16xlarge g4ad.16xlarge g4dn.12xlarge g5.12xlarge g5.24xlarge g6.12xlarge g6.24xlarge g6e.12xlarge g6e.24xlarge p3.8xlarge)
params=(
"--gpus=4"
"--gpus-min=4 --gpus-max=4"
diff --git a/test/license-test/Dockerfile b/test/license-test/Dockerfile
index 2b346b02..08ed3f92 100644
--- a/test/license-test/Dockerfile
+++ b/test/license-test/Dockerfile
@@ -1,4 +1,4 @@
-FROM public.ecr.aws/docker/library/golang:1.17
+FROM public.ecr.aws/docker/library/golang:1.23
WORKDIR /app
diff --git a/test/readme-test/readme-codeblocks.go b/test/readme-test/readme-codeblocks.go
index 32cea4ab..0fadb5fe 100644
--- a/test/readme-test/readme-codeblocks.go
+++ b/test/readme-test/readme-codeblocks.go
@@ -5,7 +5,6 @@ import (
"encoding/json"
"flag"
"fmt"
- "io/ioutil"
"log"
"os"
"strings"
@@ -59,7 +58,7 @@ func main() {
}
func compareBlockWithFile(codeBlock string, codePath string) bool {
- fileContents, err := ioutil.ReadFile(codePath)
+ fileContents, err := os.ReadFile(codePath)
if err != nil {
log.Fatalf("Unable to read file contents at %s", codePath)
}
diff --git a/test/readme-test/run-readme-codeblocks b/test/readme-test/run-readme-codeblocks
index b58bb523..ebdf1181 100755
--- a/test/readme-test/run-readme-codeblocks
+++ b/test/readme-test/run-readme-codeblocks
@@ -22,8 +22,8 @@ interpreters=($(echo $rundoc_output | jq -r '.code_blocks[] | .interpreter'))
## Execute --help check which compares the help codeblock in the README to the actual output of the binary
rd list-blocks -T "bash#help" /aeis/README.md | jq -r '.code_blocks[0] .code' > $BUILD_DIR/readme_help.out
-docker run -t --rm codeblocks build/ec2-instance-selector --help > $BUILD_DIR/actual_help.out
-diff --ignore-all-space --ignore-blank-lines "$BUILD_DIR/actual_help.out" "$BUILD_DIR/readme_help.out"
+docker run -t --rm codeblocks build/ec2-instance-selector --help | perl -pe 's/\e\[?.*?[a-zA-Z]//g' > $BUILD_DIR/actual_help.out
+diff --ignore-all-space --ignore-blank-lines --ignore-trailing-space "$BUILD_DIR/actual_help.out" "$BUILD_DIR/readme_help.out"
echo "✅ README help section matches actual binary output!"
## Execute go codeblocks example tests which checks the go codeblocks in the readme with a source file path
@@ -35,7 +35,7 @@ for i in "${!example_files[@]}"; do
example_file="${example_files[$i]}"
example_bin=$(echo $example_file | cut -d'.' -f1)
mkdir -p $BUILD_DIR/examples
- docker run -i -e GOOS=$OS -e GOARCH=amd64 -v $BUILD_DIR:/amazon-ec2-instance-selector/build --rm codeblocks go build -o build/examples/$example_bin $example_file
+ docker run -i -e GOOS=$OS -e GOARCH=amd64 -e CGO_ENABLED=0 -v $BUILD_DIR:/amazon-ec2-instance-selector/build --rm codeblocks go build -o build/examples/$example_bin $example_file
$BUILD_DIR/examples/$example_bin
echo "✅ $example_file Executed Successfully!"
fi
diff --git a/test/readme-test/spellcheck-Dockerfile b/test/readme-test/spellcheck-Dockerfile
index cd36641b..974f58ae 100644
--- a/test/readme-test/spellcheck-Dockerfile
+++ b/test/readme-test/spellcheck-Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.16
+FROM golang:1.23
RUN go install github.com/client9/misspell/cmd/misspell@v0.3.4
diff --git a/test/static/DescribeInstanceTypesPages/g2_2xlarge_group.json b/test/static/DescribeInstanceTypes/g2_2xlarge_group.json
similarity index 100%
rename from test/static/DescribeInstanceTypesPages/g2_2xlarge_group.json
rename to test/static/DescribeInstanceTypes/g2_2xlarge_group.json
diff --git a/test/static/DescribeInstanceTypesPages/pv_instances.json b/test/static/DescribeInstanceTypes/pv_instances.json
similarity index 100%
rename from test/static/DescribeInstanceTypesPages/pv_instances.json
rename to test/static/DescribeInstanceTypes/pv_instances.json
diff --git a/test/static/DescribeInstanceTypesPages/25_instances.json b/test/static/DescribeInstanceTypesPages/25_instances.json
deleted file mode 100644
index 45ca66a8..00000000
--- a/test/static/DescribeInstanceTypesPages/25_instances.json
+++ /dev/null
@@ -1,1736 +0,0 @@
-{
- "InstanceTypes": [
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "a1.2xlarge",
- "MemoryInfo": {
- "SizeInMiB": 16384
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 15,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "arm64"
- ],
- "SustainedClockSpeedInGhz": 2.3
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": 8,
- "DefaultThreadsPerCore": 1,
- "DefaultVCpus": 8,
- "ValidCores": [
- 8
- ],
- "ValidThreadsPerCore": [
- 1
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "a1.4xlarge",
- "MemoryInfo": {
- "SizeInMiB": 32768
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "arm64"
- ],
- "SustainedClockSpeedInGhz": 2.3
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": 16,
- "DefaultThreadsPerCore": 1,
- "DefaultVCpus": 16,
- "ValidCores": [
- 16
- ],
- "ValidThreadsPerCore": [
- 1
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "a1.large",
- "MemoryInfo": {
- "SizeInMiB": 4096
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 10,
- "Ipv6AddressesPerInterface": 10,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 3,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "arm64"
- ],
- "SustainedClockSpeedInGhz": 2.3
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": 2,
- "DefaultThreadsPerCore": 1,
- "DefaultVCpus": 2,
- "ValidCores": [
- 2
- ],
- "ValidThreadsPerCore": [
- 1
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "a1.medium",
- "MemoryInfo": {
- "SizeInMiB": 2048
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 4,
- "Ipv6AddressesPerInterface": 4,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 2,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "arm64"
- ],
- "SustainedClockSpeedInGhz": 2.3
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": 1,
- "DefaultThreadsPerCore": 1,
- "DefaultVCpus": 1,
- "ValidCores": [
- 1
- ],
- "ValidThreadsPerCore": [
- 1
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": true,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": null,
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "a1.metal",
- "MemoryInfo": {
- "SizeInMiB": 32768
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "arm64"
- ],
- "SustainedClockSpeedInGhz": 2.3
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": null,
- "DefaultThreadsPerCore": null,
- "DefaultVCpus": 16,
- "ValidCores": null,
- "ValidThreadsPerCore": null
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "a1.xlarge",
- "MemoryInfo": {
- "SizeInMiB": 8192
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 15,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "arm64"
- ],
- "SustainedClockSpeedInGhz": 2.3
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": 4,
- "DefaultThreadsPerCore": 1,
- "DefaultVCpus": 4,
- "ValidCores": [
- 4
- ],
- "ValidThreadsPerCore": [
- 1
- ]
- }
- },
- {
- "AutoRecoverySupported": false,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": false,
- "DedicatedHostsSupported": false,
- "EbsInfo": {
- "EbsOptimizedSupport": "unsupported",
- "EncryptionSupport": "unsupported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": {
- "Disks": [
- {
- "Count": 1,
- "SizeInGB": 350,
- "Type": "hdd"
- }
- ],
- "TotalSizeInGB": 350
- },
- "InstanceStorageSupported": true,
- "InstanceType": "c1.medium",
- "MemoryInfo": {
- "SizeInMiB": 1740
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 6,
- "Ipv6AddressesPerInterface": 0,
- "Ipv6Supported": false,
- "MaximumNetworkInterfaces": 2,
- "NetworkPerformance": "Moderate"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "i386",
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": null
- },
- "SupportedRootDeviceTypes": [
- "ebs",
- "instance-store"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": 2,
- "DefaultThreadsPerCore": 1,
- "DefaultVCpus": 2,
- "ValidCores": [
- 1,
- 2
- ],
- "ValidThreadsPerCore": [
- 1
- ]
- }
- },
- {
- "AutoRecoverySupported": false,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": false,
- "DedicatedHostsSupported": false,
- "EbsInfo": {
- "EbsOptimizedSupport": "supported",
- "EncryptionSupport": "unsupported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": {
- "Disks": [
- {
- "Count": 4,
- "SizeInGB": 420,
- "Type": "hdd"
- }
- ],
- "TotalSizeInGB": 1680
- },
- "InstanceStorageSupported": true,
- "InstanceType": "c1.xlarge",
- "MemoryInfo": {
- "SizeInMiB": 7168
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 0,
- "Ipv6Supported": false,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "High"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": null
- },
- "SupportedRootDeviceTypes": [
- "ebs",
- "instance-store"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": 8,
- "DefaultThreadsPerCore": 1,
- "DefaultVCpus": 8,
- "ValidCores": [
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8
- ],
- "ValidThreadsPerCore": [
- 1
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": false,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "supported",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": {
- "Disks": [
- {
- "Count": 2,
- "SizeInGB": 80,
- "Type": "ssd"
- }
- ],
- "TotalSizeInGB": 160
- },
- "InstanceStorageSupported": true,
- "InstanceType": "c3.2xlarge",
- "MemoryInfo": {
- "SizeInMiB": 15360
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 15,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "High"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.8
- },
- "SupportedRootDeviceTypes": [
- "ebs",
- "instance-store"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 4,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 8,
- "ValidCores": [
- 1,
- 2,
- 3,
- 4
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": false,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "supported",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": {
- "Disks": [
- {
- "Count": 2,
- "SizeInGB": 160,
- "Type": "ssd"
- }
- ],
- "TotalSizeInGB": 320
- },
- "InstanceStorageSupported": true,
- "InstanceType": "c3.4xlarge",
- "MemoryInfo": {
- "SizeInMiB": 30720
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "High"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.8
- },
- "SupportedRootDeviceTypes": [
- "ebs",
- "instance-store"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 8,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 16,
- "ValidCores": [
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": false,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "unsupported",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": {
- "Disks": [
- {
- "Count": 2,
- "SizeInGB": 320,
- "Type": "ssd"
- }
- ],
- "TotalSizeInGB": 640
- },
- "InstanceStorageSupported": true,
- "InstanceType": "c3.8xlarge",
- "MemoryInfo": {
- "SizeInMiB": 61440
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.8
- },
- "SupportedRootDeviceTypes": [
- "ebs",
- "instance-store"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 16,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 32,
- "ValidCores": [
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": false,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "unsupported",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": {
- "Disks": [
- {
- "Count": 2,
- "SizeInGB": 16,
- "Type": "ssd"
- }
- ],
- "TotalSizeInGB": 32
- },
- "InstanceStorageSupported": true,
- "InstanceType": "c3.large",
- "MemoryInfo": {
- "SizeInMiB": 3840
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 10,
- "Ipv6AddressesPerInterface": 10,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 3,
- "NetworkPerformance": "Moderate"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "i386",
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.8
- },
- "SupportedRootDeviceTypes": [
- "ebs",
- "instance-store"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 1,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 2,
- "ValidCores": [
- 1
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": false,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "supported",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": {
- "Disks": [
- {
- "Count": 2,
- "SizeInGB": 40,
- "Type": "ssd"
- }
- ],
- "TotalSizeInGB": 80
- },
- "InstanceStorageSupported": true,
- "InstanceType": "c3.xlarge",
- "MemoryInfo": {
- "SizeInMiB": 7680
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 15,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "Moderate"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.8
- },
- "SupportedRootDeviceTypes": [
- "ebs",
- "instance-store"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 2,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 4,
- "ValidCores": [
- 1,
- 2
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c4.2xlarge",
- "MemoryInfo": {
- "SizeInMiB": 15360
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 15,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "High"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.9
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 4,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 8,
- "ValidCores": [
- 1,
- 2,
- 3,
- 4
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c4.4xlarge",
- "MemoryInfo": {
- "SizeInMiB": 30720
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "High"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.9
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 8,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 16,
- "ValidCores": [
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c4.8xlarge",
- "MemoryInfo": {
- "SizeInMiB": 61440
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.9
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 18,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 36,
- "ValidCores": [
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c4.large",
- "MemoryInfo": {
- "SizeInMiB": 3840
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 10,
- "Ipv6AddressesPerInterface": 10,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 3,
- "NetworkPerformance": "Moderate"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.9
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 1,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 2,
- "ValidCores": [
- 1
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c4.xlarge",
- "MemoryInfo": {
- "SizeInMiB": 7680
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 15,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "High"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.9
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 2,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 4,
- "ValidCores": [
- 1,
- 2
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": false,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c5.12xlarge",
- "MemoryInfo": {
- "SizeInMiB": 98304
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "12 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 3.6
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 24,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 48,
- "ValidCores": [
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18,
- 20,
- 22,
- 24
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c5.18xlarge",
- "MemoryInfo": {
- "SizeInMiB": 147456
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 50,
- "Ipv6AddressesPerInterface": 50,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 15,
- "NetworkPerformance": "25 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 3.4
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 36,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 72,
- "ValidCores": [
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18,
- 20,
- 22,
- 24,
- 26,
- 28,
- 30,
- 32,
- 34,
- 36
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": false,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": false,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c5.24xlarge",
- "MemoryInfo": {
- "SizeInMiB": 196608
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 50,
- "Ipv6AddressesPerInterface": 50,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 15,
- "NetworkPerformance": "25 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 3.6
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 48,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 96,
- "ValidCores": [
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18,
- 20,
- 22,
- 24,
- 26,
- 28,
- 30,
- 32,
- 34,
- 36,
- 38,
- 40,
- 42,
- 44,
- 46,
- 48
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c5.2xlarge",
- "MemoryInfo": {
- "SizeInMiB": 16384
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 15,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 3.4
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 4,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 8,
- "ValidCores": [
- 2,
- 4
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c5.4xlarge",
- "MemoryInfo": {
- "SizeInMiB": 32768
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 3.4
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 8,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 16,
- "ValidCores": [
- 2,
- 4,
- 6,
- 8
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c5.9xlarge",
- "MemoryInfo": {
- "SizeInMiB": 73728
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 30,
- "Ipv6AddressesPerInterface": 30,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 8,
- "NetworkPerformance": "10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 3.4
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 18,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 36,
- "ValidCores": [
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- },
- {
- "AutoRecoverySupported": true,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": null,
- "HibernationSupported": true,
- "Hypervisor": "nitro",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": null,
- "InstanceStorageSupported": false,
- "InstanceType": "c5.large",
- "MemoryInfo": {
- "SizeInMiB": 4096
- },
- "NetworkInfo": {
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 10,
- "Ipv6AddressesPerInterface": 10,
- "Ipv6Supported": true,
- "MaximumNetworkInterfaces": 3,
- "NetworkPerformance": "Up to 10 Gigabit"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 3.4
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "VCpuInfo": {
- "DefaultCores": 1,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 2,
- "ValidCores": [
- 1
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- }
-]
-}
\ No newline at end of file
diff --git a/test/static/DescribeInstanceTypesPages/g2_2xlarge.json b/test/static/DescribeInstanceTypesPages/g2_2xlarge.json
deleted file mode 100644
index a28ec0f0..00000000
--- a/test/static/DescribeInstanceTypesPages/g2_2xlarge.json
+++ /dev/null
@@ -1,91 +0,0 @@
-{
- "InstanceTypes": [
- {
- "AutoRecoverySupported": false,
- "BareMetal": false,
- "BurstablePerformanceSupported": false,
- "CurrentGeneration": false,
- "DedicatedHostsSupported": true,
- "EbsInfo": {
- "EbsOptimizedSupport": "supported",
- "EncryptionSupport": "supported"
- },
- "FpgaInfo": null,
- "FreeTierEligible": false,
- "GpuInfo": {
- "Gpus": [
- {
- "Count": 1,
- "Manufacturer": "NVIDIA",
- "MemoryInfo": {
- "SizeInMiB": 4096
- },
- "Name": "K520"
- }
- ],
- "TotalGpuMemoryInMiB": 4096
- },
- "HibernationSupported": false,
- "Hypervisor": "xen",
- "InferenceAcceleratorInfo": null,
- "InstanceStorageInfo": {
- "Disks": [
- {
- "Count": 1,
- "SizeInGB": 60,
- "Type": "ssd"
- }
- ],
- "TotalSizeInGB": 60
- },
- "InstanceStorageSupported": true,
- "InstanceType": "g2.2xlarge",
- "MemoryInfo": {
- "SizeInMiB": 15360
- },
- "NetworkInfo": {
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 15,
- "Ipv6AddressesPerInterface": 0,
- "Ipv6Supported": false,
- "MaximumNetworkInterfaces": 4,
- "NetworkPerformance": "Moderate"
- },
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.6
- },
- "SupportedRootDeviceTypes": [
- "ebs",
- "instance-store"
- ],
- "SupportedUsageClasses": [
- "on-demand"
- ],
- "VCpuInfo": {
- "DefaultCores": 4,
- "DefaultThreadsPerCore": 2,
- "DefaultVCpus": 8,
- "ValidCores": [
- 1,
- 2,
- 3,
- 4
- ],
- "ValidThreadsPerCore": [
- 1,
- 2
- ]
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/test/static/DescribeInstanceTypesPages/m4_xlarge.json b/test/static/DescribeInstanceTypesPages/m4_xlarge.json
deleted file mode 100644
index eccbef82..00000000
--- a/test/static/DescribeInstanceTypesPages/m4_xlarge.json
+++ /dev/null
@@ -1,64 +0,0 @@
-{
- "InstanceTypes": [
- {
- "FreeTierEligible": false,
- "InstanceStorageSupported": false,
- "Hypervisor": "xen",
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "MemoryInfo": {
- "SizeInMiB": 16384
- },
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "VCpuInfo": {
- "ValidThreadsPerCore": [
- 1,
- 2
- ],
- "DefaultCores": 2,
- "DefaultVCpus": 4,
- "ValidCores": [
- 1,
- 2
- ],
- "DefaultThreadsPerCore": 2
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.4
- },
- "BareMetal": false,
- "AutoRecoverySupported": true,
- "NetworkInfo": {
- "NetworkPerformance": "High",
- "MaximumNetworkInterfaces": 4,
- "Ipv6Supported": true,
- "Ipv6AddressesPerInterface": 15,
- "EnaSupport": "unsupported",
- "Ipv4AddressesPerInterface": 15
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "HibernationSupported": true,
- "BurstablePerformanceSupported": false,
- "InstanceType": "m4.xlarge"
- }
- ]
-}
\ No newline at end of file
diff --git a/test/static/DescribeInstanceTypesPages/t3_micro.json b/test/static/DescribeInstanceTypesPages/t3_micro.json
deleted file mode 100644
index 37ca8c7c..00000000
--- a/test/static/DescribeInstanceTypesPages/t3_micro.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- "InstanceTypes": [
- {
- "FreeTierEligible": false,
- "InstanceStorageSupported": false,
- "Hypervisor": "nitro",
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "partition",
- "spread"
- ]
- },
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "MemoryInfo": {
- "SizeInMiB": 1024
- },
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "VCpuInfo": {
- "ValidThreadsPerCore": [
- 1,
- 2
- ],
- "DefaultCores": 1,
- "DefaultVCpus": 2,
- "ValidCores": [
- 1
- ],
- "DefaultThreadsPerCore": 2
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.5
- },
- "BareMetal": false,
- "AutoRecoverySupported": true,
- "NetworkInfo": {
- "NetworkPerformance": "Up to 5 Gigabit",
- "MaximumNetworkInterfaces": 2,
- "Ipv6Supported": true,
- "Ipv6AddressesPerInterface": 2,
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 2
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "HibernationSupported": false,
- "BurstablePerformanceSupported": true,
- "InstanceType": "t3.micro"
- }
- ]
-}
\ No newline at end of file
diff --git a/test/static/DescribeInstanceTypesPages/t3_micro_and_p3_16xl.json b/test/static/DescribeInstanceTypesPages/t3_micro_and_p3_16xl.json
deleted file mode 100644
index b95069ff..00000000
--- a/test/static/DescribeInstanceTypesPages/t3_micro_and_p3_16xl.json
+++ /dev/null
@@ -1,149 +0,0 @@
-{
- "InstanceTypes": [
- {
- "FreeTierEligible": false,
- "InstanceStorageSupported": false,
- "Hypervisor": "nitro",
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "partition",
- "spread"
- ]
- },
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "MemoryInfo": {
- "SizeInMiB": 1024
- },
- "CurrentGeneration": true,
- "DedicatedHostsSupported": true,
- "VCpuInfo": {
- "ValidThreadsPerCore": [
- 1,
- 2
- ],
- "DefaultCores": 1,
- "DefaultVCpus": 2,
- "ValidCores": [
- 1
- ],
- "DefaultThreadsPerCore": 2
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.5
- },
- "BareMetal": false,
- "AutoRecoverySupported": true,
- "NetworkInfo": {
- "NetworkPerformance": "Up to 5 Gigabit",
- "MaximumNetworkInterfaces": 2,
- "Ipv6Supported": true,
- "Ipv6AddressesPerInterface": 2,
- "EnaSupport": "required",
- "Ipv4AddressesPerInterface": 2
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "HibernationSupported": false,
- "BurstablePerformanceSupported": true,
- "InstanceType": "t3.micro"
- },
- {
- "FreeTierEligible": false,
- "InstanceStorageSupported": false,
- "Hypervisor": "xen",
- "PlacementGroupInfo": {
- "SupportedStrategies": [
- "cluster",
- "partition",
- "spread"
- ]
- },
- "SupportedUsageClasses": [
- "on-demand",
- "spot"
- ],
- "MemoryInfo": {
- "SizeInMiB": 499712
- },
- "CurrentGeneration": true,
- "GpuInfo": {
- "Gpus": [
- {
- "Count": 8,
- "MemoryInfo": {
- "SizeInMiB": 16384
- },
- "Name": "V100",
- "Manufacturer": "NVIDIA"
- }
- ],
- "TotalGpuMemoryInMiB": 131072
- },
- "VCpuInfo": {
- "ValidThreadsPerCore": [
- 1,
- 2
- ],
- "DefaultCores": 32,
- "DefaultVCpus": 64,
- "ValidCores": [
- 2,
- 4,
- 6,
- 8,
- 10,
- 12,
- 14,
- 16,
- 18,
- 20,
- 22,
- 24,
- 26,
- 28,
- 30,
- 32
- ],
- "DefaultThreadsPerCore": 2
- },
- "ProcessorInfo": {
- "SupportedArchitectures": [
- "x86_64"
- ],
- "SustainedClockSpeedInGhz": 2.7
- },
- "BareMetal": false,
- "AutoRecoverySupported": true,
- "NetworkInfo": {
- "NetworkPerformance": "25 Gigabit",
- "MaximumNetworkInterfaces": 8,
- "Ipv6Supported": true,
- "Ipv6AddressesPerInterface": 30,
- "EnaSupport": "supported",
- "Ipv4AddressesPerInterface": 30
- },
- "SupportedRootDeviceTypes": [
- "ebs"
- ],
- "EbsInfo": {
- "EbsOptimizedSupport": "default",
- "EncryptionSupport": "supported"
- },
- "HibernationSupported": false,
- "DedicatedHostsSupported": true,
- "BurstablePerformanceSupported": false,
- "InstanceType": "p3.16xlarge"
- }
- ]
-}
\ No newline at end of file
diff --git a/test/static/DescribeSpotPriceHistoryPages/m5_large.json b/test/static/DescribeSpotPriceHistory/m5_large.json
similarity index 100%
rename from test/static/DescribeSpotPriceHistoryPages/m5_large.json
rename to test/static/DescribeSpotPriceHistory/m5_large.json
diff --git a/test/static/GetProductsPages/m5_large.json b/test/static/GetProducts/m5_large.json
similarity index 100%
rename from test/static/GetProductsPages/m5_large.json
rename to test/static/GetProducts/m5_large.json