infrawrench-bulk_instances 1.0.0 → 1.2.0
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 +4 -4
- data/README.md +56 -3
- data/lib/infrawrench/bulk_instances/aws_instance.rb +1 -1
- data/lib/infrawrench/bulk_instances/aws_pricing_data.rb +27 -8
- data/lib/infrawrench/bulk_instances/types.rb +10 -6
- data/lib/infrawrench/bulk_instances/version.rb +1 -1
- data/lib/infrawrench/bulk_instances.rb +86 -16
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35025be8d677ed34cdfea296faca613c02d6ffe5c88d35266ad33fc36947d704
|
|
4
|
+
data.tar.gz: 3035dee6a0a3f8f86923ab7fb1b1c16c02fbc0e2aff9798a18d4640b4518138c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd9380c0871c15a34ebe92328f868d6347f6a3b6ce3722a137318d44c48b70cc22573fc38d696a9f66584ea604e97feacfeeecd3772bf87d6285ba93167d11fe
|
|
7
|
+
data.tar.gz: 33c0e9e0a4e9855840c34f2a93d4d4903a8f813d495daadf782e0057ba3b9df45f34c2f17699f155a7dd509ad62629cf603f046c07f9f9dea9c1510a684da852
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# infrawrench-bulk_instances
|
|
2
2
|
|
|
3
|
-
AWS
|
|
4
|
-
Infrawrench publishes at `instances-bundles.infrawrench.com`.
|
|
3
|
+
AWS instance attributes and prices (EC2, RDS, ElastiCache and more), with their full
|
|
4
|
+
history, from the bundles Infrawrench publishes at `instances-bundles.infrawrench.com`.
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
7
|
gem install infrawrench-bulk_instances
|
|
@@ -29,16 +29,69 @@ m5.pricing_history.reserved # every reserved price change, oldest first
|
|
|
29
29
|
Any Infrawrench bearer credential works as `token:` (an `iwk_` API key, an agent
|
|
30
30
|
credential or a WorkOS access token). It defaults to `INFRAWRENCH_API_KEY`.
|
|
31
31
|
|
|
32
|
+
## Other AWS platforms
|
|
33
|
+
|
|
34
|
+
`download_aws_pricing_data(platform, **options)` loads any platform's bundle;
|
|
35
|
+
`download_aws_ec2_pricing_data(**options)` is the same as `download_aws_pricing_data("ec2", **options)`.
|
|
36
|
+
`Infrawrench::BulkInstances::AWS_PLATFORMS` lists the platforms:
|
|
37
|
+
|
|
38
|
+
| Platform | AWS service |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `ec2` | EC2 |
|
|
41
|
+
| `rds` | RDS |
|
|
42
|
+
| `elasticache` | ElastiCache |
|
|
43
|
+
| `opensearch` | OpenSearch Service |
|
|
44
|
+
| `redshift` | Redshift |
|
|
45
|
+
| `docdb` | DocumentDB |
|
|
46
|
+
| `neptune` | Neptune |
|
|
47
|
+
| `memorydb` | MemoryDB |
|
|
48
|
+
| `dax` | DynamoDB Accelerator |
|
|
49
|
+
| `sagemaker` | SageMaker |
|
|
50
|
+
| `emr` | EMR |
|
|
51
|
+
| `gamelift` | GameLift |
|
|
52
|
+
|
|
53
|
+
The fields that pick a price differ by platform. `data.variant_keys` names them, and every
|
|
54
|
+
price has a `variant` hash keyed by them:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
rds = Infrawrench::BulkInstances.download_aws_pricing_data("rds")
|
|
58
|
+
rds.variant_keys # ["databaseEngine", "databaseEdition", "licenseModel", "deploymentOption", ...]
|
|
59
|
+
rds["db.m5.large"].pricing.on_demand.find do |p|
|
|
60
|
+
p.region_code == "us-east-1" && p.variant["databaseEngine"] == "MySQL" &&
|
|
61
|
+
p.variant["deploymentOption"] == "Multi-AZ"
|
|
62
|
+
end&.hourly
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`operating_system`, `tenancy`, `license_model`, `pre_installed_sw` and `capacitystatus` are EC2's
|
|
66
|
+
variant fields. On other platforms they are `""`, unless the platform has a field with that name
|
|
67
|
+
(RDS has `licenseModel`).
|
|
68
|
+
|
|
32
69
|
Requires Ruby 3.2 or later, and liblzma (part of macOS and of practically every
|
|
33
70
|
Linux distribution; `xz-utils`/`xz` otherwise) to decompress downloads.
|
|
34
71
|
|
|
72
|
+
## One instance
|
|
73
|
+
|
|
74
|
+
`Infrawrench::BulkInstances.fetch_aws_instance(platform, instance_type, ...)` fetches a single
|
|
75
|
+
instance from the Infrawrench API instead of downloading the platform's whole bundle. It returns
|
|
76
|
+
the same `AWSInstance` that `get` does, or `nil` if the platform has no instance with that name:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
m5 = Infrawrench::BulkInstances.fetch_aws_instance("ec2", "m5.large")
|
|
80
|
+
m5&.pricing&.on_demand&.length
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
It takes `token:` like the bundle download, and `base_url:` for where the API is served from
|
|
84
|
+
(`https://app.infrawrench.com` by default). Nothing is cached. The API syncs each new bundle within
|
|
85
|
+
about half an hour of it being published, so it can briefly trail `download_aws_pricing_data`.
|
|
86
|
+
|
|
35
87
|
## Caching
|
|
36
88
|
|
|
37
89
|
The bundle is downloaded xz-compressed (about 20 MB) and kept decompressed (about
|
|
38
90
|
210 MB) in `~/.infrawrench/aws_pricing_data` (`cache_path:`), so a cached load skips
|
|
39
91
|
decompression. Each load asks the server which bundle is latest and downloads it
|
|
40
92
|
only if it is not cached, deleting the older one. If the server cannot be
|
|
41
|
-
reached, the newest cached bundle is used instead.
|
|
93
|
+
reached, the newest cached bundle is used instead. Each platform's bundle is cached
|
|
94
|
+
under its own name (`rds-bundle-…json`), so loading one never evicts another. Pass `cache_pricing_data: false`
|
|
42
95
|
to always download and never touch the disk. The cache is shared with the other
|
|
43
96
|
Infrawrench SDKs.
|
|
44
97
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Infrawrench
|
|
4
4
|
module BulkInstances
|
|
5
|
-
#
|
|
5
|
+
# One AWS platform's pricing data, keyed by instance type.
|
|
6
6
|
#
|
|
7
7
|
# Nothing is decoded until it is asked for: #get wraps the raw rows, and each
|
|
8
8
|
# reader on the returned AWSInstance decodes its part on first access and keeps
|
|
@@ -15,10 +15,12 @@ module Infrawrench
|
|
|
15
15
|
# index into +compression_table+:
|
|
16
16
|
#
|
|
17
17
|
# {
|
|
18
|
+
# "offer" => "AmazonRDS", # EC2 bundles leave it out
|
|
18
19
|
# "compression_table" => [String, ...],
|
|
19
20
|
# "snapshots" => [Integer, ...],
|
|
20
21
|
# "regions" => [[location, [[key, value], ...]], ...],
|
|
21
|
-
# "
|
|
22
|
+
# "variant_keys" => [String, ...], # EC2 bundles leave it out (see EC2_VARIANT_KEYS)
|
|
23
|
+
# "variants" => [[value, ...], ...], # one value per variant key
|
|
22
24
|
# "reserved_terms" => [[lease, offering, purchase], ...],
|
|
23
25
|
# "instances" => { "m5.large" => [attrs, attr_changes, base_price, on_demand, reserved, created_at, deleted_at] },
|
|
24
26
|
# }
|
|
@@ -50,6 +52,18 @@ module Infrawrench
|
|
|
50
52
|
end
|
|
51
53
|
alias [] get
|
|
52
54
|
|
|
55
|
+
# The AWS offer code, e.g. +AmazonEC2+ or +AmazonRDS+.
|
|
56
|
+
# @return [String]
|
|
57
|
+
def offer
|
|
58
|
+
@raw["offer"] || "AmazonEC2"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The names of the fields that pick a price, which every price's +variant+ is keyed by.
|
|
62
|
+
# @return [Array<String>]
|
|
63
|
+
def variant_keys
|
|
64
|
+
@raw["variant_keys"] || EC2_VARIANT_KEYS
|
|
65
|
+
end
|
|
66
|
+
|
|
53
67
|
# The publication date of every AWS price list in the bundle, oldest first.
|
|
54
68
|
# @return [Array<Time>]
|
|
55
69
|
def snapshots
|
|
@@ -82,13 +96,18 @@ module Infrawrench
|
|
|
82
96
|
# @api private
|
|
83
97
|
# @return [Variant]
|
|
84
98
|
def variant(i)
|
|
85
|
-
@variants ||= @raw["variants"].map do |
|
|
99
|
+
@variants ||= @raw["variants"].map do |fields|
|
|
100
|
+
keys = variant_keys
|
|
101
|
+
raise "variant has #{fields.length} fields, expected #{keys.length}" if fields.length != keys.length
|
|
102
|
+
|
|
103
|
+
variant = keys.zip(fields).to_h { |k, v| [k, str(v)] }.freeze
|
|
86
104
|
Variant.new(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
variant:,
|
|
106
|
+
operating_system: variant.fetch("operatingSystem", ""),
|
|
107
|
+
tenancy: variant.fetch("tenancy", ""),
|
|
108
|
+
license_model: variant.fetch("licenseModel", ""),
|
|
109
|
+
pre_installed_sw: variant.fetch("preInstalledSw", ""),
|
|
110
|
+
capacitystatus: variant.fetch("capacitystatus", ""),
|
|
92
111
|
)
|
|
93
112
|
end.freeze
|
|
94
113
|
@variants[i]
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Infrawrench
|
|
4
4
|
module BulkInstances
|
|
5
|
-
# Every price and price change starts with its location:
|
|
5
|
+
# Every price and price change starts with its location and variant:
|
|
6
6
|
#
|
|
7
7
|
# - +region+: the AWS location name, e.g. <tt>US East (N. Virginia)</tt>.
|
|
8
8
|
# - +region_code+: e.g. +us-east-1+. +nil+ for locations the price list never gave a code.
|
|
9
|
+
# - +variant+: every field that picks the price, as a frozen <tt>Hash{String => String}</tt> keyed
|
|
10
|
+
# by name in AWSPricingData#variant_keys order: for RDS, +databaseEngine+, +deploymentOption+ and so on.
|
|
11
|
+
# - +operating_system+, +tenancy+, +license_model+, +pre_installed_sw+, +capacitystatus+: EC2's
|
|
12
|
+
# variant fields. For other platforms these are <tt>""</tt> unless the platform shares the field.
|
|
9
13
|
|
|
10
14
|
# The product fields that pick a price.
|
|
11
|
-
Variant = Data.define(:operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus)
|
|
15
|
+
Variant = Data.define(:variant, :operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus)
|
|
12
16
|
|
|
13
17
|
ReservedTerm = Data.define(:lease_contract_length, :offering_class, :purchase_option)
|
|
14
18
|
|
|
@@ -18,7 +22,7 @@ module Infrawrench
|
|
|
18
22
|
# @return [Time] When this price took effect.
|
|
19
23
|
OnDemandPrice = Data.define(
|
|
20
24
|
:region, :region_code,
|
|
21
|
-
:operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus,
|
|
25
|
+
:variant, :operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus,
|
|
22
26
|
:hourly, :since,
|
|
23
27
|
)
|
|
24
28
|
|
|
@@ -30,7 +34,7 @@ module Infrawrench
|
|
|
30
34
|
# @return [Time]
|
|
31
35
|
ReservedPrice = Data.define(
|
|
32
36
|
:region, :region_code,
|
|
33
|
-
:operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus,
|
|
37
|
+
:variant, :operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus,
|
|
34
38
|
:lease_contract_length, :offering_class, :purchase_option,
|
|
35
39
|
:hourly, :upfront, :since,
|
|
36
40
|
)
|
|
@@ -47,7 +51,7 @@ module Infrawrench
|
|
|
47
51
|
# @return [Float, nil] +nil+ means no longer offered from +at+ on.
|
|
48
52
|
OnDemandPriceChange = Data.define(
|
|
49
53
|
:at, :region, :region_code,
|
|
50
|
-
:operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus,
|
|
54
|
+
:variant, :operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus,
|
|
51
55
|
:hourly,
|
|
52
56
|
)
|
|
53
57
|
|
|
@@ -59,7 +63,7 @@ module Infrawrench
|
|
|
59
63
|
# @return [Float, nil]
|
|
60
64
|
ReservedPriceChange = Data.define(
|
|
61
65
|
:at, :region, :region_code,
|
|
62
|
-
:operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus,
|
|
66
|
+
:variant, :operating_system, :tenancy, :license_model, :pre_installed_sw, :capacitystatus,
|
|
63
67
|
:lease_contract_length, :offering_class, :purchase_option,
|
|
64
68
|
:hourly, :upfront,
|
|
65
69
|
)
|
|
@@ -14,9 +14,10 @@ require_relative "bulk_instances/aws_pricing_data"
|
|
|
14
14
|
require_relative "bulk_instances/aws_instance"
|
|
15
15
|
|
|
16
16
|
module Infrawrench
|
|
17
|
-
# AWS
|
|
17
|
+
# AWS instance attributes and pricing (EC2, RDS, ElastiCache and more), with full history, from Infrawrench.
|
|
18
18
|
module BulkInstances
|
|
19
19
|
DEFAULT_BASE_URL = "https://instances-bundles.infrawrench.com"
|
|
20
|
+
DEFAULT_API_URL = "https://app.infrawrench.com"
|
|
20
21
|
USER_AGENT = "infrawrench/bulk-instances, ruby, 1.0"
|
|
21
22
|
|
|
22
23
|
# What Net::HTTP raises when the server cannot be reached or the connection breaks.
|
|
@@ -42,12 +43,15 @@ module Infrawrench
|
|
|
42
43
|
end
|
|
43
44
|
private_constant :Util
|
|
44
45
|
|
|
46
|
+
# The AWS platforms Infrawrench publishes a bundle for. Any other lowercase name is
|
|
47
|
+
# requested as is, so a platform added later works without a new SDK.
|
|
48
|
+
AWS_PLATFORMS = %w[ec2 rds elasticache opensearch redshift docdb neptune memorydb dax sagemaker emr gamelift].freeze
|
|
49
|
+
|
|
50
|
+
# The variant keys of EC2 bundles, which don't list their own.
|
|
51
|
+
EC2_VARIANT_KEYS = %w[operatingSystem tenancy licenseModel preInstalledSw capacitystatus].freeze
|
|
52
|
+
|
|
45
53
|
class << self
|
|
46
|
-
# Downloads the AWS EC2 pricing data
|
|
47
|
-
#
|
|
48
|
-
# The bucket's +latest+ object names the newest bundle. With caching on, a bundle
|
|
49
|
-
# is only downloaded when +latest+ names one that is not in the cache yet, and if
|
|
50
|
-
# +latest+ cannot be fetched at all (offline, outage) the newest cached bundle is used.
|
|
54
|
+
# Downloads the AWS EC2 pricing data. The same as <tt>download_aws_pricing_data("ec2", ...)</tt>.
|
|
51
55
|
#
|
|
52
56
|
# @param token [String, nil] Your Infrawrench API token. Defaults to the +INFRAWRENCH_API_KEY+ environment variable.
|
|
53
57
|
# @param cache_pricing_data [Boolean] Whether to cache the pricing data.
|
|
@@ -56,6 +60,29 @@ module Infrawrench
|
|
|
56
60
|
# @return [AWSPricingData]
|
|
57
61
|
# @raise [AWSPricingDataError]
|
|
58
62
|
def download_aws_ec2_pricing_data(token: nil, cache_pricing_data: true, cache_path: nil, base_url: nil)
|
|
63
|
+
download_aws_pricing_data("ec2", token:, cache_pricing_data:, cache_path:, base_url:)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Downloads one AWS platform's pricing data compressed blob and decompresses it.
|
|
67
|
+
#
|
|
68
|
+
# The bucket's <tt><platform>-latest</tt> object (+latest+ for EC2) names the newest bundle. With
|
|
69
|
+
# caching on, a bundle is only downloaded when that names one that is not in the cache yet,
|
|
70
|
+
# and if it cannot be fetched at all (offline, outage) the newest cached bundle is used.
|
|
71
|
+
#
|
|
72
|
+
# @param platform [String, Symbol] One of AWS_PLATFORMS, e.g. +"rds"+, or any other lowercase platform name.
|
|
73
|
+
# @param token [String, nil] Your Infrawrench API token. Defaults to the +INFRAWRENCH_API_KEY+ environment variable.
|
|
74
|
+
# @param cache_pricing_data [Boolean] Whether to cache the pricing data.
|
|
75
|
+
# @param cache_path [String, nil] The cache path. Defaults to <tt>~/.infrawrench/aws_pricing_data</tt>.
|
|
76
|
+
# @param base_url [String, nil] Where the bundles are served from. Defaults to https://instances-bundles.infrawrench.com
|
|
77
|
+
# @return [AWSPricingData]
|
|
78
|
+
# @raise [AWSPricingDataError]
|
|
79
|
+
def download_aws_pricing_data(platform, token: nil, cache_pricing_data: true, cache_path: nil, base_url: nil)
|
|
80
|
+
unless (platform.is_a?(String) || platform.is_a?(Symbol)) && platform.match?(/\A[a-z0-9]+\z/)
|
|
81
|
+
raise AWSPricingDataError, %("#{platform}" is not a platform name.)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# EC2's keys have no prefix because they were published before the other platforms.
|
|
85
|
+
prefix = platform.to_s == "ec2" ? "" : "#{platform}-"
|
|
59
86
|
token = ENV.fetch("INFRAWRENCH_API_KEY", nil) if token.nil?
|
|
60
87
|
if token.nil? || token.empty?
|
|
61
88
|
raise AWSPricingDataError, "No Infrawrench API token: pass `token:` or set INFRAWRENCH_API_KEY."
|
|
@@ -65,15 +92,15 @@ module Infrawrench
|
|
|
65
92
|
cache_path = (cache_path || File.join(Dir.home, ".infrawrench", "aws_pricing_data")).to_s
|
|
66
93
|
|
|
67
94
|
begin
|
|
68
|
-
latest = download("#{base_url}
|
|
95
|
+
latest = download("#{base_url}/#{prefix}latest", token, &:read_body).to_s.dup.force_encoding(Encoding::UTF_8).scrub.strip
|
|
69
96
|
unless latest.match?(/\A[A-Za-z0-9._-]+\z/)
|
|
70
|
-
raise AWSPricingDataError, %(latest holds "#{latest}", which is not a timestamp.)
|
|
97
|
+
raise AWSPricingDataError, %(#{prefix}latest holds "#{latest}", which is not a timestamp.)
|
|
71
98
|
end
|
|
72
99
|
|
|
73
|
-
name = "bundle-#{latest}.json"
|
|
100
|
+
name = "#{prefix}bundle-#{latest}.json"
|
|
74
101
|
rescue StandardError => e
|
|
75
102
|
# A rejected token is a real answer; anything else falls back to what we already have.
|
|
76
|
-
cached = newest_cached(cache_path) if cache_pricing_data && !(e.is_a?(AWSPricingDataError) && e.status == 401)
|
|
103
|
+
cached = newest_cached(cache_path, prefix) if cache_pricing_data && !(e.is_a?(AWSPricingDataError) && e.status == 401)
|
|
77
104
|
raise if cached.nil?
|
|
78
105
|
|
|
79
106
|
name = cached
|
|
@@ -92,9 +119,9 @@ module Infrawrench
|
|
|
92
119
|
tmp = File.join(cache_path, "#{name}.#{Process.pid}.tmp")
|
|
93
120
|
File.binwrite(tmp, json)
|
|
94
121
|
File.rename(tmp, File.join(cache_path, name))
|
|
95
|
-
#
|
|
122
|
+
# This platform's older bundles, including compressed ones cached by earlier versions of this SDK.
|
|
96
123
|
Dir.each_child(cache_path) do |f|
|
|
97
|
-
FileUtils.rm_f(File.join(cache_path, f)) if f != name && f.match?(/\
|
|
124
|
+
FileUtils.rm_f(File.join(cache_path, f)) if f != name && f.match?(/\A#{prefix}bundle-.+\.json(\.xz)?\z/)
|
|
98
125
|
end
|
|
99
126
|
end
|
|
100
127
|
end
|
|
@@ -102,10 +129,53 @@ module Infrawrench
|
|
|
102
129
|
AWSPricingData.new(JSON.parse(json, freeze: true))
|
|
103
130
|
end
|
|
104
131
|
|
|
132
|
+
# Fetches one instance from the Infrawrench API, without downloading the platform's bundle.
|
|
133
|
+
# Nothing is cached.
|
|
134
|
+
#
|
|
135
|
+
# The result is the same as <tt>download_aws_pricing_data(platform).get(instance_type)</tt>, from
|
|
136
|
+
# whichever bundle the API last synced, which can trail the newest bundle by up to half an hour.
|
|
137
|
+
#
|
|
138
|
+
# @param platform [String, Symbol] One of AWS_PLATFORMS, e.g. +"rds"+, or any other lowercase platform name.
|
|
139
|
+
# @param instance_type [String] The instance type, e.g. +"m5.large"+.
|
|
140
|
+
# @param token [String, nil] Your Infrawrench API token. Defaults to the +INFRAWRENCH_API_KEY+ environment variable.
|
|
141
|
+
# @param base_url [String, nil] Where the Infrawrench API is served from. Defaults to https://app.infrawrench.com
|
|
142
|
+
# @return [AWSInstance, nil] +nil+ if the platform has no instance with that name.
|
|
143
|
+
# @raise [AWSPricingDataError]
|
|
144
|
+
def fetch_aws_instance(platform, instance_type, token: nil, base_url: nil)
|
|
145
|
+
unless (platform.is_a?(String) || platform.is_a?(Symbol)) && platform.match?(/\A[a-z0-9]+\z/)
|
|
146
|
+
raise AWSPricingDataError, %("#{platform}" is not a platform name.)
|
|
147
|
+
end
|
|
148
|
+
raise AWSPricingDataError, "The instance type is empty." if instance_type.to_s.empty?
|
|
149
|
+
|
|
150
|
+
token = ENV.fetch("INFRAWRENCH_API_KEY", nil) if token.nil?
|
|
151
|
+
if token.nil? || token.empty?
|
|
152
|
+
raise AWSPricingDataError, "No Infrawrench API token: pass `token:` or set INFRAWRENCH_API_KEY."
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
base_url = (base_url || DEFAULT_API_URL).sub(%r{/+\z}, "")
|
|
156
|
+
# Percent-encoded, with spaces as %20: a + in a path is a plus.
|
|
157
|
+
name = URI.encode_www_form_component(instance_type.to_s).gsub("+", "%20")
|
|
158
|
+
begin
|
|
159
|
+
json = download("#{base_url}/api/v1/aws-bulk-pricing/#{platform}/#{name}", token, &:read_body)
|
|
160
|
+
rescue AWSPricingDataError => e
|
|
161
|
+
return nil if e.status == 404
|
|
162
|
+
|
|
163
|
+
raise
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The bundle's tables cut down to what this instance uses, and its row renumbered to match.
|
|
167
|
+
value = JSON.parse(json.to_s.dup.force_encoding(Encoding::UTF_8), freeze: true)
|
|
168
|
+
bundle = value.slice("offer", "compression_table", "regions", "variant_keys", "variants", "reserved_terms")
|
|
169
|
+
bundle["snapshots"] = []
|
|
170
|
+
bundle["instances"] = { value["name"] => value["instance"] }
|
|
171
|
+
AWSPricingData.new(bundle).get(value["name"])
|
|
172
|
+
end
|
|
173
|
+
|
|
105
174
|
private
|
|
106
175
|
|
|
107
176
|
# GETs +url+ and yields the successful response, retrying the 503 the bundles Worker
|
|
108
|
-
# answers when it cannot reach the Infrawrench API
|
|
177
|
+
# answers when it cannot reach the Infrawrench API (and the API answers when it cannot
|
|
178
|
+
# reach its store).
|
|
109
179
|
def download(url, token)
|
|
110
180
|
uri = URI(url)
|
|
111
181
|
(1..).each do |attempt|
|
|
@@ -135,9 +205,9 @@ module Infrawrench
|
|
|
135
205
|
raise AWSPricingDataError, "GET #{url} failed: #{e.message}"
|
|
136
206
|
end
|
|
137
207
|
|
|
138
|
-
# The newest bundle already in the cache directory, if any.
|
|
139
|
-
def newest_cached(cache_path)
|
|
140
|
-
Dir.children(cache_path).grep(/\
|
|
208
|
+
# The newest bundle for a platform already in the cache directory, if any.
|
|
209
|
+
def newest_cached(cache_path, prefix)
|
|
210
|
+
Dir.children(cache_path).grep(/\A#{prefix}bundle-.+\.json\z/).max
|
|
141
211
|
rescue SystemCallError
|
|
142
212
|
nil
|
|
143
213
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: infrawrench-bulk_instances
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Infrawrench LLC
|
|
@@ -61,5 +61,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
61
61
|
requirements: []
|
|
62
62
|
rubygems_version: 3.6.9
|
|
63
63
|
specification_version: 4
|
|
64
|
-
summary: AWS
|
|
64
|
+
summary: AWS instance attributes and pricing (EC2, RDS, ElastiCache and more), with
|
|
65
|
+
full history, from Infrawrench.
|
|
65
66
|
test_files: []
|