sumomo 0.8.3 → 0.8.9
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 +5 -5
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +169 -0
- data/Gemfile +2 -0
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/data/sumomo/api_modules/node_modules/.bin/uuid +1 -1
- data/data/sumomo/api_modules/real_script.js +37 -1
- data/data/sumomo/api_modules/test_script.js +212 -175
- data/data/sumomo/custom_resources/AMILookup.js +146 -58
- data/data/sumomo/custom_resources/Echo.js +25 -0
- data/data/sumomo/custom_resources/SelectSpot.js +142 -54
- data/data/sumomo/custom_resources/TempS3Bucket.js +213 -0
- data/data/sumomo/custom_resources/USEastCertificate.js +44 -31
- data/data/sumomo/custom_resources/USEastCertificateWaiter.js +60 -0
- data/exe/sumomo +50 -41
- data/lib/sumomo.rb +236 -233
- data/lib/sumomo/api.rb +148 -151
- data/lib/sumomo/cdn.rb +119 -113
- data/lib/sumomo/dns.rb +20 -20
- data/lib/sumomo/ec2.rb +490 -491
- data/lib/sumomo/ecs.rb +256 -262
- data/lib/sumomo/irregular.rb +9 -0
- data/lib/sumomo/momo_extensions/resource.rb +8 -7
- data/lib/sumomo/momo_extensions/stack.rb +4 -3
- data/lib/sumomo/network.rb +109 -106
- data/lib/sumomo/stack.rb +191 -189
- data/lib/sumomo/version.rb +3 -1
- data/sumomo.gemspec +23 -22
- metadata +27 -22
@@ -1,5 +1,7 @@
|
|
1
1
|
var acm = new aws.ACM({region: "us-east-1"}); // MUST be us-east-1.
|
2
2
|
|
3
|
+
var return_properties = {};
|
4
|
+
|
3
5
|
function extractRootDomain(domain)
|
4
6
|
{
|
5
7
|
var splitArr = domain.split('.');
|
@@ -15,39 +17,46 @@ function extractRootDomain(domain)
|
|
15
17
|
|
16
18
|
function wait_for_approval(domain_name, on_success, on_fail)
|
17
19
|
{
|
18
|
-
|
20
|
+
get_domain(domain_name, function(data)
|
19
21
|
{
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
var params = {
|
23
|
+
CertificateArn: data.arn
|
24
|
+
};
|
25
|
+
|
26
|
+
acm.describeCertificate(params, function(err, cert_data) {
|
27
|
+
if (err)
|
28
|
+
{
|
29
|
+
on_fail(err);
|
30
|
+
}
|
31
|
+
else
|
32
|
+
{
|
33
|
+
// Do not wait if we requested DNS validation
|
34
|
+
if (request.ResourceProperties.ValidationMethod === "DNS")
|
35
|
+
{
|
36
|
+
return_properties.RecordName = cert_data.Certificate.DomainValidationOptions[0].ResourceRecord.Name;
|
37
|
+
return_properties.RecordType = cert_data.Certificate.DomainValidationOptions[0].ResourceRecord.Type;
|
38
|
+
return_properties.RecordValue = cert_data.Certificate.DomainValidationOptions[0].ResourceRecord.Value;
|
39
|
+
return on_success(data.arn);
|
40
|
+
}
|
41
|
+
|
42
|
+
if (cert_data.Certificate.DomainValidationOptions[0].ValidationStatus === "SUCCESS")
|
43
|
+
{
|
44
|
+
on_success(data.arn);
|
45
|
+
}
|
46
|
+
else if (cert_data.Certificate.DomainValidationOptions[0].ValidationStatus === "FAILED")
|
27
47
|
{
|
28
|
-
on_fail(
|
48
|
+
on_fail("Verification Failed");
|
29
49
|
}
|
30
50
|
else
|
31
51
|
{
|
32
|
-
|
33
|
-
{
|
34
|
-
on_success(data.arn);
|
35
|
-
}
|
36
|
-
else if (cert_data.Certificate.DomainValidationOptions[0].ValidationStatus === "FAILED")
|
37
|
-
{
|
38
|
-
on_fail("Verification Failed");
|
39
|
-
}
|
40
|
-
else
|
52
|
+
setTimeout(function()
|
41
53
|
{
|
42
54
|
wait_for_approval(domain_name, on_success, on_fail);
|
43
|
-
}
|
55
|
+
}, 3000);
|
44
56
|
}
|
45
|
-
}
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
}, 3000);
|
57
|
+
}
|
58
|
+
});
|
59
|
+
}, on_fail);
|
51
60
|
}
|
52
61
|
|
53
62
|
function create(domain_name, on_success, on_fail)
|
@@ -62,6 +71,11 @@ function create(domain_name, on_success, on_fail)
|
|
62
71
|
]
|
63
72
|
}
|
64
73
|
|
74
|
+
if (request.ResourceProperties.ValidationMethod === "DNS")
|
75
|
+
{
|
76
|
+
params.ValidationMethod = "DNS";
|
77
|
+
}
|
78
|
+
|
65
79
|
console.log("Requesting Cert");
|
66
80
|
acm.requestCertificate(params, function(err, data)
|
67
81
|
{
|
@@ -129,12 +143,11 @@ function fail(err)
|
|
129
143
|
Cloudformation.send(request, context, Cloudformation.FAILED, {}, "Error: " + err);
|
130
144
|
}
|
131
145
|
|
132
|
-
|
133
146
|
if (request.RequestType == "Create")
|
134
147
|
{
|
135
148
|
create(request.ResourceProperties.DomainName, function(data)
|
136
149
|
{
|
137
|
-
Cloudformation.send(request, context, Cloudformation.SUCCESS,
|
150
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, return_properties, "Success", data);
|
138
151
|
}, fail);
|
139
152
|
}
|
140
153
|
|
@@ -144,14 +157,14 @@ if (request.RequestType == "Update")
|
|
144
157
|
{
|
145
158
|
create(request.ResourceProperties.DomainName, function(data)
|
146
159
|
{
|
147
|
-
Cloudformation.send(request, context, Cloudformation.SUCCESS,
|
160
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, return_properties, "Success", data);
|
148
161
|
}, fail);
|
149
162
|
}
|
150
163
|
else
|
151
164
|
{
|
152
165
|
get_domain(function(data)
|
153
166
|
{
|
154
|
-
Cloudformation.send(request, context, Cloudformation.SUCCESS,
|
167
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, return_properties, "Success", data.arn);
|
155
168
|
}, fail);
|
156
169
|
}
|
157
170
|
}
|
@@ -160,10 +173,10 @@ if (request.RequestType == "Delete")
|
|
160
173
|
{
|
161
174
|
destroy(request.ResourceProperties.DomainName, function()
|
162
175
|
{
|
163
|
-
Cloudformation.send(request, context, Cloudformation.SUCCESS,
|
176
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, return_properties, "Success", "(deleted)");
|
164
177
|
},
|
165
178
|
function()
|
166
179
|
{
|
167
|
-
Cloudformation.send(request, context, Cloudformation.SUCCESS,
|
180
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, return_properties, "Success", "(don't care)");
|
168
181
|
});
|
169
182
|
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
var acm = new aws.ACM({region: "us-east-1"}); // MUST be us-east-1.
|
2
|
+
|
3
|
+
var arn = request.ResourceProperties.Certificate;
|
4
|
+
|
5
|
+
function wait_for_approval(on_success, on_fail)
|
6
|
+
{
|
7
|
+
var params = {
|
8
|
+
CertificateArn: arn
|
9
|
+
};
|
10
|
+
|
11
|
+
acm.describeCertificate(params, function(err, cert_data) {
|
12
|
+
if (err)
|
13
|
+
{
|
14
|
+
on_fail(err);
|
15
|
+
}
|
16
|
+
else
|
17
|
+
{
|
18
|
+
if (cert_data.Certificate.DomainValidationOptions[0].ValidationStatus === "SUCCESS")
|
19
|
+
{
|
20
|
+
on_success();
|
21
|
+
}
|
22
|
+
else if (cert_data.Certificate.DomainValidationOptions[0].ValidationStatus === "FAILED")
|
23
|
+
{
|
24
|
+
on_fail("Verification Failed");
|
25
|
+
}
|
26
|
+
else
|
27
|
+
{
|
28
|
+
setTimeout(function()
|
29
|
+
{
|
30
|
+
wait_for_approval(on_success, on_fail);
|
31
|
+
}, 3000);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
});
|
35
|
+
}
|
36
|
+
|
37
|
+
function fail(err)
|
38
|
+
{
|
39
|
+
Cloudformation.send(request, context, Cloudformation.FAILED, {}, "Error: " + err);
|
40
|
+
}
|
41
|
+
|
42
|
+
function success()
|
43
|
+
{
|
44
|
+
Cloudformation.send(request, context, Cloudformation.SUCCESS, {}, "Success");
|
45
|
+
}
|
46
|
+
|
47
|
+
if (request.RequestType == "Create")
|
48
|
+
{
|
49
|
+
wait_for_approval(success, fail);
|
50
|
+
}
|
51
|
+
|
52
|
+
if (request.RequestType == "Update")
|
53
|
+
{
|
54
|
+
wait_for_approval(success, fail);
|
55
|
+
}
|
56
|
+
|
57
|
+
if (request.RequestType == "Delete")
|
58
|
+
{
|
59
|
+
success();
|
60
|
+
}
|
data/exe/sumomo
CHANGED
@@ -1,61 +1,70 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require 'trollop'
|
5
|
+
require 'sumomo'
|
6
|
+
require 'yaml'
|
6
7
|
|
7
|
-
SUB_COMMANDS = %w
|
8
|
-
global_opts = Trollop
|
8
|
+
SUB_COMMANDS = %w[delete create update outputs testapi].freeze
|
9
|
+
global_opts = Trollop.options do
|
9
10
|
banner <<-USAGE
|
10
11
|
Sumomo v#{Sumomo::VERSION}
|
11
12
|
|
12
13
|
Usage: sumomo [options] <create|update|delete|outputs> <stackname>
|
13
14
|
USAGE
|
14
15
|
|
15
|
-
opt :region,
|
16
|
-
opt :profile,
|
16
|
+
opt :region, 'AWS region to use', type: :string, default: 'ap-northeast-1'
|
17
|
+
opt :profile, 'AWS credential profile to use', type: :string, default: 'default'
|
17
18
|
|
18
19
|
stop_on SUB_COMMANDS
|
19
20
|
end
|
20
21
|
|
21
|
-
ENV[
|
22
|
+
ENV['AWS_PROFILE'] = global_opts[:profile]
|
22
23
|
|
23
|
-
puts
|
24
|
-
p ENV[
|
24
|
+
puts 'Using profile:'
|
25
|
+
p ENV['AWS_PROFILE']
|
25
26
|
|
26
27
|
cmd = ARGV.shift # get the subcommand
|
27
28
|
|
28
29
|
cmd_opts = case cmd
|
29
|
-
when
|
30
|
-
|
31
|
-
|
32
|
-
when
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
when
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
when
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
30
|
+
when 'delete'
|
31
|
+
Sumomo.delete_stack(name: ARGV[0], region: global_opts[:region])
|
32
|
+
|
33
|
+
when 'create', 'update'
|
34
|
+
local_opts = Trollop.options do
|
35
|
+
opt :filename, 'File that describes the stack', type: :string, default: 'Sumomofile'
|
36
|
+
end
|
37
|
+
Sumomo.create_stack(name: ARGV[0], region: global_opts[:region]) do
|
38
|
+
proc = proc {}
|
39
|
+
eval File.read(local_opts[:filename]), proc.binding, local_opts[:filename]
|
40
|
+
end
|
41
|
+
|
42
|
+
when 'outputs'
|
43
|
+
puts "Outputs for stack #{ARGV[0]}"
|
44
|
+
puts Sumomo.get_stack_outputs(name: ARGV[0], region: global_opts[:region]).to_yaml
|
45
|
+
|
46
|
+
when 'login'
|
47
|
+
puts "Login to stack #{ARGV[0]} instance at #{ARGV[1]}"
|
48
|
+
`aws s3 cp s3://#{ARGV[0]}/cloudformation/#{ARGV[0]}_master_key.pem x.txt`
|
49
|
+
key = JSON.parse(File.read('x.txt'))['value']
|
50
|
+
File.write('key.pem', key)
|
51
|
+
`chmod 0600 key.pem`
|
52
|
+
exec "ssh -i 'key.pem' ec2-user@#{ARGV[1]}"
|
53
|
+
|
54
|
+
when 'testapi'
|
55
|
+
local_opts = Trollop.options do
|
56
|
+
opt :filename, 'File that describes the stack', type: :string, default: 'Sumomofile'
|
57
|
+
opt :apiname, 'Name of the API you want to test', type: :string
|
58
|
+
opt :prettyprint, 'Test API outputs JSON with nice indentation', type: :boolean, default: true
|
59
|
+
end
|
60
|
+
puts 'API Test Mode'
|
61
|
+
Sumomo.test_api(local_opts[:apiname], local_opts[:prettyprint]) do
|
62
|
+
proc = proc {}
|
63
|
+
eval File.read(local_opts[:filename]), proc.binding, local_opts[:filename]
|
64
|
+
end
|
65
|
+
exit(0)
|
66
|
+
else
|
67
|
+
Trollop.die "Unknown subcommand #{cmd.inspect}"
|
59
68
|
end
|
60
69
|
|
61
|
-
Sumomo
|
70
|
+
Sumomo.wait_for_stack(name: ARGV[0], region: global_opts[:region])
|
data/lib/sumomo.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'momo'
|
2
4
|
require 's3cabinet'
|
3
5
|
require 'aws-sdk'
|
4
6
|
require 'zip'
|
5
7
|
require 'yaml'
|
6
8
|
|
7
|
-
require
|
9
|
+
require 'sumomo/version'
|
8
10
|
require 'sumomo/api'
|
9
11
|
require 'sumomo/cdn'
|
10
12
|
require 'sumomo/dns'
|
@@ -16,236 +18,237 @@ require 'sumomo/momo_extensions/resource'
|
|
16
18
|
require 'sumomo/momo_extensions/stack'
|
17
19
|
|
18
20
|
module Sumomo
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
21
|
+
def self.make_master_key_name(name:)
|
22
|
+
"#{name}_master_key"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.make_master_key_key(name:)
|
26
|
+
"cloudformation/#{make_master_key_name(name: name)}.pem"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.update_stack(name:, region:, sns_arn: nil, &block)
|
30
|
+
cf = Aws::CloudFormation::Client.new(region: region)
|
31
|
+
s3 = Aws::S3::Client.new(region: region)
|
32
|
+
ec2 = Aws::EC2::Client.new(region: region)
|
33
|
+
|
34
|
+
begin
|
35
|
+
s3.head_bucket(bucket: name)
|
36
|
+
rescue Aws::S3::Errors::NotFound => e
|
37
|
+
s3.create_bucket(bucket: name)
|
38
|
+
end
|
39
|
+
|
40
|
+
store = S3Cabinet::S3Cabinet.new(nil, nil, name, region)
|
41
|
+
|
42
|
+
master_key_name = make_master_key_name(name: name)
|
43
|
+
master_key_key = make_master_key_key(name: name)
|
44
|
+
|
45
|
+
unless store.get(master_key_key)
|
46
|
+
|
47
|
+
resp = nil
|
48
|
+
begin
|
49
|
+
puts 'No master key found, creating...'
|
50
|
+
resp = ec2.create_key_pair(key_name: master_key_name)
|
51
|
+
rescue StandardError
|
52
|
+
puts 'Master key conflict! Deleting old one'
|
53
|
+
ec2.delete_key_pair(key_name: master_key_name)
|
54
|
+
resp = ec2.create_key_pair(key_name: master_key_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
store.set(master_key_key, resp.key_material)
|
58
|
+
store.set("#{master_key_key}.fingerprint", resp.key_fingerprint)
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
dummy_number = store.get('cloudformation/dummy_number')
|
63
|
+
dummy_number = 0 if dummy_number.nil?
|
64
|
+
dummy_number += 1
|
65
|
+
store.set('cloudformation/dummy_number', dummy_number)
|
66
|
+
|
67
|
+
hidden_values = []
|
68
|
+
|
69
|
+
template = Momo.cfl do
|
70
|
+
inject Sumomo::Stack
|
71
|
+
|
72
|
+
@region = region
|
73
|
+
@version_number = dummy_number
|
74
|
+
@custom_resources = {}
|
75
|
+
@bucket_name = name
|
76
|
+
@store = store
|
77
|
+
@master_key_name = master_key_name
|
78
|
+
@ec2 = ec2
|
79
|
+
@cf = cf
|
80
|
+
@s3 = s3
|
81
|
+
@has_dummy = true
|
82
|
+
@dummy_vpc = nil
|
83
|
+
@timeout = nil
|
84
|
+
|
85
|
+
instance_eval(&block)
|
86
|
+
|
87
|
+
dummy_vpc = @dummy_vpc
|
88
|
+
|
89
|
+
if @has_dummy
|
90
|
+
make 'AWS::EC2::SecurityGroup', name: 'DummyResource' do
|
91
|
+
GroupDescription 'Dummy resource for tracking Cloudformation Deployment.'
|
92
|
+
VpcId dummy_vpc unless dummy_vpc.nil?
|
93
|
+
Tags [{ 'Key' => 'Name', 'Value' => "dummyfordeploy#{dummy_number}" }]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
hidden_values = @hidden_values
|
98
|
+
end.templatize
|
99
|
+
|
100
|
+
# TODO: if the template is too big, split it into nested templates
|
101
|
+
|
102
|
+
# puts JSON.parse(template).to_yaml
|
103
|
+
|
104
|
+
store.set_raw('cloudformation/template', template)
|
105
|
+
|
106
|
+
update_options = {
|
107
|
+
stack_name: name,
|
108
|
+
template_url: store.url('cloudformation/template'),
|
109
|
+
parameters: hidden_values,
|
110
|
+
capabilities: ['CAPABILITY_IAM']
|
111
|
+
}
|
112
|
+
|
113
|
+
begin
|
114
|
+
cf.update_stack(update_options)
|
115
|
+
rescue StandardError => e
|
116
|
+
if e.message.end_with? 'does not exist'
|
117
|
+
update_options[:timeout_in_minutes] = @timeout if @timeout
|
118
|
+
update_options[:notification_arns] = sns_arn if sns_arn
|
119
|
+
cf.create_stack(update_options)
|
120
|
+
else
|
121
|
+
p e
|
122
|
+
puts "Error: #{e.message}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.wait_for_stack(name:, region:)
|
128
|
+
cf = Aws::CloudFormation::Client.new(region: region)
|
129
|
+
|
130
|
+
stack_id = name
|
131
|
+
|
132
|
+
begin
|
133
|
+
resp = cf.describe_stack_events(stack_name: stack_id)
|
134
|
+
top_event = resp.stack_events[0]
|
135
|
+
top_event_id = top_event.event_id
|
136
|
+
puts "#{top_event.logical_resource_id} #{top_event.resource_status} #{top_event.resource_status_reason}"
|
137
|
+
rescue StandardError => e
|
138
|
+
puts "describe_stack_events: #{e.message}"
|
139
|
+
end
|
140
|
+
|
141
|
+
failure_count = 0
|
142
|
+
loop do
|
143
|
+
begin
|
144
|
+
unless /^arn\:/.match(stack_id)
|
145
|
+
stack_id = cf.describe_stacks(stack_name: stack_id).stacks[0].stack_id
|
146
|
+
# puts "Unique Stack ID: #{stack_id}"
|
147
|
+
end
|
148
|
+
|
149
|
+
resp = cf.describe_stack_events(stack_name: stack_id)
|
150
|
+
curr = 0
|
151
|
+
lines = []
|
152
|
+
loop do
|
153
|
+
curr_event = resp.stack_events[curr]
|
154
|
+
break if curr_event.event_id == top_event_id
|
155
|
+
|
156
|
+
lines << "#{curr_event.logical_resource_id} #{curr_event.resource_status} #{curr_event.resource_status_reason}"
|
157
|
+
break if curr == resp.stack_events.length - 1
|
158
|
+
|
159
|
+
curr += 1
|
160
|
+
end
|
161
|
+
|
162
|
+
lines.reverse.each { |x| puts x }
|
163
|
+
|
164
|
+
top_event_id = resp.stack_events[0].event_id
|
165
|
+
rescue StandardError => e
|
166
|
+
puts "describe_stack_events: #{e.message}"
|
167
|
+
failure_count += 1
|
168
|
+
break if failure_count > 5
|
169
|
+
end
|
170
|
+
|
171
|
+
sleep 1
|
172
|
+
begin
|
173
|
+
resp = cf.describe_stacks(stack_name: stack_id)
|
174
|
+
|
175
|
+
break if /(COMPLETE$)|(FAILED$)/.match(resp.stacks[0].stack_status)
|
176
|
+
rescue StandardError => e
|
177
|
+
puts "describe_stacks: #{e.message}"
|
178
|
+
break
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class APITester
|
184
|
+
attr_accessor :apis
|
185
|
+
def initialize(&block)
|
186
|
+
@apis = {}
|
187
|
+
instance_eval(&block)
|
188
|
+
end
|
189
|
+
|
190
|
+
def make_api(_domain_name, name:, script: nil, dns: nil, cert: nil, with_statements: [], &block)
|
191
|
+
@apis[name] = block
|
192
|
+
end
|
193
|
+
|
194
|
+
def method_missing(name, *args, &block); end
|
195
|
+
end
|
196
|
+
|
197
|
+
def self.test_api(apiname, pretty_print, &block)
|
198
|
+
tester = APITester.new(&block)
|
199
|
+
test_name = nil
|
200
|
+
if tester.apis.length == 1
|
201
|
+
test_name = tester.apis.keys.first
|
202
|
+
elsif apiname
|
203
|
+
if tester.apis.key? apiname
|
204
|
+
test_name = apiname
|
205
|
+
else
|
206
|
+
puts "Unknown API name. Please choose from one of the APIs: #{tester.apis.keys.inspect}"
|
207
|
+
end
|
208
|
+
else
|
209
|
+
puts "Please choose from one of the APIs: #{tester.apis.keys.inspect}"
|
210
|
+
end
|
211
|
+
|
212
|
+
if test_name
|
213
|
+
puts "Testing API #{test_name}"
|
214
|
+
apigen = Stack::APIGenerator.new(pretty_print: pretty_print, &tester.apis[test_name])
|
215
|
+
|
216
|
+
script = File.read(File.join(Gem.loaded_specs['sumomo'].full_gem_path, 'data', 'sumomo', 'api_modules', 'test_script.js'))
|
217
|
+
script.sub!('// {{ ROUTES }}', apigen.generate)
|
218
|
+
script.gsub!('{{ SCRIPT }}', apigen.init_script)
|
219
|
+
|
220
|
+
File.write('.test.js', script)
|
221
|
+
Stack::APIGenerator.combine_modules('.test_modules')
|
222
|
+
|
223
|
+
exec 'NODE_PATH=.test_modules node .test.js'
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.delete_stack(name:, region:, retain_bucket: false)
|
228
|
+
cf = Aws::CloudFormation::Client.new(region: region)
|
229
|
+
ec2 = Aws::EC2::Client.new(region: region)
|
230
|
+
|
231
|
+
cf.delete_stack(stack_name: name)
|
232
|
+
ec2.delete_key_pair(key_name: make_master_key_name(name: name))
|
233
|
+
|
234
|
+
unless retain_bucket
|
235
|
+
wait_for_stack(name: name, region: region)
|
236
|
+
s3 = Aws::S3::Resource.new(region: region)
|
237
|
+
bucket = s3.bucket(name)
|
238
|
+
bucket.delete!
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.get_stack_outputs(name:, region:)
|
243
|
+
cf = Aws::CloudFormation::Client.new(region: region)
|
244
|
+
|
245
|
+
map = {}
|
246
|
+
cf.describe_stacks(stack_name: name).stacks[0].outputs.each do |x|
|
247
|
+
map[x.output_key] = x.output_value
|
248
|
+
end
|
249
|
+
|
250
|
+
map
|
251
|
+
end
|
252
|
+
|
253
|
+
singleton_class.send(:alias_method, :create_stack, :update_stack)
|
251
254
|
end
|