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.
@@ -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