sumomo 0.8.12 → 0.8.15
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 +12 -11
- data/data/sumomo/custom_resources/ACMCertificate.js +32 -1
- data/data/sumomo/custom_resources/ACMCertificateWaiter.js +4 -0
- data/data/sumomo/custom_resources/AMILookup.js +378 -150
- data/data/sumomo/custom_resources/SelectSpot.js +376 -142
- data/exe/sumomo +4 -1
- data/lib/sumomo/ec2.rb +5 -1
- data/lib/sumomo/stack.rb +7 -4
- data/lib/sumomo/version.rb +1 -1
- data/lib/sumomo.rb +2 -1
- data/sumomo.gemspec +6 -4
- data/update-spec.rb +381 -0
- metadata +45 -16
data/lib/sumomo/ec2.rb
CHANGED
@@ -96,6 +96,7 @@ module Sumomo
|
|
96
96
|
call('Fn::Join', '', [
|
97
97
|
|
98
98
|
"#!/bin/bash -v\n",
|
99
|
+
"yum install -y aws-cfn-bootstrap\n",
|
99
100
|
"yum update -y aws-cfn-bootstrap\n",
|
100
101
|
|
101
102
|
"# Helper function\n",
|
@@ -267,6 +268,7 @@ module Sumomo
|
|
267
268
|
docker_password: '',
|
268
269
|
eip: nil,
|
269
270
|
policies: [],
|
271
|
+
scalein_protection: true,
|
270
272
|
&block
|
271
273
|
)
|
272
274
|
|
@@ -463,6 +465,8 @@ module Sumomo
|
|
463
465
|
|
464
466
|
VPCZoneIdentifier subnet_ids
|
465
467
|
|
468
|
+
NewInstancesProtectedFromScaleIn scalein_protection
|
469
|
+
|
466
470
|
LaunchConfigurationName launch_config
|
467
471
|
LoadBalancerNames [elb] if elb
|
468
472
|
|
@@ -510,7 +514,7 @@ module Sumomo
|
|
510
514
|
|
511
515
|
if ecs_cluster
|
512
516
|
ecs_config = <<~CONFIG
|
513
|
-
ECS_CLUSTER={{cluster_name}}
|
517
|
+
ECS_CLUSTER={{ cluster_name }}
|
514
518
|
ECS_ENGINE_AUTH_TYPE=docker
|
515
519
|
ECS_ENGINE_AUTH_DATA={"https://index.docker.io/v1/":{"username":"{{docker_username}}","password":"{{docker_password}}","email":"{{docker_email}}"}}
|
516
520
|
CONFIG
|
data/lib/sumomo/stack.rb
CHANGED
@@ -23,9 +23,10 @@ module Sumomo
|
|
23
23
|
description: "Lambda Function in #{@bucket_name}",
|
24
24
|
function_key: "cloudformation/lambda/function_#{name}",
|
25
25
|
handler: 'index.handler',
|
26
|
-
runtime: '
|
26
|
+
runtime: 'nodejs14.x',
|
27
27
|
memory_size: 128,
|
28
28
|
timeout: 30,
|
29
|
+
enable_logging: true,
|
29
30
|
role: nil)
|
30
31
|
|
31
32
|
name ||= make_default_resource_name('Lambda')
|
@@ -59,9 +60,11 @@ module Sumomo
|
|
59
60
|
Role role.Arn
|
60
61
|
end
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
63
|
+
if enable_logging
|
64
|
+
make 'AWS::Logs::LogGroup', name: "#{name}LogGroup" do
|
65
|
+
LogGroupName call('Fn::Join', '', ['/aws/lambda/', fun])
|
66
|
+
RetentionInDays 30
|
67
|
+
end
|
65
68
|
end
|
66
69
|
|
67
70
|
fun
|
data/lib/sumomo/version.rb
CHANGED
data/lib/sumomo.rb
CHANGED
@@ -117,7 +117,8 @@ module Sumomo
|
|
117
117
|
stack_name: name,
|
118
118
|
template_url: store.url('cloudformation/template'),
|
119
119
|
parameters: hidden_values,
|
120
|
-
|
120
|
+
disable_rollback: false,
|
121
|
+
capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']
|
121
122
|
}
|
122
123
|
|
123
124
|
begin
|
data/sumomo.gemspec
CHANGED
@@ -27,15 +27,17 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ['lib']
|
29
29
|
|
30
|
-
spec.add_development_dependency 'bundler'
|
31
|
-
spec.add_development_dependency 'rake'
|
32
|
-
spec.add_development_dependency 'rspec'
|
30
|
+
spec.add_development_dependency 'bundler'
|
31
|
+
spec.add_development_dependency 'rake'
|
32
|
+
spec.add_development_dependency 'rspec'
|
33
33
|
|
34
34
|
spec.add_dependency 'activesupport'
|
35
|
-
spec.add_dependency 'aws-sdk', '~>
|
35
|
+
spec.add_dependency 'aws-sdk', '~> 3'
|
36
|
+
spec.add_dependency 'ox'
|
36
37
|
spec.add_dependency 'hashie'
|
37
38
|
spec.add_dependency 'momo', '0.4.1'
|
38
39
|
spec.add_dependency 'rubyzip'
|
39
40
|
spec.add_dependency 's3cabinet'
|
40
41
|
spec.add_dependency 'trollop'
|
42
|
+
spec.add_dependency 'webrick'
|
41
43
|
end
|
data/update-spec.rb
ADDED
@@ -0,0 +1,381 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
puts "Updates the data in the custom_resources folder"
|
4
|
+
|
5
|
+
`aws ec2 describe-instance-types > itypes`
|
6
|
+
|
7
|
+
data = JSON.parse(File.read('itypes'))
|
8
|
+
|
9
|
+
start = {
|
10
|
+
a: 2.0,
|
11
|
+
c: 2.5,
|
12
|
+
d: 2.0,
|
13
|
+
f: 2.0,
|
14
|
+
g: 2.0,
|
15
|
+
h: 2.0,
|
16
|
+
i: 3.0,
|
17
|
+
m: 2.0,
|
18
|
+
p: 2.0,
|
19
|
+
r: 2.0,
|
20
|
+
t: 1.0,
|
21
|
+
v: 2.0,
|
22
|
+
x: 2.0,
|
23
|
+
z: 2.0
|
24
|
+
}
|
25
|
+
|
26
|
+
types = []
|
27
|
+
types2 = []
|
28
|
+
data['InstanceTypes'].each do |info|
|
29
|
+
|
30
|
+
virt = 'pv'
|
31
|
+
if info["GpuInfo"]
|
32
|
+
virt = 'ecs-gpu-hvm'
|
33
|
+
elsif info["SupportedVirtualizationTypes"].last == 'hvm'
|
34
|
+
virt = 'ecs-hvm'
|
35
|
+
end
|
36
|
+
|
37
|
+
mount = 'ebs'
|
38
|
+
|
39
|
+
arch = info["ProcessorInfo"]["SupportedArchitectures"].last
|
40
|
+
|
41
|
+
raise 'UNKNOWN ARCH COMBINATION' if arch == 'arm64' && mount == 's3'
|
42
|
+
|
43
|
+
pattern = "amzn2-ami-#{virt}-2.0.*#{arch}-#{mount}"
|
44
|
+
|
45
|
+
if arch == 'x86_64_mac'
|
46
|
+
pattern = 'amzn-ec2-macos-*'
|
47
|
+
end
|
48
|
+
|
49
|
+
gennum = /[0-9]/.match(info['InstanceType']).to_s.to_i
|
50
|
+
ecu = (( start[ info['InstanceType'][0].to_sym ] + gennum.to_f * 0.5) * (info['VCpuInfo']['DefaultVCpus']).to_f * 1024).floor
|
51
|
+
types << " '#{info['InstanceType']}': { cpu: #{info['VCpuInfo']['DefaultVCpus'] * 1024}, memory: #{info["MemoryInfo"]["SizeInMiB"]}, gen: #{gennum}, ecu: #{ecu}, arch: '#{info["ProcessorInfo"]["SupportedArchitectures"].last}' }"
|
52
|
+
types2 << " '#{info['InstanceType']}': { pattern: '#{pattern}' }"
|
53
|
+
end
|
54
|
+
|
55
|
+
def align(arr)
|
56
|
+
result = []
|
57
|
+
lengths = arr[0].split(':').map{|x| x.length}
|
58
|
+
|
59
|
+
arr.each do |x|
|
60
|
+
tokens = x.split(':')
|
61
|
+
tokens.each_with_index do |y,i|
|
62
|
+
if y.length > lengths[i]
|
63
|
+
lengths[i] = y.length
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
arr.each do |x|
|
69
|
+
tokens = x.split(':')
|
70
|
+
newtokens = []
|
71
|
+
tokens.each_with_index do |y,i|
|
72
|
+
newtokens << (' ' * (lengths[i] - y.length) + y)
|
73
|
+
end
|
74
|
+
result << newtokens.join(':')
|
75
|
+
end
|
76
|
+
|
77
|
+
result
|
78
|
+
end
|
79
|
+
|
80
|
+
types.sort!
|
81
|
+
types2.sort!
|
82
|
+
|
83
|
+
types = align(types)
|
84
|
+
types2 = align(types2)
|
85
|
+
|
86
|
+
selectspot = <<~SELECTSPOT
|
87
|
+
var ec2 = new aws.EC2({region: request.ResourceProperties.Region});
|
88
|
+
|
89
|
+
if (request.RequestType == "Delete")
|
90
|
+
{
|
91
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, {}, "Success");
|
92
|
+
return;
|
93
|
+
}
|
94
|
+
|
95
|
+
var prices = []
|
96
|
+
|
97
|
+
var exclude_string = request.ResourceProperties.ExcludeString || ""
|
98
|
+
var look_back = request.ResourceProperties.LookBack;
|
99
|
+
var want_to_pay = request.ResourceProperties.TargetPrice;
|
100
|
+
|
101
|
+
var exclude = exclude_string.split(",")
|
102
|
+
|
103
|
+
var typeToCapability = {
|
104
|
+
#{types.join(",\n")}
|
105
|
+
}
|
106
|
+
|
107
|
+
function pick_lowest_prices(prices)
|
108
|
+
{
|
109
|
+
var item_map = {};
|
110
|
+
|
111
|
+
for(var i in prices)
|
112
|
+
{
|
113
|
+
var price = prices[i];
|
114
|
+
if (!item_map[price.type] || item_map[price.type].price > price.price)
|
115
|
+
{
|
116
|
+
item_map[price.type] = price;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
var result = [];
|
121
|
+
|
122
|
+
for(var i in item_map)
|
123
|
+
{
|
124
|
+
result.push(item_map[i]);
|
125
|
+
}
|
126
|
+
|
127
|
+
return result;
|
128
|
+
}
|
129
|
+
|
130
|
+
function score_type(type)
|
131
|
+
{
|
132
|
+
return (typeToCapability[type].cpu + typeToCapability[type].memory) * (1 + typeToCapability[type].gen * 0.05);
|
133
|
+
}
|
134
|
+
|
135
|
+
function difference(v1, v2)
|
136
|
+
{
|
137
|
+
return Math.abs(v1-v2);
|
138
|
+
}
|
139
|
+
|
140
|
+
function complete()
|
141
|
+
{
|
142
|
+
prices = pick_lowest_prices(prices);
|
143
|
+
|
144
|
+
prices.sort(function(a,b) {
|
145
|
+
if (a.price < b.price) {
|
146
|
+
return -1;
|
147
|
+
}
|
148
|
+
if (a.price > b.price) {
|
149
|
+
return 1;
|
150
|
+
}
|
151
|
+
return 0;
|
152
|
+
});
|
153
|
+
|
154
|
+
var index = 0;
|
155
|
+
for(var i in prices)
|
156
|
+
{
|
157
|
+
index = i;
|
158
|
+
if(prices[i].price > want_to_pay)
|
159
|
+
{
|
160
|
+
break;
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
|
165
|
+
shortlist = prices.splice(0, index)
|
166
|
+
|
167
|
+
var result = prices[0];
|
168
|
+
|
169
|
+
if (shortlist.length != 0)
|
170
|
+
{
|
171
|
+
shortlist.sort(function(a,b) {
|
172
|
+
if (score_type(a.type) > score_type(b.type)) {
|
173
|
+
return -1;
|
174
|
+
}
|
175
|
+
if (score_type(a.type) < score_type(b.type)) {
|
176
|
+
return 1;
|
177
|
+
}
|
178
|
+
return 0;
|
179
|
+
});
|
180
|
+
|
181
|
+
//for(var i in shortlist)
|
182
|
+
//{
|
183
|
+
// console.log(shortlist[i]);
|
184
|
+
//}
|
185
|
+
result = shortlist[0];
|
186
|
+
}
|
187
|
+
|
188
|
+
response = {
|
189
|
+
Price: String(result.price + 0.0001),
|
190
|
+
Zone: String(result.zone),
|
191
|
+
AveragePrice: String(result.average_price),
|
192
|
+
HighLine: String(result.high_line),
|
193
|
+
HighTime: String(result.high_time),
|
194
|
+
LowLine: String(result.low_line),
|
195
|
+
LowTime: String(result.low_time),
|
196
|
+
CpuUnits: String(typeToCapability[result.type].cpu),
|
197
|
+
CpuRating: String(typeToCapability[result.type].ecu),
|
198
|
+
MemRating: String(typeToCapability[result.type].memory)
|
199
|
+
}
|
200
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, response, "Success", result.type);
|
201
|
+
|
202
|
+
console.log(JSON.stringify(prices));
|
203
|
+
|
204
|
+
}
|
205
|
+
|
206
|
+
function get_spot_prices(zones)
|
207
|
+
{
|
208
|
+
var newest_price={};
|
209
|
+
var xtotal={};
|
210
|
+
var xcount={};
|
211
|
+
var xhigh={};
|
212
|
+
var xlow={};
|
213
|
+
var xhightime={};
|
214
|
+
var xlowtime={};
|
215
|
+
|
216
|
+
function collect_data(zone, remaining, next_token)
|
217
|
+
{
|
218
|
+
if (remaining === 0 || next_token === null)
|
219
|
+
{
|
220
|
+
for(var type in newest_price)
|
221
|
+
{
|
222
|
+
var included = true;
|
223
|
+
for(var i=0;i<exclude.length;i++)
|
224
|
+
{
|
225
|
+
if(type.indexOf(exclude[i]) != -1)
|
226
|
+
{
|
227
|
+
included = false;
|
228
|
+
break;
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
if (!included)
|
233
|
+
{
|
234
|
+
continue;
|
235
|
+
}
|
236
|
+
|
237
|
+
prices.push({
|
238
|
+
type: type,
|
239
|
+
price: newest_price[type],
|
240
|
+
zone: zones[0],
|
241
|
+
average_price: xtotal[type]/xcount[type],
|
242
|
+
high_line: xhigh[type],
|
243
|
+
high_time: xhightime[type],
|
244
|
+
low_line: xlow[type],
|
245
|
+
low_time: xlowtime[type],
|
246
|
+
});
|
247
|
+
}
|
248
|
+
|
249
|
+
zones.splice(0,1);
|
250
|
+
get_spot_prices(zones);
|
251
|
+
}
|
252
|
+
else
|
253
|
+
{
|
254
|
+
ec2.describeSpotPriceHistory({
|
255
|
+
AvailabilityZone: zone,
|
256
|
+
MaxResults: 1000,
|
257
|
+
NextToken: next_token
|
258
|
+
}, function(err, data) {
|
259
|
+
if (err)
|
260
|
+
{
|
261
|
+
console.log(err, err.stack); // an error occurred
|
262
|
+
Cloudformation.send(request, context, Cloudformation.FAILED, {}, JSON.stringify(err));
|
263
|
+
}
|
264
|
+
else
|
265
|
+
{
|
266
|
+
|
267
|
+
//console.log(JSON.stringify(data.SpotPriceHistory))
|
268
|
+
for(var i=0;i<data.SpotPriceHistory.length;i++)
|
269
|
+
{
|
270
|
+
var history = data.SpotPriceHistory[i];
|
271
|
+
|
272
|
+
if (!newest_price[history.InstanceType])
|
273
|
+
{
|
274
|
+
newest_price[history.InstanceType] = Number(history.SpotPrice);
|
275
|
+
xtotal[history.InstanceType] = Number(history.SpotPrice);
|
276
|
+
xcount[history.InstanceType] = 1;
|
277
|
+
xhigh[history.InstanceType] = Number(history.SpotPrice);
|
278
|
+
xlow[history.InstanceType] = Number(history.SpotPrice);
|
279
|
+
xhightime[history.InstanceType] = history.Timestamp;
|
280
|
+
xlowtime[history.InstanceType] = history.Timestamp;
|
281
|
+
}
|
282
|
+
else
|
283
|
+
{
|
284
|
+
xtotal[history.InstanceType]+= Number(history.SpotPrice);
|
285
|
+
xcount[history.InstanceType]+= 1;
|
286
|
+
if (xhigh[history.InstanceType] < Number(history.SpotPrice))
|
287
|
+
{
|
288
|
+
xhigh[history.InstanceType] = Number(history.SpotPrice);
|
289
|
+
xhightime[history.InstanceType] = history.Timestamp;
|
290
|
+
}
|
291
|
+
if (xlow[history.InstanceType] > Number(history.SpotPrice))
|
292
|
+
{
|
293
|
+
xlow[history.InstanceType] = Number(history.SpotPrice);
|
294
|
+
xlowtime[history.InstanceType] = history.Timestamp;
|
295
|
+
}
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
collect_data(zone, remaining-1, data.NextToken);
|
300
|
+
}
|
301
|
+
});
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
305
|
+
if (zones.length == 0)
|
306
|
+
{
|
307
|
+
complete();
|
308
|
+
}
|
309
|
+
else
|
310
|
+
{
|
311
|
+
collect_data(zones[0], look_back);
|
312
|
+
}
|
313
|
+
}
|
314
|
+
|
315
|
+
ec2.describeAvailabilityZones({}, function(err, data) {
|
316
|
+
if (err)
|
317
|
+
{
|
318
|
+
console.log(err, err.stack); // an error occurred
|
319
|
+
Cloudformation.send(request, context, Cloudformation.FAILED, {}, JSON.stringify(err));
|
320
|
+
}
|
321
|
+
else
|
322
|
+
{
|
323
|
+
var zones = data.AvailabilityZones.map(function(x) {return x.ZoneName;});
|
324
|
+
get_spot_prices(zones);
|
325
|
+
}
|
326
|
+
});
|
327
|
+
|
328
|
+
SELECTSPOT
|
329
|
+
|
330
|
+
lookup = <<~AMILOOKUP
|
331
|
+
var typeToPattern = {
|
332
|
+
#{types2.join(",\n")}
|
333
|
+
}
|
334
|
+
|
335
|
+
var ec2 = new aws.EC2({region: request.ResourceProperties.Region});
|
336
|
+
|
337
|
+
var describeImagesParams = {
|
338
|
+
Filters: [{ Name: "name", Values: [typeToPattern[request.ResourceProperties.InstanceType].pattern]}],
|
339
|
+
Owners: [request.ResourceProperties.Architecture == "HVMG2" ? "679593333241" : "amazon"]
|
340
|
+
};
|
341
|
+
|
342
|
+
// Check if the image is a beta or rc image. The Lambda function won't return any of those images.
|
343
|
+
function isBeta(imageName) {
|
344
|
+
return imageName.toLowerCase().indexOf("beta") > -1 || imageName.toLowerCase().indexOf(".rc") > -1;
|
345
|
+
}
|
346
|
+
|
347
|
+
// Get AMI IDs with the specified name pattern and owner
|
348
|
+
ec2.describeImages(describeImagesParams, function(err, describeImagesResult)
|
349
|
+
{
|
350
|
+
if (err)
|
351
|
+
{
|
352
|
+
console.log(err, err.stack); // an error occurred
|
353
|
+
Cloudformation.send(request, context, Cloudformation.FAILED, {}, JSON.stringify(err));
|
354
|
+
}
|
355
|
+
else
|
356
|
+
{
|
357
|
+
var response = {}
|
358
|
+
var id = "NONE";
|
359
|
+
var images = describeImagesResult.Images;
|
360
|
+
// Sort images by name in descending order. The names contain the AMI version, formatted as YYYY.MM.Ver.
|
361
|
+
images.sort(function(x, y) { return y.Name.localeCompare(x.Name); });
|
362
|
+
for (var j = 0; j < images.length; j++)
|
363
|
+
{
|
364
|
+
if (isBeta(images[j].Name)) continue;
|
365
|
+
id = images[j].ImageId;
|
366
|
+
response["Name"] = images[j].Name;
|
367
|
+
response["ImageType"] = images[j].ImageType;
|
368
|
+
response["CreationDate"] = images[j].CreationDate;
|
369
|
+
response["Description"] = images[j].Description;
|
370
|
+
response["RootDeviceType"] = images[j].RootDeviceType;
|
371
|
+
response["RootDeviceName"] = images[j].RootDeviceName;
|
372
|
+
response["VirtualizationType"] = images[j].VirtualizationType;
|
373
|
+
break;
|
374
|
+
}
|
375
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, response, "Success", id);
|
376
|
+
}
|
377
|
+
});
|
378
|
+
AMILOOKUP
|
379
|
+
|
380
|
+
File.write('data/sumomo/custom_resources/SelectSpot.js', selectspot);
|
381
|
+
File.write('data/sumomo/custom_resources/AMILookup.js', lookup);
|
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sumomo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Siaw
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: activesupport
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +72,28 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '3'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: ox
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: hashie
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,6 +164,20 @@ dependencies:
|
|
150
164
|
- - ">="
|
151
165
|
- !ruby/object:Gem::Version
|
152
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: webrick
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
153
181
|
description: Sumomo
|
154
182
|
email:
|
155
183
|
- davidsiaw@gmail.com
|
@@ -2544,6 +2572,7 @@ files:
|
|
2544
2572
|
- lib/sumomo/stack.rb
|
2545
2573
|
- lib/sumomo/version.rb
|
2546
2574
|
- sumomo.gemspec
|
2575
|
+
- update-spec.rb
|
2547
2576
|
homepage: https://github.com/davidsiaw/sumomo
|
2548
2577
|
licenses: []
|
2549
2578
|
metadata:
|