ec2spot 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/2024.02.02_SpotAdvisorData.json +1 -0
- data/lib/ec2spot.rb +180 -0
- metadata +79 -0
data/lib/ec2spot.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
require "aws-sdk-ec2"
|
2
|
+
require "aws-sdk-pricing"
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
class EC2Spot
|
6
|
+
private_class_method :new
|
7
|
+
|
8
|
+
def self.instance
|
9
|
+
@instance ||= new
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.instance!
|
13
|
+
@instance = new
|
14
|
+
end
|
15
|
+
|
16
|
+
def prices(*instance_types)
|
17
|
+
json = spot_price_history(*instance_types).group_by(&:instance_type).map do |instance_type, spot_price_history|
|
18
|
+
prices = spot_price_history.map(&:spot_price).map(&:to_f)
|
19
|
+
{
|
20
|
+
instance: instance_type,
|
21
|
+
min: prices.min,
|
22
|
+
max: prices.max,
|
23
|
+
avg: prices.sum / prices.length,
|
24
|
+
full: instance_price(instance_type),
|
25
|
+
savings: (1 - (prices.sum / prices.length) / instance_price(instance_type)),
|
26
|
+
interrupts: spot_advisor_range(instance_type)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def services(service_code, next_token: nil)
|
34
|
+
resp = @pricing_client.describe_services({
|
35
|
+
service_code: service_code,
|
36
|
+
format_version: "aws_v1",
|
37
|
+
next_token: next_token,
|
38
|
+
max_results: 100,
|
39
|
+
})
|
40
|
+
json = resp.services
|
41
|
+
if !resp.next_token.nil? && !resp.next_token.empty?
|
42
|
+
json += services(service_code, next_token: resp.next_token)
|
43
|
+
end
|
44
|
+
json
|
45
|
+
end
|
46
|
+
|
47
|
+
def attribute_values(service_code, attribute_name, next_token: nil)
|
48
|
+
resp = @pricing_client.get_attribute_values({
|
49
|
+
service_code: service_code,
|
50
|
+
attribute_name: attribute_name,
|
51
|
+
next_token: next_token,
|
52
|
+
max_results: 100,
|
53
|
+
})
|
54
|
+
json = resp.attribute_values
|
55
|
+
if !resp.next_token.nil? && !resp.next_token.empty?
|
56
|
+
json += attribute_values(service_code, attribute_name, next_token: resp.next_token)
|
57
|
+
end
|
58
|
+
json
|
59
|
+
end
|
60
|
+
|
61
|
+
def instance_price(instance_type)
|
62
|
+
data = products("AmazonEC2", instance_type)
|
63
|
+
data["terms"]["OnDemand"].first[1]["priceDimensions"].first[1]["pricePerUnit"]["USD"].to_f
|
64
|
+
end
|
65
|
+
|
66
|
+
def products(service_code, instance_type, next_token: nil)
|
67
|
+
resp = @pricing_client.get_products({
|
68
|
+
service_code: service_code,
|
69
|
+
filters: [
|
70
|
+
{
|
71
|
+
type: "TERM_MATCH",
|
72
|
+
field: "instanceType",
|
73
|
+
value: instance_type,
|
74
|
+
},
|
75
|
+
{
|
76
|
+
type: "TERM_MATCH",
|
77
|
+
field: "regionCode",
|
78
|
+
value: @region,
|
79
|
+
},
|
80
|
+
{
|
81
|
+
type: "TERM_MATCH",
|
82
|
+
field: "operatingSystem",
|
83
|
+
value: "Linux",
|
84
|
+
},
|
85
|
+
{
|
86
|
+
type: "TERM_MATCH",
|
87
|
+
field: "usagetype",
|
88
|
+
value: "USE2-BoxUsage:#{instance_type}",
|
89
|
+
},
|
90
|
+
{
|
91
|
+
type: "TERM_MATCH",
|
92
|
+
field: "operation",
|
93
|
+
value: "RunInstances",
|
94
|
+
}
|
95
|
+
],
|
96
|
+
format_version: "aws_v1",
|
97
|
+
next_token: next_token,
|
98
|
+
max_results: 100,
|
99
|
+
})
|
100
|
+
json = resp.price_list
|
101
|
+
if resp.price_list.length > 1 || (!resp.next_token.nil? && !resp.next_token.empty?)
|
102
|
+
throw "Only expected one result, but got #{json.length}"
|
103
|
+
end
|
104
|
+
JSON.parse(json.first)
|
105
|
+
end
|
106
|
+
|
107
|
+
def instance_types()
|
108
|
+
resp = @ec2_client.describe_instance_type_offerings({
|
109
|
+
max_results: 1000
|
110
|
+
})
|
111
|
+
resp.instance_type_offerings
|
112
|
+
end
|
113
|
+
|
114
|
+
def spot_price_history(*instance_types, next_token: nil)
|
115
|
+
now = Time.now.utc
|
116
|
+
its_been_one_week = now - (60 * 60 * 24 * 7)
|
117
|
+
json = []
|
118
|
+
resp = @ec2_client.describe_spot_price_history({
|
119
|
+
end_time: now,
|
120
|
+
instance_types: instance_types,
|
121
|
+
product_descriptions: [
|
122
|
+
"Linux/UNIX (Amazon VPC)",
|
123
|
+
],
|
124
|
+
start_time: its_been_one_week,
|
125
|
+
max_results: 1000,
|
126
|
+
next_token: next_token
|
127
|
+
})
|
128
|
+
json = resp.spot_price_history
|
129
|
+
if !resp.next_token.nil? && !resp.next_token.empty?
|
130
|
+
json += spot_price_history(*instance_types, next_token: resp.next_token)
|
131
|
+
end
|
132
|
+
json
|
133
|
+
end
|
134
|
+
|
135
|
+
def instance_type_info(*instance_types, next_token: nil)
|
136
|
+
json = []
|
137
|
+
resp = @ec2_client.describe_instance_types({
|
138
|
+
instance_types: instance_types,
|
139
|
+
next_token: next_token
|
140
|
+
})
|
141
|
+
json = resp.instance_types
|
142
|
+
if !resp.next_token.nil? && !resp.next_token.empty?
|
143
|
+
json += instance_type_info(*instance_types, next_token: resp.next_token)
|
144
|
+
end
|
145
|
+
json
|
146
|
+
end
|
147
|
+
|
148
|
+
def spot_advisor_range(instance_type)
|
149
|
+
@spot_advisor_ranges[@spot_advisor[instance_type]["r"]]
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize
|
153
|
+
@ec2_client = Aws::EC2::Client.new()
|
154
|
+
@pricing_client = Aws::Pricing::Client.new(region: "us-east-1")
|
155
|
+
@region = ENV.fetch("AWS_REGION", "us-east-2")
|
156
|
+
data = nil
|
157
|
+
begin
|
158
|
+
url = URI.parse("https://spot-bid-advisor.s3.amazonaws.com/spot-advisor-data.json")
|
159
|
+
http = Net::HTTP.new(url.host, url.port)
|
160
|
+
http.use_ssl = (url.scheme == 'https')
|
161
|
+
request = Net::HTTP::Get.new(url.path)
|
162
|
+
response = http.request(request)
|
163
|
+
data = JSON.parse(response.body)
|
164
|
+
rescue => e
|
165
|
+
puts "Failed to fetch spot-advisor-data.json from S3: #{e.message} using local file instead."
|
166
|
+
current_directory = File.dirname(__FILE__)
|
167
|
+
filepath = File.join(current_directory, "2024.02.02_SpotAdvisorData.json")
|
168
|
+
data = JSON.parse(File.read(filepath))
|
169
|
+
end
|
170
|
+
@spot_advisor = data["spot_advisor"][@region]["Linux"]
|
171
|
+
@spot_advisor_ranges = [
|
172
|
+
"<5%",
|
173
|
+
"5-10%",
|
174
|
+
"10-15%",
|
175
|
+
"15-20%",
|
176
|
+
">20%",
|
177
|
+
]
|
178
|
+
self
|
179
|
+
end
|
180
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ec2spot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patrick Wiseman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk-ec2
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.437'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.437'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aws-sdk-pricing
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.54'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.54'
|
41
|
+
description: ec2spot create uses the AWS API to fetch spot prices for a list of instance
|
42
|
+
types. It includes the min, max, avg, and ondemand prices over a seven day period.
|
43
|
+
It also loads an estimate of the interrupts from the spot advisor tool. It expect
|
44
|
+
the AWS client to be configured using one of the standard methods.
|
45
|
+
email: patrick@deft.services
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- lib/2024.02.02_SpotAdvisorData.json
|
51
|
+
- lib/ec2spot.rb
|
52
|
+
homepage: https://github.com/deftinc/ec2spot
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
metadata:
|
56
|
+
homepage_uri: https://github.com/deftinc/ec2spot
|
57
|
+
source_code_uri: https://github.com/deftinc/ec2spot/tree/0.0.2
|
58
|
+
rubygems_mfa_required: 'true'
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 3.2.0
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements:
|
74
|
+
- A configured AWS client with access to the EC2 and Pricing APIs.
|
75
|
+
rubygems_version: 3.4.10
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: Wrapper around the AWS SDK for pricing EC2 Spot Instances.
|
79
|
+
test_files: []
|