sumomo 0.8.3 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- setTimeout(function()
20
+ get_domain(domain_name, function(data)
19
21
  {
20
- get_domain(domain_name, function(data)
21
- {
22
- var params = {
23
- CertificateArn: data.arn
24
- };
25
- acm.describeCertificate(params, function(err, cert_data) {
26
- if (err)
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(err);
48
+ on_fail("Verification Failed");
29
49
  }
30
50
  else
31
51
  {
32
- if (cert_data.Certificate.DomainValidationOptions[0].ValidationStatus === "SUCCESS")
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
- }, on_fail);
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, {}, "Success", data);
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, {}, "Success", data);
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, {}, "Success", data.arn);
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, {}, "Success", "(deleted)");
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, {}, "Success", "(don't care)");
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 "trollop"
4
- require "sumomo"
5
- require "yaml"
4
+ require 'trollop'
5
+ require 'sumomo'
6
+ require 'yaml'
6
7
 
7
- SUB_COMMANDS = %w(delete create update outputs testapi)
8
- global_opts = Trollop::options do
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, "AWS region to use", type: :string, default: "ap-northeast-1"
16
- opt :profile, "AWS credential profile to use", type: :string, default: "default"
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["AWS_PROFILE"] = global_opts[:profile]
22
+ ENV['AWS_PROFILE'] = global_opts[:profile]
22
23
 
23
- puts "Using profile:"
24
- p ENV["AWS_PROFILE"]
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 "delete"
30
- Sumomo::delete_stack(name: ARGV[0], region: global_opts[:region])
31
-
32
- when "create", "update"
33
- local_opts = Trollop::options do
34
- opt :filename, "File that describes the stack", type: :string, default: "Sumomofile"
35
- end
36
- Sumomo::create_stack(name: ARGV[0], region: global_opts[:region]) do
37
- proc = Proc.new {}
38
- eval File.read(local_opts[:filename]), proc.binding, local_opts[:filename]
39
- end
40
-
41
- when "outputs"
42
- puts "Outputs for stack #{ARGV[0]}"
43
- puts Sumomo::get_stack_outputs(name: ARGV[0], region: global_opts[:region]).to_yaml
44
-
45
- when "testapi"
46
- local_opts = Trollop::options do
47
- opt :filename, "File that describes the stack", type: :string, default: "Sumomofile"
48
- opt :apiname, "Name of the API you want to test", type: :string
49
- opt :prettyprint, "Test API outputs JSON with nice indentation", type: :boolean, default: true
50
- end
51
- puts "API Test Mode"
52
- Sumomo::test_api(local_opts[:apiname], local_opts[:prettyprint]) do
53
- proc = Proc.new {}
54
- eval File.read(local_opts[:filename]), proc.binding, local_opts[:filename]
55
- end
56
- exit(0)
57
- else
58
- Trollop::die "Unknown subcommand #{cmd.inspect}"
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::wait_for_stack(name: ARGV[0], region: global_opts[:region])
70
+ Sumomo.wait_for_stack(name: ARGV[0], region: global_opts[:region])
@@ -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 "sumomo/version"
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
- def self.make_master_key_name(name:)
21
- "#{name}_master_key"
22
- end
23
-
24
- def self.make_master_key_key(name:)
25
- "cloudformation/#{make_master_key_name(name: name)}.pem"
26
- end
27
-
28
- def self.update_stack(name:, region:, sns_arn:nil, &block)
29
-
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
- if !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
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
- if dummy_number == nil
64
- dummy_number = 0
65
- end
66
- dummy_number += 1
67
- store.set("cloudformation/dummy_number", dummy_number)
68
-
69
- hidden_values = []
70
-
71
- template = Momo::cfl do
72
- inject Sumomo::Stack
73
-
74
- @region = region
75
- @version_number = dummy_number
76
- @custom_resources = {}
77
- @bucket_name = name
78
- @store = store
79
- @master_key_name = master_key_name
80
- @ec2 = ec2
81
- @cf = cf
82
- @s3 = s3
83
-
84
- make "AWS::EC2::SecurityGroup", name: "DummyResource" do
85
- GroupDescription "Dummy thing for Cloudformation Deployment."
86
- Tags [{"Key" => "Name", "Value" => "dummyfordeploy#{dummy_number}"}]
87
- end
88
-
89
- instance_eval(&block)
90
-
91
- hidden_values = @hidden_values
92
-
93
- end.templatize
94
-
95
- # TODO if the template is too big, split it into nested templates
96
-
97
- #puts JSON.parse(template).to_yaml
98
-
99
- store.set_raw("cloudformation/template", template)
100
-
101
- update_options = {
102
- stack_name: name,
103
- template_url: store.url("cloudformation/template"),
104
- parameters: hidden_values,
105
- capabilities: ["CAPABILITY_IAM"]
106
- }
107
-
108
- begin
109
- cf.update_stack(update_options)
110
- rescue => e
111
- if e.message.end_with? "does not exist"
112
- update_options[:timeout_in_minutes] = 30
113
- update_options[:notification_arns] = sns_arn if sns_arn
114
- cf.create_stack(update_options)
115
- else
116
- p e
117
- puts "Error: #{e.message}"
118
- end
119
- end
120
- end
121
-
122
- def self.wait_for_stack(name:, region:)
123
- cf = Aws::CloudFormation::Client.new(region: region)
124
-
125
- stack_id = name
126
-
127
- begin
128
- resp = cf.describe_stack_events(stack_name: stack_id)
129
- top_event = resp.stack_events[0]
130
- top_event_id = top_event.event_id
131
- puts "#{top_event.logical_resource_id} #{top_event.resource_status} #{top_event.resource_status_reason}"
132
- rescue => e
133
- puts "describe_stack_events: #{e.message}"
134
- end
135
-
136
- failure_count = 0
137
- loop do
138
- begin
139
- unless /^arn\:/.match(stack_id)
140
- stack_id = cf.describe_stacks(stack_name: stack_id).stacks[0].stack_id
141
- #puts "Unique Stack ID: #{stack_id}"
142
- end
143
-
144
- resp = cf.describe_stack_events(stack_name: stack_id)
145
- curr = 0
146
- lines = []
147
- loop do
148
- curr_event = resp.stack_events[curr]
149
- break if curr_event.event_id == top_event_id
150
- lines << "#{curr_event.logical_resource_id} #{curr_event.resource_status} #{curr_event.resource_status_reason}"
151
- break if curr == resp.stack_events.length - 1
152
- curr += 1
153
- end
154
-
155
- lines.reverse.each { |x| puts x }
156
-
157
- top_event_id = resp.stack_events[0].event_id
158
- rescue => e
159
- puts "describe_stack_events: #{e.message}"
160
- failure_count += 1
161
- break if failure_count > 5
162
- end
163
-
164
-
165
- sleep 1
166
- begin
167
- resp = cf.describe_stacks(stack_name: stack_id)
168
-
169
- break if /(COMPLETE$)|(FAILED$)/.match(resp.stacks[0].stack_status)
170
-
171
- rescue => e
172
- puts "describe_stacks: #{e.message}"
173
- break
174
- end
175
- end
176
- end
177
-
178
- class APITester
179
-
180
- attr_accessor :apis
181
- def initialize(&block)
182
- @apis = {}
183
- instance_eval(&block)
184
- end
185
-
186
- def make_api(domain_name, name:, script:nil, dns:nil, cert:nil, with_statements: [], &block)
187
- @apis[name] = block
188
- end
189
-
190
- def method_missing(name, *args, &block)
191
- end
192
- end
193
-
194
- def self.test_api(apiname, pretty_print, &block)
195
- tester = APITester.new(&block)
196
- test_name = nil
197
- if tester.apis.length == 1
198
- test_name = tester.apis.keys.first
199
- elsif apiname
200
- if tester.apis.has_key? apiname
201
- test_name = apiname
202
- else
203
- puts "Unknown API name. Please choose from one of the APIs: #{tester.apis.keys.inspect}"
204
- end
205
- else
206
- puts "Please choose from one of the APIs: #{tester.apis.keys.inspect}"
207
- end
208
-
209
- if test_name
210
- puts "Testing API #{test_name}"
211
- apigen = Stack::APIGenerator.new(pretty_print: pretty_print, &tester.apis[test_name])
212
-
213
- script = File.read(File.join(Gem.loaded_specs['sumomo'].full_gem_path, "data", "sumomo", "api_modules", "test_script.js"))
214
- script.sub!("// {{ ROUTES }}", apigen.generate);
215
- script.gsub!("{{ SCRIPT }}", apigen.init_script);
216
-
217
- File.write(".test.js", script)
218
- Stack::APIGenerator.combine_modules(".test_modules")
219
-
220
- exec "NODE_PATH=.test_modules node .test.js"
221
- end
222
- end
223
-
224
- def self.delete_stack(name:, region:, retain_bucket: false)
225
- cf = Aws::CloudFormation::Client.new(region: region)
226
- ec2 = Aws::EC2::Client.new(region: region)
227
-
228
- cf.delete_stack(stack_name: name)
229
- ec2.delete_key_pair(key_name: make_master_key_name(name: name))
230
-
231
- if !retain_bucket
232
- self.wait_for_stack(name: name, region: region)
233
- s3 = Aws::S3::Resource.new(region: region)
234
- bucket = s3.bucket(name)
235
- bucket.delete!
236
- end
237
- end
238
-
239
- def self.get_stack_outputs(name:, region:)
240
- cf = Aws::CloudFormation::Client.new(region: region)
241
-
242
- map = {}
243
- cf.describe_stacks(stack_name: name).stacks[0].outputs.each do |x|
244
- map[x.output_key] = x.output_value
245
- end
246
-
247
- map
248
- end
249
-
250
- singleton_class.send(:alias_method, :create_stack, :update_stack)
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