kumogata 0.3.9 → 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
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