kumogata 0.3.9 → 0.3.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3669d15c552f8d437460408ae184ac2c51c43539
4
- data.tar.gz: 18f24d8608b34c615721d5a30797a2cf93c37d6d
3
+ metadata.gz: 479281b139e795bc62314dd9a9347981eb6ac01e
4
+ data.tar.gz: 2e8cec1bd6ea499537779a43c3418b674e334ae4
5
5
  SHA512:
6
- metadata.gz: f78de3b3a5a42554653cf55c6df6c1b1852d84de7bdef037f8f747ea507f98094556f4e0e7c74ee36f9864915d74386bcc049c1c4d6f452a075523913969be0f
7
- data.tar.gz: b485fb1416ba26d4f2e9a5e0e10a591e7be47f9e6c730f1098ab7bdce2d2042d655c465eecc7bd951cbfc6fafecbfa6ab1727153e1a5a95ee613920075db8941
6
+ metadata.gz: b1fe62841c65d7c06be78f6a28646b6695db44e50088ac64fea693a2b1e21a8c27de4c1f584a0cb5c944844b562965da17f3b350f0d5e4ba01381089d08ebd9a
7
+ data.tar.gz: cb8bb2af5382cfacf4aa90912c98654fe74434f5159a5fcb954538cb77d94c649109a98c6827abe993ea11fe971b9c2a2c4edfd0baf3f4cd196efbd869ff7ee7
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ tmp
18
18
  packer_cache
19
19
  test.rb
20
20
  result.json
21
+ command_result.json
data/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
 
6
6
  Kumogata is a tool for [AWS CloudFormation](https://aws.amazon.com/cloudformation/).
7
7
 
8
- [![Gem Version](https://badge.fury.io/rb/kumogata.png?201403100300)](http://badge.fury.io/rb/kumogata)
9
- [![Build Status](https://drone.io/github.com/winebarrel/kumogata/status.png?201403100300)](https://drone.io/github.com/winebarrel/kumogata/latest)
8
+ [![Gem Version](https://badge.fury.io/rb/kumogata.png?201403110046)](http://badge.fury.io/rb/kumogata)
9
+ [![Build Status](https://drone.io/github.com/winebarrel/kumogata/status.png?201403110046)](https://drone.io/github.com/winebarrel/kumogata/latest)
10
10
 
11
11
  It can define a template in Ruby DSL, such as:
12
12
 
@@ -92,6 +92,7 @@ Options:
92
92
  --notify SNS_TOPICS
93
93
  --timeout MINUTES
94
94
  --result-log PATH
95
+ --command-result-log PATH
95
96
  --force
96
97
  -w, --ignore-all-space
97
98
  --no-color
@@ -274,6 +275,60 @@ Resources do
274
275
  end # Resources
275
276
  ```
276
277
 
278
+ ## Post command
279
+
280
+ You can run shell/ssh commands after building servers using `_post()`.
281
+
282
+ * Template
283
+ ```ruby
284
+ Parameters do
285
+ ...
286
+ end
287
+
288
+ Resources do
289
+ ...
290
+ end
291
+
292
+ Outputs do
293
+ MyPublicIp do
294
+ Value { Fn__GetAtt name, "PublicIp" }
295
+ end
296
+ end
297
+
298
+ _post do
299
+ my_shell_command do
300
+ command <<-EOS
301
+ echo <%= Key "MyPublicIp" %>
302
+ EOS
303
+ end
304
+ my_ssh_command do
305
+ ssh do
306
+ host { Key "MyPublicIp" } # or '<%= Key "MyPublicIp" %>'
307
+ user "ec2-user"
308
+ end
309
+ command <<-EOS
310
+ hostname
311
+ EOS
312
+ end
313
+ end
314
+ ```
315
+
316
+ * Execution result
317
+ ```
318
+ ...
319
+ Command: my_shell_command
320
+ Status: 0
321
+ ---
322
+ 1> 54.199.251.30
323
+
324
+ Command: my_ssh_command
325
+ Status: 0
326
+ ---
327
+ 1> ip-10-0-129-20
328
+
329
+ (Save to `/foo/bar/command_result.json`)
330
+ ```
331
+
277
332
  ## Demo
278
333
 
279
334
  * Create resources
@@ -282,6 +337,8 @@ end # Resources
282
337
  * https://asciinema.org/a/7980
283
338
  * Create a stack while outputting the event log
284
339
  * https://asciinema.org/a/8075
340
+ * Create a stack and run post commands
341
+ * https://asciinema.org/a/8088
285
342
 
286
343
  ## Contributing
287
344
 
data/kumogata.gemspec CHANGED
@@ -21,10 +21,11 @@ Gem::Specification.new do |spec|
21
21
  spec.add_dependency 'aws-sdk'
22
22
  spec.add_dependency 'coderay'
23
23
  spec.add_dependency 'diffy'
24
- spec.add_dependency 'dslh', '>= 0.2.4'
24
+ spec.add_dependency 'dslh', '>= 0.2.5'
25
25
  spec.add_dependency 'hashie'
26
26
  spec.add_dependency 'highline'
27
27
  spec.add_dependency 'json'
28
+ spec.add_dependency 'net-ssh'
28
29
  spec.add_dependency 'term-ansicolor'
29
30
  spec.add_dependency 'uuidtools'
30
31
  spec.add_development_dependency 'bundler'
@@ -6,6 +6,7 @@ class Kumogata::ArgumentParser
6
6
  :replace_underscore => true,
7
7
  :delete_stack => true,
8
8
  :result_log => File.join(Dir.pwd, 'result.json'),
9
+ :command_result_log => File.join(Dir.pwd, 'command_result.json'),
9
10
  :color => true,
10
11
  :debug => false,
11
12
  }
@@ -91,6 +92,7 @@ class Kumogata::ArgumentParser
91
92
  opt.on('' , '--notify SNS_TOPICS', Array) {|v| options[:notify] = v }
92
93
  opt.on('' , '--timeout MINUTES', Integer) {|v| options[:timeout] = v }
93
94
  opt.on('' , '--result-log PATH') {|v| options[:result_log] = v }
95
+ opt.on('' , '--command-result-log PATH') {|v| options[:command] = v }
94
96
  opt.on('' , '--force') { options[:force] = true }
95
97
  opt.on('-w', '--ignore-all-space') { options[:ignore_all_space] = true }
96
98
  opt.on('' , '--no-color') { options[:color] = false }
@@ -3,6 +3,7 @@ class Kumogata::Client
3
3
  @options = options
4
4
  @options = Hashie::Mash.new(@options) unless @options.kind_of?(Hashie::Mash)
5
5
  @cloud_formation = AWS::CloudFormation.new
6
+ @post_processing = Kumogata::PostProcessing.new(@options)
6
7
  end
7
8
 
8
9
  def create(path_or_url, stack_name = nil)
@@ -12,7 +13,10 @@ class Kumogata::Client
12
13
  template = open_template(path_or_url)
13
14
  update_deletion_policy(template)
14
15
  add_encryption_password(template)
15
- create_stack(template, stack_name)
16
+
17
+ outputs = create_stack(template, stack_name)
18
+ @post_processing.run(:create, outputs)
19
+
16
20
  nil
17
21
  end
18
22
 
@@ -41,7 +45,10 @@ class Kumogata::Client
41
45
  end
42
46
 
43
47
  add_encryption_password(template)
44
- update_stack(template, stack_name)
48
+
49
+ outputs = update_stack(template, stack_name)
50
+ @post_processing.run(:update, outputs)
51
+
45
52
  nil
46
53
  end
47
54
 
@@ -141,7 +148,7 @@ class Kumogata::Client
141
148
  end
142
149
  end
143
150
 
144
- Dslh.eval(template.read, {
151
+ template = Dslh.eval(template.read, {
145
152
  :key_conv => key_converter,
146
153
  :value_conv => value_converter,
147
154
  :scope_hook => proc {|scope|
@@ -149,6 +156,16 @@ class Kumogata::Client
149
156
  },
150
157
  :filename => template.path,
151
158
  })
159
+
160
+ @post_processing.fetch!(template)
161
+
162
+ return template
163
+ end
164
+
165
+ def evaluate_after_trigger(template)
166
+ triggers = template.delete('_after')
167
+ return {} unless triggers
168
+
152
169
  end
153
170
 
154
171
  def devaluate_template(template)
@@ -203,6 +220,15 @@ class Kumogata::Client
203
220
 
204
221
  @__hash__[path] = value
205
222
  end
223
+
224
+ def _post(options = {}, &block)
225
+ commands = Dslh::ScopeBlock.nest(binding, 'block')
226
+
227
+ @__hash__[:_post] = {
228
+ :options => options,
229
+ :commands => commands,
230
+ }
231
+ end
206
232
  EOS
207
233
  end
208
234
 
@@ -237,6 +263,8 @@ class Kumogata::Client
237
263
  end
238
264
 
239
265
  output_result(stack_name, outputs, summaries)
266
+
267
+ return outputs
240
268
  end
241
269
 
242
270
  def update_stack(template, stack_name)
@@ -257,6 +285,8 @@ class Kumogata::Client
257
285
  outputs = outputs_for(stack)
258
286
  summaries = resource_summaries_for(stack)
259
287
  output_result(stack_name, outputs, summaries)
288
+
289
+ return outputs
260
290
  end
261
291
 
262
292
  def delete_stack(stack_name)
@@ -332,6 +362,8 @@ class Kumogata::Client
332
362
  end
333
363
 
334
364
  def while_in_progress(stack, complete_status, event_log)
365
+ # XXX: Status does not change if you have been memoized.
366
+ # Should be forcibly disabled memoization?
335
367
  while stack.status =~ /_IN_PROGRESS\Z/
336
368
  print_event_log(stack, event_log)
337
369
  sleep 1
@@ -1,5 +1,3 @@
1
- require 'term/ansicolor'
2
-
3
1
  class String
4
2
  @@colorize = false
5
3
 
@@ -151,7 +149,7 @@ class String
151
149
 
152
150
  data = data.flatten.select {|i| not i.nil? }.map {|i|
153
151
  if i.kind_of?(String)
154
- StringIO.new(i).to_a
152
+ i.lines.to_a
155
153
  else
156
154
  i
157
155
  end
@@ -0,0 +1,221 @@
1
+ class Kumogata::PostProcessing
2
+ TRIGGER_TIMING = [:create, :update]
3
+
4
+ def initialize(options)
5
+ @options = options
6
+ @commands = {}
7
+
8
+ @command_options = {
9
+ :undent => true,
10
+ :trim_mode => nil,
11
+ }
12
+ end
13
+
14
+ def fetch!(template)
15
+ _post = template.delete(:_post)
16
+ return unless _post
17
+
18
+ options = _post[:options] || {}
19
+ @command_options.merge(options)
20
+
21
+ _post.fetch(:commands).each do |name, attrs|
22
+ timing = [(attrs['after'] || TRIGGER_TIMING)].flatten.map {|i| i.to_sym }
23
+ validate_timing(name, timing)
24
+
25
+ command = attrs['command']
26
+ next unless command
27
+
28
+ @commands[name] = {
29
+ :after => timing,
30
+ :command => command,
31
+ }
32
+
33
+ if (ssh = attrs['ssh'])
34
+ validate_ssh(name, ssh)
35
+ @commands[name][:ssh] = ssh
36
+ end
37
+ end
38
+ end
39
+
40
+ def run(timing, outputs)
41
+ results = []
42
+
43
+ @commands.each do |name, attrs|
44
+ next unless attrs[:after].include?(timing)
45
+
46
+ out, err, status = run_command(attrs, outputs)
47
+ results << print_command_result(name, out, err, status)
48
+ end
49
+
50
+ save_command_results(results) unless results.empty?
51
+ end
52
+
53
+ private
54
+
55
+ def validate_timing(name, timing)
56
+ timing.each do |t|
57
+ unless TRIGGER_TIMING.include?(t)
58
+ raise "Unknown post processing timing #{timing.inspect} in #{name}"
59
+ end
60
+ end
61
+ end
62
+
63
+ def validate_ssh(name, ssh)
64
+ host, user, options = ssh.values_at('host', 'user', 'options')
65
+
66
+ unless host and user
67
+ raise "`host` and `user` is required for post processing ssh in #{name}"
68
+ end
69
+
70
+ if host.kind_of?(Hash)
71
+ if host.keys != ['Key']
72
+ raise "Invalid post processing ssh host #{host.inspect} in #{name}"
73
+ end
74
+
75
+ host_key, host_value = host.first
76
+ ssh['host'] = "<%= #{host_key} #{host_value.to_s.inspect} %>"
77
+ else
78
+ ssh['host'] = host.to_s
79
+ end
80
+
81
+ if user.kind_of?(Hash)
82
+ if user.keys != ['Key']
83
+ raise "Invalid post processing ssh user #{user.inspect} in #{name}"
84
+ end
85
+
86
+ user_key, user_value = user.first
87
+ ssh['user'] = "<%= #{user_key} #{user_value.to_s.inspect} %>"
88
+ else
89
+ ssh['user'] = user.to_s
90
+ end
91
+
92
+ if options and not options.kind_of?(Hash)
93
+ raise "Invalid post processing ssh options #{user.inspect} in #{name}"
94
+ end
95
+ end
96
+
97
+ def run_command(attrs, outputs)
98
+ command, ssh = attrs.values_at(:command, :ssh)
99
+
100
+ if ssh
101
+ run_ssh_command(ssh, command, outputs)
102
+ else
103
+ run_shell_command(command, outputs)
104
+ end
105
+ end
106
+
107
+ def run_ssh_command(ssh, command, outputs)
108
+ host, user, options = ssh.values_at('host', 'user', 'options')
109
+ host = evaluate_command_template(host, outputs)
110
+ user = evaluate_command_template(user, outputs)
111
+ args = [host, user]
112
+ args << ssh['options'] if ssh['options']
113
+
114
+ command = evaluate_command_template(command, outputs)
115
+
116
+ begin
117
+ Net::SSH.start(*args) {|ssh| ssh_exec!(ssh, command) }
118
+ rescue Net::SSH::HostKeyMismatch => e
119
+ e.remember_host!
120
+ retry
121
+ end
122
+ end
123
+
124
+ def ssh_exec!(ssh, command)
125
+ stdout_data = ''
126
+ stderr_data = ''
127
+ exit_code = nil
128
+ #exit_signal = nil
129
+
130
+ ssh.open_channel do |channel|
131
+ channel.exec(command) do |ch, success|
132
+ unless success
133
+ raise "Couldn't execute command #{command.inspect} (ssh.channel.exec)"
134
+ end
135
+
136
+ channel.on_data do |ch, data|
137
+ stdout_data << data
138
+ end
139
+
140
+ channel.on_extended_data do |ch, type, data|
141
+ stderr_data << data
142
+ end
143
+
144
+ channel.on_request('exit-status') do |ch, data|
145
+ exit_code = data.read_long
146
+ end
147
+
148
+ #channel.on_request('exit-signal') do |ch, data|
149
+ # exit_signal = data.read_long
150
+ #end
151
+ end
152
+ end
153
+
154
+ ssh.loop
155
+
156
+ #[stdout_data, stderr_data, exit_code, exit_signal]
157
+ [stdout_data, stderr_data, exit_code]
158
+ end
159
+
160
+ def run_shell_command(command, outputs)
161
+ command = evaluate_command_template(command, outputs)
162
+ Open3.capture3(command)
163
+ end
164
+
165
+ def evaluate_command_template(command, outputs)
166
+ command = command.undent if @command_options[:undent]
167
+ trim_mode = @command_options[:trim_mode]
168
+
169
+ scope = Object.new
170
+ scope.instance_variable_set(:@__outputs__, outputs)
171
+
172
+ scope.instance_eval(<<-EOS)
173
+ def Key(name)
174
+ @__outputs__[name]
175
+ end
176
+
177
+ ERB.new(#{command.inspect}, nil, #{trim_mode.inspect}).result(binding)
178
+ EOS
179
+ end
180
+
181
+ def print_command_result(name, out, err, status)
182
+ status = status.to_i
183
+ dspout = (out || '').lines.map {|i| "1> ".intense_green + i }.join.chomp
184
+ dsperr = (err || '').lines.map {|i| "2> ".intense_red + i }.join.chomp
185
+
186
+ puts <<-EOS
187
+
188
+ Command: #{name.intense_blue}
189
+ Status: #{status.zero? ? status : status.to_s.red}#{
190
+ dspout.empty? ? '' : ("\n---\n" + dspout)
191
+ }#{
192
+ dsperr.empty? ? '' : ("\n---\n" + dsperr)
193
+ }
194
+ EOS
195
+
196
+ {name => {
197
+ 'ExitStatus' => status,
198
+ 'StdOut' => out.force_encoding('UTF-8'),
199
+ 'StdErr' => err.force_encoding('UTF-8'),
200
+ }}
201
+ end
202
+
203
+ def save_command_results(results)
204
+ puts <<-EOS
205
+
206
+ (Save to `#{@options.command_result_log}`)
207
+ EOS
208
+
209
+ open(@options.command_result_log, 'wb') do |f|
210
+ f.puts JSON.pretty_generate(results)
211
+ end
212
+ end
213
+
214
+ def validate_stack_name(stack_name)
215
+ return unless stack_name
216
+
217
+ unless /\A[a-zA-Z][-a-zA-Z0-9]*\Z/i =~ stack_name
218
+ raise "1 validation error detected: Value '#{stack_name}' at 'stackName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*"
219
+ end
220
+ end
221
+ end
@@ -1,3 +1,3 @@
1
1
  module Kumogata
2
- VERSION = '0.3.9'
2
+ VERSION = '0.3.10'
3
3
  end
data/lib/kumogata.rb CHANGED
@@ -10,11 +10,13 @@ require 'hashie'
10
10
  require 'highline/import'
11
11
  require 'json'
12
12
  require 'logger'
13
+ require 'net/ssh'
13
14
  require 'open-uri'
15
+ require 'open3'
14
16
  require 'optparse'
15
17
  require 'singleton'
16
- require 'stringio'
17
18
  require 'strscan'
19
+ require 'term/ansicolor'
18
20
  require 'uuidtools'
19
21
 
20
22
  require 'kumogata/argument_parser'
@@ -24,4 +26,5 @@ require 'kumogata/ext/coderay_ext'
24
26
  require 'kumogata/ext/json_ext'
25
27
  require 'kumogata/ext/string_ext'
26
28
  require 'kumogata/logger'
29
+ require 'kumogata/post_processing'
27
30
  require 'kumogata/utils'
@@ -59,6 +59,404 @@ end
59
59
  end
60
60
  end
61
61
 
62
+ it 'create a stack from Ruby template and run command' do
63
+ template = <<-TEMPLATE
64
+ Resources do
65
+ myEC2Instance do
66
+ Type "AWS::EC2::Instance"
67
+ Properties do
68
+ ImageId "ami-XXXXXXXX"
69
+ InstanceType "t1.micro"
70
+ end
71
+ end
72
+ end
73
+
74
+ Outputs do
75
+ AZ do
76
+ Value do
77
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
78
+ end
79
+ end
80
+
81
+ Region do
82
+ Value do
83
+ Ref "AWS::Region"
84
+ end
85
+ end
86
+ end
87
+
88
+ _post do
89
+ command_a do
90
+ command <<-EOS
91
+ echo <%= Key "AZ" %>
92
+ echo <%= Key "Region" %>
93
+ EOS
94
+ end
95
+ command_b do
96
+ command <<-EOS
97
+ echo <%= Key "Region" %>
98
+ echo <%= Key "AZ" %>
99
+ EOS
100
+ end
101
+ end
102
+ TEMPLATE
103
+
104
+ run_client(:create, :template => template) do |client, cf|
105
+ json = eval_template(template, :update_deletion_policy => true).to_json
106
+ client.should_receive(:print_event_log).twice
107
+ client.should_receive(:create_event_log).once
108
+
109
+ output1 = make_double('output') do |obj|
110
+ obj.should_receive(:key) { 'AZ' }
111
+ obj.should_receive(:value) { 'ap-northeast-1b' }
112
+ end
113
+
114
+ output2 = make_double('output') do |obj|
115
+ obj.should_receive(:key) { 'Region' }
116
+ obj.should_receive(:value) { 'ap-northeast-1' }
117
+ end
118
+
119
+ resource_summary = make_double('resource_summary') do |obj|
120
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
121
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
122
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
123
+ obj.should_receive(:[]).with(:resource_status) { 'CREATE_COMPLETE' }
124
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
125
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
126
+ end
127
+
128
+ stack = make_double('stack') do |obj|
129
+ obj.should_receive(:status).and_return(
130
+ 'CREATE_COMPLETE', 'CREATE_COMPLETE',
131
+ 'DELETE_COMPLETE', 'DELETE_COMPLETE', 'DELETE_COMPLETE')
132
+ obj.should_receive(:outputs) { [output1, output2] }
133
+ obj.should_receive(:resource_summaries) { [resource_summary] }
134
+ obj.should_receive(:delete)
135
+ end
136
+
137
+ stacks = make_double('stacks') do |obj|
138
+ obj.should_receive(:create)
139
+ .with('kumogata-user-host-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', json, {}) { stack }
140
+ obj.should_receive(:[])
141
+ .with('kumogata-user-host-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') { stack }
142
+ end
143
+
144
+ cf.should_receive(:stacks).twice { stacks }
145
+
146
+ process_status1 = double('process_status1')
147
+ process_status2 = double('process_status2')
148
+
149
+ Open3.should_receive(:capture3).with("echo ap-northeast-1b\necho ap-northeast-1\n")
150
+ .and_return(["ap-northeast-1b\nap-northeast-1\n", "", process_status1])
151
+ Open3.should_receive(:capture3).with("echo ap-northeast-1\necho ap-northeast-1b\n")
152
+ .and_return(["ap-northeast-1\nap-northeast-1b\n", "", process_status2])
153
+
154
+ client.instance_variable_get(:@post_processing)
155
+ .should_receive(:print_command_result)
156
+ .with('command_a', "ap-northeast-1b\nap-northeast-1\n", "", process_status1)
157
+ .and_return('command_a' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1b\necho ap-northeast-1\n", 'StdErr' => ""})
158
+ client.instance_variable_get(:@post_processing)
159
+ .should_receive(:print_command_result)
160
+ .with('command_b', "ap-northeast-1\nap-northeast-1b\n", "", process_status2)
161
+ .and_return('command_b' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1\necho ap-northeast-1b\n", 'StdErr' => ""})
162
+
163
+ client.instance_variable_get(:@post_processing)
164
+ .should_receive(:save_command_results)
165
+ .with([{'command_a' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1b\necho ap-northeast-1\n", 'StdErr' => ""}},
166
+ {'command_b' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1\necho ap-northeast-1b\n", 'StdErr' => ""}}])
167
+ end
168
+ end
169
+
170
+ it 'create a stack from Ruby template and run ssh command' do
171
+ template = <<-TEMPLATE
172
+ Resources do
173
+ myEC2Instance do
174
+ Type "AWS::EC2::Instance"
175
+ Properties do
176
+ ImageId "ami-XXXXXXXX"
177
+ InstanceType "t1.micro"
178
+ end
179
+ end
180
+ end
181
+
182
+ Outputs do
183
+ PublicIp do
184
+ Value do
185
+ Fn__GetAtt "myEC2Instance", "PublicIp"
186
+ end
187
+ end
188
+ end
189
+
190
+ _post do
191
+ ssh_command do
192
+ ssh do
193
+ host { Key "PublicIp" }
194
+ user "ec2-user"
195
+ end
196
+ command <<-EOS
197
+ ls
198
+ EOS
199
+ end
200
+ end
201
+ TEMPLATE
202
+
203
+ run_client(:create, :template => template) do |client, cf|
204
+ json = eval_template(template, :update_deletion_policy => true).to_json
205
+ client.should_receive(:print_event_log).twice
206
+ client.should_receive(:create_event_log).once
207
+
208
+ output = make_double('output') do |obj|
209
+ obj.should_receive(:key) { 'PublicIp' }
210
+ obj.should_receive(:value) { '127.0.0.1' }
211
+ end
212
+
213
+ resource_summary = make_double('resource_summary') do |obj|
214
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
215
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
216
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
217
+ obj.should_receive(:[]).with(:resource_status) { 'CREATE_COMPLETE' }
218
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
219
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
220
+ end
221
+
222
+ stack = make_double('stack') do |obj|
223
+ obj.should_receive(:status).and_return(
224
+ 'CREATE_COMPLETE', 'CREATE_COMPLETE',
225
+ 'DELETE_COMPLETE', 'DELETE_COMPLETE', 'DELETE_COMPLETE')
226
+ obj.should_receive(:outputs) { [output] }
227
+ obj.should_receive(:resource_summaries) { [resource_summary] }
228
+ obj.should_receive(:delete)
229
+ end
230
+
231
+ stacks = make_double('stacks') do |obj|
232
+ obj.should_receive(:create)
233
+ .with('kumogata-user-host-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', json, {}) { stack }
234
+ obj.should_receive(:[])
235
+ .with('kumogata-user-host-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') { stack }
236
+ end
237
+
238
+ cf.should_receive(:stacks).twice { stacks }
239
+
240
+ Net::SSH.should_receive(:start).with("127.0.0.1", "ec2-user")
241
+ .and_return(["file1\nfile2\n", "", 0])
242
+
243
+ client.instance_variable_get(:@post_processing)
244
+ .should_receive(:print_command_result)
245
+ .with('ssh_command', "file1\nfile2\n", "", 0)
246
+ .and_return('ssh_command' => {'ExitStatus' => 0, 'StdOut' => "file1\nfile2\n", 'StdErr' => ""})
247
+
248
+ client.instance_variable_get(:@post_processing)
249
+ .should_receive(:save_command_results)
250
+ .with([{'ssh_command' => {'ExitStatus' => 0, 'StdOut' => "file1\nfile2\n", 'StdErr' => ""}}])
251
+ end
252
+ end
253
+
254
+ it 'create a stack from Ruby template and run command (specifies timing)' do
255
+ template = <<-TEMPLATE
256
+ Resources do
257
+ myEC2Instance do
258
+ Type "AWS::EC2::Instance"
259
+ Properties do
260
+ ImageId "ami-XXXXXXXX"
261
+ InstanceType "t1.micro"
262
+ end
263
+ end
264
+ end
265
+
266
+ Outputs do
267
+ AZ do
268
+ Value do
269
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
270
+ end
271
+ end
272
+
273
+ Region do
274
+ Value do
275
+ Ref "AWS::Region"
276
+ end
277
+ end
278
+ end
279
+
280
+ _post do
281
+ command_a do
282
+ after :create
283
+ command <<-EOS
284
+ echo <%= Key "AZ" %>
285
+ echo <%= Key "Region" %>
286
+ EOS
287
+ end
288
+ command_b do
289
+ after :create, :update
290
+ command <<-EOS
291
+ echo <%= Key "Region" %>
292
+ echo <%= Key "AZ" %>
293
+ EOS
294
+ end
295
+ end
296
+ TEMPLATE
297
+
298
+ run_client(:create, :template => template) do |client, cf|
299
+ json = eval_template(template, :update_deletion_policy => true).to_json
300
+ client.should_receive(:print_event_log).twice
301
+ client.should_receive(:create_event_log).once
302
+
303
+ output1 = make_double('output') do |obj|
304
+ obj.should_receive(:key) { 'AZ' }
305
+ obj.should_receive(:value) { 'ap-northeast-1b' }
306
+ end
307
+
308
+ output2 = make_double('output') do |obj|
309
+ obj.should_receive(:key) { 'Region' }
310
+ obj.should_receive(:value) { 'ap-northeast-1' }
311
+ end
312
+
313
+ resource_summary = make_double('resource_summary') do |obj|
314
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
315
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
316
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
317
+ obj.should_receive(:[]).with(:resource_status) { 'CREATE_COMPLETE' }
318
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
319
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
320
+ end
321
+
322
+ stack = make_double('stack') do |obj|
323
+ obj.should_receive(:status).and_return(
324
+ 'CREATE_COMPLETE', 'CREATE_COMPLETE',
325
+ 'DELETE_COMPLETE', 'DELETE_COMPLETE', 'DELETE_COMPLETE')
326
+ obj.should_receive(:outputs) { [output1, output2] }
327
+ obj.should_receive(:resource_summaries) { [resource_summary] }
328
+ obj.should_receive(:delete)
329
+ end
330
+
331
+ stacks = make_double('stacks') do |obj|
332
+ obj.should_receive(:create)
333
+ .with('kumogata-user-host-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', json, {}) { stack }
334
+ obj.should_receive(:[])
335
+ .with('kumogata-user-host-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') { stack }
336
+ end
337
+
338
+ cf.should_receive(:stacks).twice { stacks }
339
+
340
+ process_status1 = double('process_status1')
341
+ process_status2 = double('process_status2')
342
+
343
+ Open3.should_receive(:capture3).with("echo ap-northeast-1b\necho ap-northeast-1\n")
344
+ .and_return(["ap-northeast-1b\nap-northeast-1\n", "", process_status1])
345
+ Open3.should_receive(:capture3).with("echo ap-northeast-1\necho ap-northeast-1b\n")
346
+ .and_return(["ap-northeast-1\nap-northeast-1b\n", "", process_status2])
347
+
348
+ client.instance_variable_get(:@post_processing)
349
+ .should_receive(:print_command_result)
350
+ .with('command_a', "ap-northeast-1b\nap-northeast-1\n", "", process_status1)
351
+ .and_return('command_a' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1b\necho ap-northeast-1\n", 'StdErr' => ""})
352
+ client.instance_variable_get(:@post_processing)
353
+ .should_receive(:print_command_result)
354
+ .with('command_b', "ap-northeast-1\nap-northeast-1b\n", "", process_status2)
355
+ .and_return('command_b' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1\necho ap-northeast-1b\n", 'StdErr' => ""})
356
+
357
+ client.instance_variable_get(:@post_processing)
358
+ .should_receive(:save_command_results)
359
+ .with([{'command_a' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1b\necho ap-northeast-1\n", 'StdErr' => ""}},
360
+ {'command_b' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1\necho ap-northeast-1b\n", 'StdErr' => ""}}])
361
+ end
362
+ end
363
+
364
+ it 'create a stack from Ruby template and run command (update timing)' do
365
+ template = <<-TEMPLATE
366
+ Resources do
367
+ myEC2Instance do
368
+ Type "AWS::EC2::Instance"
369
+ Properties do
370
+ ImageId "ami-XXXXXXXX"
371
+ InstanceType "t1.micro"
372
+ end
373
+ end
374
+ end
375
+
376
+ Outputs do
377
+ AZ do
378
+ Value do
379
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
380
+ end
381
+ end
382
+
383
+ Region do
384
+ Value do
385
+ Ref "AWS::Region"
386
+ end
387
+ end
388
+ end
389
+
390
+ _post do
391
+ command_a do
392
+ after :update
393
+ command <<-EOS
394
+ echo <%= Key "AZ" %>
395
+ echo <%= Key "Region" %>
396
+ EOS
397
+ end
398
+ command_b do
399
+ after :update
400
+ command <<-EOS
401
+ echo <%= Key "Region" %>
402
+ echo <%= Key "AZ" %>
403
+ EOS
404
+ end
405
+ end
406
+ TEMPLATE
407
+
408
+ run_client(:create, :template => template) do |client, cf|
409
+ json = eval_template(template, :update_deletion_policy => true).to_json
410
+ client.should_receive(:print_event_log).twice
411
+ client.should_receive(:create_event_log).once
412
+
413
+ output1 = make_double('output') do |obj|
414
+ obj.should_receive(:key) { 'AZ' }
415
+ obj.should_receive(:value) { 'ap-northeast-1b' }
416
+ end
417
+
418
+ output2 = make_double('output') do |obj|
419
+ obj.should_receive(:key) { 'Region' }
420
+ obj.should_receive(:value) { 'ap-northeast-1' }
421
+ end
422
+
423
+ resource_summary = make_double('resource_summary') do |obj|
424
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
425
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
426
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
427
+ obj.should_receive(:[]).with(:resource_status) { 'CREATE_COMPLETE' }
428
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
429
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
430
+ end
431
+
432
+ stack = make_double('stack') do |obj|
433
+ obj.should_receive(:status).and_return(
434
+ 'CREATE_COMPLETE', 'CREATE_COMPLETE',
435
+ 'DELETE_COMPLETE', 'DELETE_COMPLETE', 'DELETE_COMPLETE')
436
+ obj.should_receive(:outputs) { [output1, output2] }
437
+ obj.should_receive(:resource_summaries) { [resource_summary] }
438
+ obj.should_receive(:delete)
439
+ end
440
+
441
+ stacks = make_double('stacks') do |obj|
442
+ obj.should_receive(:create)
443
+ .with('kumogata-user-host-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', json, {}) { stack }
444
+ obj.should_receive(:[])
445
+ .with('kumogata-user-host-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') { stack }
446
+ end
447
+
448
+ cf.should_receive(:stacks).twice { stacks }
449
+
450
+ Open3.should_not_receive(:capture3)
451
+
452
+ client.instance_variable_get(:@post_processing)
453
+ .should_not_receive(:print_command_result)
454
+
455
+ client.instance_variable_get(:@post_processing)
456
+ .should_not_receive(:save_command_results)
457
+ end
458
+ end
459
+
62
460
  it 'create a stack from Ruby template (include DeletionPolicy)' do
63
461
  template = <<-EOS
64
462
  Resources do
@@ -56,6 +56,395 @@ end
56
56
  end
57
57
  end
58
58
 
59
+ it 'update a stack from Ruby template and run command' do
60
+ template = <<-TEMPLATE
61
+ Resources do
62
+ myEC2Instance do
63
+ Type "AWS::EC2::Instance"
64
+ Properties do
65
+ ImageId "ami-XXXXXXXX"
66
+ InstanceType "t1.micro"
67
+ end
68
+ end
69
+ end
70
+
71
+ Outputs do
72
+ AZ do
73
+ Value do
74
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
75
+ end
76
+ end
77
+
78
+ Region do
79
+ Value do
80
+ Ref "AWS::Region"
81
+ end
82
+ end
83
+ end
84
+
85
+ _post do
86
+ command_a do
87
+ command <<-EOS
88
+ echo <%= Key "AZ" %>
89
+ echo <%= Key "Region" %>
90
+ EOS
91
+ end
92
+ command_b do
93
+ command <<-EOS
94
+ echo <%= Key "Region" %>
95
+ echo <%= Key "AZ" %>
96
+ EOS
97
+ end
98
+ end
99
+ TEMPLATE
100
+
101
+ run_client(:update, :arguments => ['MyStack'], :template => template) do |client, cf|
102
+ json = eval_template(template).to_json
103
+ client.should_receive(:print_event_log).once
104
+ client.should_receive(:create_event_log).once
105
+
106
+ output1 = make_double('output') do |obj|
107
+ obj.should_receive(:key) { 'AZ' }
108
+ obj.should_receive(:value) { 'ap-northeast-1b' }
109
+ end
110
+
111
+ output2 = make_double('output') do |obj|
112
+ obj.should_receive(:key) { 'Region' }
113
+ obj.should_receive(:value) { 'ap-northeast-1' }
114
+ end
115
+
116
+ resource_summary = make_double('resource_summary') do |obj|
117
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
118
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
119
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
120
+ obj.should_receive(:[]).with(:resource_status) { 'UPDATE_COMPLETE' }
121
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
122
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
123
+ end
124
+
125
+ stack = make_double('stack') do |obj|
126
+ obj.should_receive(:update).with(:template => json)
127
+ obj.should_receive(:status).and_return(
128
+ 'UPDATE_COMPLETE', 'UPDATE_COMPLETE', 'UPDATE_COMPLETE')
129
+ obj.should_receive(:outputs) { [output1, output2] }
130
+ obj.should_receive(:resource_summaries) { [resource_summary] }
131
+ end
132
+
133
+ stacks = make_double('stacks') do |obj|
134
+ obj.should_receive(:[])
135
+ .with('MyStack') { stack }
136
+ end
137
+
138
+ cf.should_receive(:stacks) { stacks }
139
+
140
+ process_status1 = double('process_status1')
141
+ process_status2 = double('process_status2')
142
+
143
+ Open3.should_receive(:capture3).with("echo ap-northeast-1b\necho ap-northeast-1\n")
144
+ .and_return(["ap-northeast-1b\nap-northeast-1\n", "", process_status1])
145
+ Open3.should_receive(:capture3).with("echo ap-northeast-1\necho ap-northeast-1b\n")
146
+ .and_return(["ap-northeast-1\nap-northeast-1b\n", "", process_status2])
147
+
148
+ client.instance_variable_get(:@post_processing)
149
+ .should_receive(:print_command_result)
150
+ .with('command_a', "ap-northeast-1b\nap-northeast-1\n", "", process_status1)
151
+ .and_return('command_a' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1b\necho ap-northeast-1\n", 'StdErr' => ""})
152
+ client.instance_variable_get(:@post_processing)
153
+ .should_receive(:print_command_result)
154
+ .with('command_b', "ap-northeast-1\nap-northeast-1b\n", "", process_status2)
155
+ .and_return('command_b' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1\necho ap-northeast-1b\n", 'StdErr' => ""})
156
+
157
+ client.instance_variable_get(:@post_processing)
158
+ .should_receive(:save_command_results)
159
+ .with([{'command_a' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1b\necho ap-northeast-1\n", 'StdErr' => ""}},
160
+ {'command_b' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1\necho ap-northeast-1b\n", 'StdErr' => ""}}])
161
+ end
162
+ end
163
+
164
+ it 'update a stack from Ruby template and run ssh command' do
165
+ template = <<-TEMPLATE
166
+ Resources do
167
+ myEC2Instance do
168
+ Type "AWS::EC2::Instance"
169
+ Properties do
170
+ ImageId "ami-XXXXXXXX"
171
+ InstanceType "t1.micro"
172
+ end
173
+ end
174
+ end
175
+
176
+ Outputs do
177
+ PublicIp do
178
+ Value do
179
+ Fn__GetAtt "myEC2Instance", "PublicIp"
180
+ end
181
+ end
182
+ end
183
+
184
+ _post do
185
+ ssh_command do
186
+ ssh do
187
+ host { Key "PublicIp" }
188
+ user "ec2-user"
189
+ end
190
+ command <<-EOS
191
+ ls
192
+ EOS
193
+ end
194
+ end
195
+ TEMPLATE
196
+
197
+ run_client(:update, :arguments => ['MyStack'], :template => template) do |client, cf|
198
+ json = eval_template(template).to_json
199
+ client.should_receive(:print_event_log).once
200
+ client.should_receive(:create_event_log).once
201
+
202
+ output = make_double('output') do |obj|
203
+ obj.should_receive(:key) { 'PublicIp' }
204
+ obj.should_receive(:value) { '127.0.0.1' }
205
+ end
206
+
207
+ resource_summary = make_double('resource_summary') do |obj|
208
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
209
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
210
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
211
+ obj.should_receive(:[]).with(:resource_status) { 'UPDATE_COMPLETE' }
212
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
213
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
214
+ end
215
+
216
+ stack = make_double('stack') do |obj|
217
+ obj.should_receive(:update).with(:template => json)
218
+ obj.should_receive(:status).and_return(
219
+ 'UPDATE_COMPLETE', 'UPDATE_COMPLETE', 'UPDATE_COMPLETE')
220
+ obj.should_receive(:outputs) { [output] }
221
+ obj.should_receive(:resource_summaries) { [resource_summary] }
222
+ end
223
+
224
+ stacks = make_double('stacks') do |obj|
225
+ obj.should_receive(:[])
226
+ .with('MyStack') { stack }
227
+ end
228
+
229
+ cf.should_receive(:stacks) { stacks }
230
+
231
+ Net::SSH.should_receive(:start).with("127.0.0.1", "ec2-user")
232
+ .and_return(["file1\nfile2\n", "", 0])
233
+
234
+ client.instance_variable_get(:@post_processing)
235
+ .should_receive(:print_command_result)
236
+ .with('ssh_command', "file1\nfile2\n", "", 0)
237
+ .and_return('ssh_command' => {'ExitStatus' => 0, 'StdOut' => "file1\nfile2\n", 'StdErr' => ""})
238
+
239
+ client.instance_variable_get(:@post_processing)
240
+ .should_receive(:save_command_results)
241
+ .with([{'ssh_command' => {'ExitStatus' => 0, 'StdOut' => "file1\nfile2\n", 'StdErr' => ""}}])
242
+ end
243
+ end
244
+
245
+ it 'update a stack from Ruby template and run command (specifies timing)' do
246
+ template = <<-TEMPLATE
247
+ Resources do
248
+ myEC2Instance do
249
+ Type "AWS::EC2::Instance"
250
+ Properties do
251
+ ImageId "ami-XXXXXXXX"
252
+ InstanceType "t1.micro"
253
+ end
254
+ end
255
+ end
256
+
257
+ Outputs do
258
+ AZ do
259
+ Value do
260
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
261
+ end
262
+ end
263
+
264
+ Region do
265
+ Value do
266
+ Ref "AWS::Region"
267
+ end
268
+ end
269
+ end
270
+
271
+ _post do
272
+ command_a do
273
+ after :update
274
+ command <<-EOS
275
+ echo <%= Key "AZ" %>
276
+ echo <%= Key "Region" %>
277
+ EOS
278
+ end
279
+ command_b do
280
+ after :create, :update
281
+ command <<-EOS
282
+ echo <%= Key "Region" %>
283
+ echo <%= Key "AZ" %>
284
+ EOS
285
+ end
286
+ end
287
+ TEMPLATE
288
+
289
+ run_client(:update, :arguments => ['MyStack'], :template => template) do |client, cf|
290
+ json = eval_template(template).to_json
291
+ client.should_receive(:print_event_log).once
292
+ client.should_receive(:create_event_log).once
293
+
294
+ output1 = make_double('output') do |obj|
295
+ obj.should_receive(:key) { 'AZ' }
296
+ obj.should_receive(:value) { 'ap-northeast-1b' }
297
+ end
298
+
299
+ output2 = make_double('output') do |obj|
300
+ obj.should_receive(:key) { 'Region' }
301
+ obj.should_receive(:value) { 'ap-northeast-1' }
302
+ end
303
+
304
+ resource_summary = make_double('resource_summary') do |obj|
305
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
306
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
307
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
308
+ obj.should_receive(:[]).with(:resource_status) { 'UPDATE_COMPLETE' }
309
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
310
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
311
+ end
312
+
313
+ stack = make_double('stack') do |obj|
314
+ obj.should_receive(:update).with(:template => json)
315
+ obj.should_receive(:status).and_return(
316
+ 'UPDATE_COMPLETE', 'UPDATE_COMPLETE', 'UPDATE_COMPLETE')
317
+ obj.should_receive(:outputs) { [output1, output2] }
318
+ obj.should_receive(:resource_summaries) { [resource_summary] }
319
+ end
320
+
321
+ stacks = make_double('stacks') do |obj|
322
+ obj.should_receive(:[])
323
+ .with('MyStack') { stack }
324
+ end
325
+
326
+ cf.should_receive(:stacks) { stacks }
327
+
328
+ process_status1 = double('process_status1')
329
+ process_status2 = double('process_status2')
330
+
331
+ Open3.should_receive(:capture3).with("echo ap-northeast-1b\necho ap-northeast-1\n")
332
+ .and_return(["ap-northeast-1b\nap-northeast-1\n", "", process_status1])
333
+ Open3.should_receive(:capture3).with("echo ap-northeast-1\necho ap-northeast-1b\n")
334
+ .and_return(["ap-northeast-1\nap-northeast-1b\n", "", process_status2])
335
+
336
+ client.instance_variable_get(:@post_processing)
337
+ .should_receive(:print_command_result)
338
+ .with('command_a', "ap-northeast-1b\nap-northeast-1\n", "", process_status1)
339
+ .and_return('command_a' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1b\necho ap-northeast-1\n", 'StdErr' => ""})
340
+ client.instance_variable_get(:@post_processing)
341
+ .should_receive(:print_command_result)
342
+ .with('command_b', "ap-northeast-1\nap-northeast-1b\n", "", process_status2)
343
+ .and_return('command_b' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1\necho ap-northeast-1b\n", 'StdErr' => ""})
344
+
345
+ client.instance_variable_get(:@post_processing)
346
+ .should_receive(:save_command_results)
347
+ .with([{'command_a' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1b\necho ap-northeast-1\n", 'StdErr' => ""}},
348
+ {'command_b' => {'ExitStatus' => 0, 'StdOut' => "ap-northeast-1\necho ap-northeast-1b\n", 'StdErr' => ""}}])
349
+ end
350
+ end
351
+
352
+ it 'update a stack from Ruby template and run command (create timing)' do
353
+ template = <<-TEMPLATE
354
+ Resources do
355
+ myEC2Instance do
356
+ Type "AWS::EC2::Instance"
357
+ Properties do
358
+ ImageId "ami-XXXXXXXX"
359
+ InstanceType "t1.micro"
360
+ end
361
+ end
362
+ end
363
+
364
+ Outputs do
365
+ AZ do
366
+ Value do
367
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
368
+ end
369
+ end
370
+
371
+ Region do
372
+ Value do
373
+ Ref "AWS::Region"
374
+ end
375
+ end
376
+ end
377
+
378
+ _post do
379
+ command_a do
380
+ after :create
381
+ command <<-EOS
382
+ echo <%= Key "AZ" %>
383
+ echo <%= Key "Region" %>
384
+ EOS
385
+ end
386
+ command_b do
387
+ after :create
388
+ command <<-EOS
389
+ echo <%= Key "Region" %>
390
+ echo <%= Key "AZ" %>
391
+ EOS
392
+ end
393
+ end
394
+ TEMPLATE
395
+
396
+ run_client(:update, :arguments => ['MyStack'], :template => template) do |client, cf|
397
+ json = eval_template(template).to_json
398
+ client.should_receive(:print_event_log).once
399
+ client.should_receive(:create_event_log).once
400
+
401
+ output1 = make_double('output') do |obj|
402
+ obj.should_receive(:key) { 'AZ' }
403
+ obj.should_receive(:value) { 'ap-northeast-1b' }
404
+ end
405
+
406
+ output2 = make_double('output') do |obj|
407
+ obj.should_receive(:key) { 'Region' }
408
+ obj.should_receive(:value) { 'ap-northeast-1' }
409
+ end
410
+
411
+ resource_summary = make_double('resource_summary') do |obj|
412
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
413
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
414
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
415
+ obj.should_receive(:[]).with(:resource_status) { 'UPDATE_COMPLETE' }
416
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
417
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
418
+ end
419
+
420
+ stack = make_double('stack') do |obj|
421
+ obj.should_receive(:update).with(:template => json)
422
+ obj.should_receive(:status).and_return(
423
+ 'UPDATE_COMPLETE', 'UPDATE_COMPLETE', 'UPDATE_COMPLETE')
424
+ obj.should_receive(:outputs) { [output1, output2] }
425
+ obj.should_receive(:resource_summaries) { [resource_summary] }
426
+ end
427
+
428
+ stacks = make_double('stacks') do |obj|
429
+ obj.should_receive(:[])
430
+ .with('MyStack') { stack }
431
+ end
432
+
433
+ cf.should_receive(:stacks) { stacks }
434
+
435
+ process_status1 = double('process_status1')
436
+ process_status2 = double('process_status2')
437
+
438
+ Open3.should_not_receive(:capture3)
439
+
440
+ client.instance_variable_get(:@post_processing)
441
+ .should_not_receive(:print_command_result)
442
+
443
+ client.instance_variable_get(:@post_processing)
444
+ .should_not_receive(:save_command_results)
445
+ end
446
+ end
447
+
59
448
  it 'update a stack from Ruby template with parameters' do
60
449
  template = <<-EOS
61
450
  Parameters do
data/spec/spec_helper.rb CHANGED
@@ -39,6 +39,7 @@ def run_client(command, options = {})
39
39
  kumogata_arguments = options[:arguments] || []
40
40
  kumogata_options = Kumogata::ArgumentParser::DEFAULT_OPTIONS.merge(options[:options] || {})
41
41
  kumogata_options[:result_log] = '/dev/null'
42
+ kumogata_options[:command_result_log] = '/dev/null'
42
43
  template_ext = options[:template_ext] || '.rb'
43
44
 
44
45
  client = Kumogata::Client.new(kumogata_options)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumogata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 0.3.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Genki Sugawara
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-09 00:00:00.000000000 Z
11
+ date: 2014-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - '>='
60
60
  - !ruby/object:Gem::Version
61
- version: 0.2.4
61
+ version: 0.2.5
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '>='
67
67
  - !ruby/object:Gem::Version
68
- version: 0.2.4
68
+ version: 0.2.5
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: hashie
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: net-ssh
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: term-ansicolor
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -202,6 +216,7 @@ files:
202
216
  - lib/kumogata/ext/json_ext.rb
203
217
  - lib/kumogata/ext/string_ext.rb
204
218
  - lib/kumogata/logger.rb
219
+ - lib/kumogata/post_processing.rb
205
220
  - lib/kumogata/utils.rb
206
221
  - lib/kumogata/version.rb
207
222
  - packer/CentOS-6.4-x86_64-with_Updates-Asia_Pacific.json