Skip to content

Commit 7578114

Browse files
mdbKrishna Birla
andauthored
Add Spot/On-demand price to table-wide output irrespective of the price filters (#106)
* Add Spot/On-demand price to table-wide output irrespective of the price filters * Minor clean-up * adjust ec2pricing imports * remove the importing of un-used Lightsail packages, as [requested by @bwagner](#78 (comment)) * move non-standard lib imports to their own stanza, as [requested by @bwagner](#78 (comment)) Signed-off-by: Mike Ball <mikedball@gmail.com> * rename aZones var to 'availabilityZones' This was requested by @bwagner here: #78 (comment) Signed-off-by: Mike Ball <mikedball@gmail.com> * rename fields/methods `*CacheUTC` Per code review from @bwagner, this renames various methods and fields to be `*CacheUTC` rather than `*CachedUTC`: #78 (comment) * update selector to use `LastSpotCacheUTC()` Previously, `selector` was incorrectly using the old `LastSpotCachedUTC` method name. * make `ec2PricingMock` proper `EC2PricingIface` * remove invalid 'FIXME' comment This is now fixed, as the Pricing client is always initialized to us-east-1. Signed-off-by: Mike Ball <mikedball@gmail.com> * move error check inside loop Per [code review feedback](#106 (comment)), the error checking should appear within the `range pricingOutput.PriceList`. Signed-off-by: Mike Ball <mikedball@gmail.com> * remove invalid 'FIXME' code comment This issue is now resolved. Signed-off-by: Mike Ball <mikedball@gmail.com> * commit `go mod tidy` results Signed-off-by: Mike Ball <mikedball@gmail.com> * protect against panics during unit tests This protects against panics like the following, for example: ``` --- FAIL: TestFilter_X8664_AMD64 (0.00s) panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x38 pc=0x153ec41] goroutine 153 [running]: testing.tRunner.func1.2({0x1613ee0, 0x1cdfe70}) /usr/local/Cellar/go/1.17.2/libexec/src/testing/testing.go:1209 +0x24e testing.tRunner.func1() /usr/local/Cellar/go/1.17.2/libexec/src/testing/testing.go:1212 +0x218 panic({0x1613ee0, 0x1cdfe70}) /usr/local/Cellar/go/1.17.2/libexec/src/runtime/panic.go:1038 +0x215 github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector.Selector.rawFilter.func1(0x100e6e7, 0x68) /Users/mball/dev/go/src/github.com/aws/amazon-ec2-instance-selector/pkg/selector/selector.go:192 +0x241 github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector_test.mockedEC2.DescribeInstanceTypesPages(...) /Users/mball/dev/go/src/github.com/aws/amazon-ec2-instance-selector/pkg/selector/selector_test.go:70 github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector.Selector.rawFilter({{_, _}, {_, _}, {_}}, {0x0, 0x0, 0x0, 0xc0003854a0, 0x0, ...}) /Users/mball/dev/go/src/github.com/aws/amazon-ec2-instance-selector/pkg/selector/selector.go:184 +0x5c2 github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector.Selector.FilterWithOutput({{_, _}, {_, _}, {_}}, {0x0, 0x0, 0x0, 0xc0003854a0, 0x0, ...}, ...) /Users/mball/dev/go/src/github.com/aws/amazon-ec2-instance-selector/pkg/selector/selector.go:116 +0xc5 github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector.Selector.Filter({{_, _}, {_, _}, {_}}, {0x0, 0x0, 0x0, 0xc0003854a0, 0x0, ...}) /Users/mball/dev/go/src/github.com/aws/amazon-ec2-instance-selector/pkg/selector/selector.go:98 +0xcc github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector_test.TestFilter_X8664_AMD64(0xc0002de820) /Users/mball/dev/go/src/github.com/aws/amazon-ec2-instance-selector/pkg/selector/selector_test.go:583 +0x14e testing.tRunner(0xc00059b520, 0x177e1b0) /usr/local/Cellar/go/1.17.2/libexec/src/testing/testing.go:1259 +0x102 created by testing.(*T).Run /usr/local/Cellar/go/1.17.2/libexec/src/testing/testing.go:1306 +0x35a exit status 2 ``` Signed-off-by: Mike Ball <mikedball@gmail.com> * report received value in test output This seeks to make the specifics of test failures a bit more clear. Signed-off-by: Mike Ball <mikedball@gmail.com> * run `go mod tidy` This addresses the following: ``` $ make unit-test go test -bench=. /Users/mball/dev/go/src/github.com/aws/amazon-ec2-instance-selector//... -v -coverprofile=coverage.out -covermode=atomic -outputdir=/Users/mball/dev/go/src/github.com/aws/amazon-ec2-instance-selector//build go: updates to go.mod needed; to update it: go mod tidy make: *** [unit-test] Error 1 ``` Signed-off-by: Mike Ball <mikedball@gmail.com> * fix failing selector.Filter unit tests By populating the `lastOnDemandCacheUTC` and `lastSpotCacheUTC` fields with non-`nil` values, the `Filter` tests exercise relevant code paths. Note that this doesn't change @krishna-birla's original implementation (see PR #78) or vision; it only ensures the tests pass. Signed-off-by: Mike Ball <mikedball@gmail.com> Co-authored-by: Krishna Birla <krishna.birla@cloudera.com>
1 parent 4448edf commit 7578114

File tree

8 files changed

+217
-106
lines changed

8 files changed

+217
-106
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
### Go ###
2+
# IDE
3+
.idea
4+
25
# Binaries for programs and plugins
36
*.exe
47
*.exe~

cmd/main.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"log"
1919
"os"
2020
"strings"
21+
"sync"
2122

2223
commandline "github.com/aws/amazon-ec2-instance-selector/v2/pkg/cli"
2324
"github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector"
@@ -187,11 +188,29 @@ Full docs can be found at github.com/aws/amazon-` + binName
187188
flags[region] = sess.Config.Region
188189

189190
instanceSelector := selector.New(sess)
190-
if _, ok := flags[pricePerHour]; ok {
191-
if flags[usageClass] == nil || *flags[usageClass].(*string) == "on-demand" {
192-
instanceSelector.EC2Pricing.HydrateOndemandCache()
191+
outputFlag := cli.StringMe(flags[output])
192+
if outputFlag != nil && *outputFlag == tableWideOutput {
193+
// If output type is `table-wide`, simply print both prices for better comparison,
194+
// even if the actual filter is applied on any one of those based on usage class
195+
196+
// Save time by hydrating in parallel
197+
wg := &sync.WaitGroup{}
198+
wg.Add(2)
199+
go func(waitGroup *sync.WaitGroup) {
200+
defer waitGroup.Done()
201+
_ = instanceSelector.EC2Pricing.HydrateOndemandCache()
202+
}(wg)
203+
go func(waitGroup *sync.WaitGroup) {
204+
defer waitGroup.Done()
205+
_ = instanceSelector.EC2Pricing.HydrateSpotCache(30)
206+
}(wg)
207+
wg.Wait()
208+
} else if flags[pricePerHour] != nil {
209+
// Else, if price filters are applied, only hydrate the respective cache as we don't have to print the prices
210+
if flags[usageClass] == nil || *cli.StringMe(flags[usageClass]) == "on-demand" {
211+
_ = instanceSelector.EC2Pricing.HydrateOndemandCache()
193212
} else {
194-
instanceSelector.EC2Pricing.HydrateSpotCache(30)
213+
_ = instanceSelector.EC2Pricing.HydrateSpotCache(30)
195214
}
196215
}
197216

@@ -252,7 +271,6 @@ Full docs can be found at github.com/aws/amazon-` + binName
252271
}
253272
}
254273

255-
outputFlag := cli.StringMe(flags[output])
256274
outputFn := getOutputFn(outputFlag, selector.InstanceTypesOutputFn(resultsOutputFn))
257275

258276
instanceTypes, itemsTruncated, err := instanceSelector.FilterWithOutput(filters, outputFn)

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ require (
99
github.com/hashicorp/hcl v1.0.0
1010
github.com/imdario/mergo v0.3.11
1111
github.com/mitchellh/go-homedir v1.1.0
12-
github.com/smartystreets/goconvey v1.6.4 // indirect
1312
github.com/spf13/cobra v0.0.7
1413
github.com/spf13/pflag v1.0.3
14+
go.uber.org/multierr v1.1.0
1515
gopkg.in/ini.v1 v1.57.0
1616
)
1717

1818
require (
1919
github.com/inconshreveable/mousetrap v1.0.0 // indirect
2020
github.com/jmespath/go-jmespath v0.4.0 // indirect
21+
github.com/smartystreets/goconvey v1.6.4 // indirect
22+
go.uber.org/atomic v1.4.0 // indirect
2123
gopkg.in/yaml.v2 v2.3.0 // indirect
2224
)

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,16 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
109109
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
110110
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
111111
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
112+
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
112113
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
113114
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
114115
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
115116
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
116117
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
117118
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
119+
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
118120
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
121+
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
119122
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
120123
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
121124
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

pkg/ec2pricing/ec2pricing.go

Lines changed: 89 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import (
88
"strings"
99
"time"
1010

11-
"github.com/aws/aws-sdk-go/aws"
1211
"github.com/aws/aws-sdk-go/aws/endpoints"
12+
"go.uber.org/multierr"
13+
14+
"github.com/aws/aws-sdk-go/aws"
1315
"github.com/aws/aws-sdk-go/aws/session"
1416
"github.com/aws/aws-sdk-go/service/ec2"
1517
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
@@ -25,19 +27,25 @@ const (
2527

2628
// EC2Pricing is the public struct to interface with AWS pricing APIs
2729
type EC2Pricing struct {
28-
PricingClient pricingiface.PricingAPI
29-
EC2Client ec2iface.EC2API
30-
AWSSession *session.Session
31-
cache map[string]float64
32-
spotCache map[string]map[string][]spotPricingEntry
30+
PricingClient pricingiface.PricingAPI
31+
EC2Client ec2iface.EC2API
32+
AWSSession *session.Session
33+
onDemandCache map[string]float64
34+
spotCache map[string]map[string][]spotPricingEntry
35+
lastOnDemandCacheUTC *time.Time // Updated on successful cache write
36+
lastSpotCacheUTC *time.Time // Updated on successful cache write
3337
}
3438

3539
// EC2PricingIface is the EC2Pricing interface mainly used to mock out ec2pricing during testing
3640
type EC2PricingIface interface {
3741
GetOndemandInstanceTypeCost(instanceType string) (float64, error)
3842
GetSpotInstanceTypeNDayAvgCost(instanceType string, availabilityZones []string, days int) (float64, error)
43+
// Keep hydrate functions thread safe by keeping different write data points
44+
// In simple words, make sure they don't write the same variable/file/row etc. which they don't (they have different cache maps)
3945
HydrateOndemandCache() error
4046
HydrateSpotCache(days int) error
47+
LastOnDemandCacheUTC() *time.Time
48+
LastSpotCacheUTC() *time.Time
4149
}
4250

4351
type spotPricingEntry struct {
@@ -49,12 +57,26 @@ type spotPricingEntry struct {
4957
func New(sess *session.Session) *EC2Pricing {
5058
return &EC2Pricing{
5159
// use us-east-1 since pricing only has endpoints in us-east-1 and ap-south-1
52-
PricingClient: pricing.New(sess.Copy(aws.NewConfig().WithRegion("us-east-1"))),
53-
EC2Client: ec2.New(sess),
54-
AWSSession: sess,
60+
PricingClient: pricing.New(sess.Copy(aws.NewConfig().WithRegion("us-east-1"))),
61+
EC2Client: ec2.New(sess),
62+
AWSSession: sess,
63+
lastOnDemandCacheUTC: nil,
64+
lastSpotCacheUTC: nil,
5565
}
5666
}
5767

68+
// LastOnDemandCacheUTC returns the UTC timestamp when the onDemandCache was last refreshed
69+
// Returns nil if the onDemandCache has not been initialized
70+
func (p *EC2Pricing) LastOnDemandCacheUTC() *time.Time {
71+
return p.lastOnDemandCacheUTC
72+
}
73+
74+
// LastSpotCacheUTC returns the UTC timestamp when the spotCache was last refreshed
75+
// Returns nil if the spotCache has not been initialized
76+
func (p *EC2Pricing) LastSpotCacheUTC() *time.Time {
77+
return p.lastSpotCacheUTC
78+
}
79+
5880
// GetSpotInstanceTypeNDayAvgCost retrieves the spot price history for a given AZ from the past N days and averages the price
5981
// Passing an empty list for availabilityZones will retrieve avg cost for all AZs in the current AWSSession's region
6082
func (p *EC2Pricing) GetSpotInstanceTypeNDayAvgCost(instanceType string, availabilityZones []string, days int) (float64, error) {
@@ -67,28 +89,31 @@ func (p *EC2Pricing) GetSpotInstanceTypeNDayAvgCost(instanceType string, availab
6789
EndTime: &endTime,
6890
InstanceTypes: []*string{&instanceType},
6991
}
70-
zoneToPriceEntries := map[string][]spotPricingEntry{}
92+
zoneToPriceEntries := make(map[string][]spotPricingEntry)
7193

7294
if _, ok := p.spotCache[instanceType]; !ok {
7395
var processingErr error
74-
err := p.EC2Client.DescribeSpotPriceHistoryPages(&spotPriceHistInput, func(dspho *ec2.DescribeSpotPriceHistoryOutput, b bool) bool {
96+
errAPI := p.EC2Client.DescribeSpotPriceHistoryPages(&spotPriceHistInput, func(dspho *ec2.DescribeSpotPriceHistoryOutput, b bool) bool {
7597
for _, history := range dspho.SpotPriceHistory {
7698
var spotPrice float64
77-
spotPrice, processingErr = strconv.ParseFloat(*history.SpotPrice, 64)
99+
spotPrice, errParse := strconv.ParseFloat(*history.SpotPrice, 64)
100+
if errParse != nil {
101+
processingErr = multierr.Append(processingErr, errParse)
102+
continue
103+
}
78104
zone := *history.AvailabilityZone
79-
80105
zoneToPriceEntries[zone] = append(zoneToPriceEntries[zone], spotPricingEntry{
81106
Timestamp: *history.Timestamp,
82107
SpotPrice: spotPrice,
83108
})
84109
}
85110
return true
86111
})
87-
if err != nil {
88-
return float64(0), err
112+
if errAPI != nil {
113+
return float64(-1), errAPI
89114
}
90115
if processingErr != nil {
91-
return float64(0), processingErr
116+
return float64(-1), processingErr
92117
}
93118
} else {
94119
for zone, priceEntries := range p.spotCache[instanceType] {
@@ -113,7 +138,7 @@ func (p *EC2Pricing) GetSpotInstanceTypeNDayAvgCost(instanceType string, availab
113138
aggregateZonePriceSum += p.calculateSpotAggregate(priceEntries)
114139
}
115140

116-
return (aggregateZonePriceSum / float64(numOfZones)), nil
141+
return aggregateZonePriceSum / float64(numOfZones), nil
117142
}
118143

119144
func (p *EC2Pricing) calculateSpotAggregate(spotPriceEntries []spotPricingEntry) float64 {
@@ -134,11 +159,16 @@ func (p *EC2Pricing) calculateSpotAggregate(spotPriceEntries []spotPricingEntry)
134159
duration := spotPriceEntries[int(math.Max(float64(i-1), 0))].Timestamp.Sub(entry.Timestamp).Minutes()
135160
priceSum += duration * entry.SpotPrice
136161
}
137-
return (priceSum / totalDuration)
162+
return priceSum / totalDuration
138163
}
139164

140165
// GetOndemandInstanceTypeCost retrieves the on-demand hourly cost for the specified instance type
141166
func (p *EC2Pricing) GetOndemandInstanceTypeCost(instanceType string) (float64, error) {
167+
// Check cache first and return it if available
168+
if price, ok := p.onDemandCache[instanceType]; ok {
169+
return price, nil
170+
}
171+
142172
regionDescription := p.getRegionForPricingAPI()
143173
// TODO: mac.metal instances cannot be found with the below filters
144174
productInput := pricing.GetProductsInput{
@@ -154,25 +184,25 @@ func (p *EC2Pricing) GetOndemandInstanceTypeCost(instanceType string) (float64,
154184
},
155185
}
156186

157-
// Check cache first and return it if available
158-
if price, ok := p.cache[instanceType]; ok {
159-
return price, nil
160-
}
161-
162187
pricePerUnitInUSD := float64(-1)
163-
err := p.PricingClient.GetProductsPages(&productInput, func(pricingOutput *pricing.GetProductsOutput, nextPage bool) bool {
164-
var err error
188+
var processingErr error
189+
errAPI := p.PricingClient.GetProductsPages(&productInput, func(pricingOutput *pricing.GetProductsOutput, nextPage bool) bool {
190+
var errParse error
165191
for _, priceDoc := range pricingOutput.PriceList {
166-
_, pricePerUnitInUSD, err = parseOndemandUnitPrice(priceDoc)
167-
}
168-
if err != nil {
169-
// keep going through pages if we can't parse the pricing doc
170-
return true
192+
_, pricePerUnitInUSD, errParse = parseOndemandUnitPrice(priceDoc)
193+
if errParse != nil {
194+
processingErr = multierr.Append(processingErr, errParse)
195+
// keep going through pages if we can't parse the pricing doc
196+
return true
197+
}
171198
}
172199
return false
173200
})
174-
if err != nil {
175-
return -1, err
201+
if errAPI != nil {
202+
return -1, errAPI
203+
}
204+
if processingErr != nil {
205+
return -1, processingErr
176206
}
177207
return pricePerUnitInUSD, nil
178208
}
@@ -182,7 +212,7 @@ func (p *EC2Pricing) GetOndemandInstanceTypeCost(instanceType string) (float64,
182212
// There is no TTL on cache entries
183213
// You'll only want to use this if you don't mind a long startup time (around 30 seconds) and will query the cache often after that.
184214
func (p *EC2Pricing) HydrateSpotCache(days int) error {
185-
newCache := map[string]map[string][]spotPricingEntry{}
215+
newCache := make(map[string]map[string][]spotPricingEntry)
186216

187217
endTime := time.Now().UTC()
188218
startTime := endTime.Add(time.Hour * time.Duration(24*-1*days))
@@ -192,14 +222,17 @@ func (p *EC2Pricing) HydrateSpotCache(days int) error {
192222
EndTime: &endTime,
193223
}
194224
var processingErr error
195-
err := p.EC2Client.DescribeSpotPriceHistoryPages(&spotPriceHistInput, func(dspho *ec2.DescribeSpotPriceHistoryOutput, b bool) bool {
225+
errAPI := p.EC2Client.DescribeSpotPriceHistoryPages(&spotPriceHistInput, func(dspho *ec2.DescribeSpotPriceHistoryOutput, b bool) bool {
196226
for _, history := range dspho.SpotPriceHistory {
197-
var spotPrice float64
198-
spotPrice, processingErr = strconv.ParseFloat(*history.SpotPrice, 64)
227+
spotPrice, errFloat := strconv.ParseFloat(*history.SpotPrice, 64)
228+
if errFloat != nil {
229+
processingErr = multierr.Append(processingErr, errFloat)
230+
continue
231+
}
199232
instanceType := *history.InstanceType
200233
zone := *history.AvailabilityZone
201234
if _, ok := newCache[instanceType]; !ok {
202-
newCache[instanceType] = map[string][]spotPricingEntry{}
235+
newCache[instanceType] = make(map[string][]spotPricingEntry)
203236
}
204237
newCache[instanceType][zone] = append(newCache[instanceType][zone], spotPricingEntry{
205238
Timestamp: *history.Timestamp,
@@ -208,20 +241,21 @@ func (p *EC2Pricing) HydrateSpotCache(days int) error {
208241
}
209242
return true
210243
})
211-
if err != nil {
212-
return err
244+
if errAPI != nil {
245+
return errAPI
213246
}
247+
cTime := time.Now().UTC()
214248
p.spotCache = newCache
249+
p.lastSpotCacheUTC = &cTime
215250
return processingErr
216251
}
217252

218253
// HydrateOndemandCache makes a bulk request to the pricing api to retrieve all instance type pricing and stores them in a local cache
219254
// If HydrateOndemandCache is called more than once, the cache will be fully refreshed
220255
// There is no TTL on cache entries
221256
func (p *EC2Pricing) HydrateOndemandCache() error {
222-
if p.cache == nil {
223-
p.cache = make(map[string]float64)
224-
}
257+
newOnDemandCache := make(map[string]float64)
258+
225259
regionDescription := p.getRegionForPricingAPI()
226260
productInput := pricing.GetProductsInput{
227261
ServiceCode: aws.String(serviceCode),
@@ -234,17 +268,25 @@ func (p *EC2Pricing) HydrateOndemandCache() error {
234268
{Type: aws.String(pricing.FilterTypeTermMatch), Field: aws.String("tenancy"), Value: aws.String("shared")},
235269
},
236270
}
237-
err := p.PricingClient.GetProductsPages(&productInput, func(pricingOutput *pricing.GetProductsOutput, nextPage bool) bool {
271+
var processingErr error
272+
errAPI := p.PricingClient.GetProductsPages(&productInput, func(pricingOutput *pricing.GetProductsOutput, nextPage bool) bool {
238273
for _, priceDoc := range pricingOutput.PriceList {
239-
instanceTypeName, price, err := parseOndemandUnitPrice(priceDoc)
240-
if err != nil {
274+
instanceTypeName, price, errParse := parseOndemandUnitPrice(priceDoc)
275+
if errParse != nil {
276+
processingErr = multierr.Append(processingErr, errParse)
241277
continue
242278
}
243-
p.cache[instanceTypeName] = price
279+
newOnDemandCache[instanceTypeName] = price
244280
}
245281
return true
246282
})
247-
return err
283+
if errAPI != nil {
284+
return errAPI
285+
}
286+
cTime := time.Now().UTC()
287+
p.onDemandCache = newOnDemandCache
288+
p.lastOnDemandCacheUTC = &cTime
289+
return processingErr
248290
}
249291

250292
// getRegionForPricingAPI attempts to retrieve the region description based on the AWS session used to create

0 commit comments

Comments
 (0)