kumogata 0.2.12 → 0.2.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 660efba91fef6116d2af9c902ba7d8ef0e11c456
4
- data.tar.gz: 7d230216b8843370326fbca927dad8d095ca8ce7
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjUxM2NjNThmOTI1OTdlMmNlZTAyNjg5NzAwMjg4ZTViYzU5Mjk5NA==
5
+ data.tar.gz: !binary |-
6
+ ZmNlYWU3YTg2YjBkZDhkNzBkYTZmZTEyYzlhNzI2ZmJkMjU1NDQ0Zg==
5
7
  SHA512:
6
- metadata.gz: 6f45ee971165149b7a4a8d0c5745af69e44ab8a35e1fe92c356023c8284da8df08d910da2c70befad9e04728b43887b200d65ebc5655a829b7ee52eed1df876a
7
- data.tar.gz: 0f613dbc268a57ecf8945220c171baf8a0aa2d828d202128d43c291f60ed42a757f0c00d368ab28bb9298ae9aa9575c1e5eafea6d4e3e221ac42eb4a075fe176
8
+ metadata.gz: !binary |-
9
+ YjcxOGQxMjBmYTc2MTEwYjk0ZDMxNjgwMmNhNGNlMjJkYWI1YzE5YzRjNzlm
10
+ MDE1OTUwYTliZmM2NTcyNGMyYmM1MmExYjI2OTZkYzNiMThjYWRjMTMzMTNm
11
+ YzNmMWQ4ODRiNTliMDI5ZDI4OTQzOTk0ZjFjZmYzZWJkYWU1MTQ=
12
+ data.tar.gz: !binary |-
13
+ YmU1MzdmMGE3YTYyMzE3NzJhZjhjYjYwYzY0MGQzYjIzNjM0MzY4YTAxYzVj
14
+ NjAzY2MwNDFhNGVlNGFjODY1YjQ0MzM2N2ZhMjAzMmFkMjNmY2I2YmJjZmY0
15
+ Y2QzYjk0NzY1ZTBhYTM2ODY1ZTM3MzBiYjM2ZWZlNmYzZjQ3ZDk=
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?201403062244)](http://badge.fury.io/rb/kumogata)
9
- [![Build Status](https://drone.io/github.com/winebarrel/kumogata/status.png?201403062244)](https://drone.io/github.com/winebarrel/kumogata/latest)
8
+ [![Gem Version](https://badge.fury.io/rb/kumogata.png?201403072000)](http://badge.fury.io/rb/kumogata)
9
+ [![Build Status](https://drone.io/github.com/winebarrel/kumogata/status.png?201403072000)](https://drone.io/github.com/winebarrel/kumogata/latest)
10
10
 
11
11
  It can define a template in Ruby DSL, such as:
12
12
 
@@ -83,6 +83,9 @@ Options:
83
83
  --skip-replace-underscore
84
84
  --skip-delete-stack
85
85
  -p, --parameters KEY_VALUES
86
+ -e, --encrypt-parameters KEYS
87
+ --encryption-password PASS
88
+ --skip-send-password
86
89
  --capabilities CAPABILITIES
87
90
  --disable-rollback
88
91
  --notify SNS_TOPICS
@@ -208,6 +211,60 @@ end
208
211
  }
209
212
  ```
210
213
 
214
+ ### Encrypt parameters
215
+
216
+ * Command line
217
+
218
+ $ kumogata create template.rb -e 'Password1,Password2' -p 'Param1=xxx,Param2=xxx,Password1=xxx,Password2=xxx'
219
+
220
+ * Template
221
+
222
+ ```ruby
223
+ Parameters do
224
+ Param1 { Type "String" }
225
+ Param2 { Type "String" }
226
+ Password1 { Type "String"; NoEcho true }
227
+ Password2 { Type "String"; NoEcho true }
228
+ end # Parameters
229
+
230
+ Resources do
231
+ myEC2Instance do
232
+ Type "AWS::EC2::Instance"
233
+
234
+ Properties do
235
+ ImageId "ami-XXXXXXXX"
236
+
237
+ UserData do
238
+ Fn__Base64 (<<-EOS).fn_join
239
+ #!/bin/bash
240
+ /opt/aws/bin/cfn-init -s <%= Ref "AWS::StackName" %> -r myEC2Instance --region <%= Ref "AWS::Region" %>
241
+ EOS
242
+ end
243
+ end
244
+
245
+ Metadata do
246
+ AWS__CloudFormation__Init do
247
+ config do
248
+ commands do
249
+ any_command do
250
+ command (<<-EOS).fn_join
251
+ ENCRYPTION_PASSWORD="`echo <%= Ref Kumogata::ENCRYPTION_PASSWORD %> | base64 -d`"
252
+
253
+ # Decrypt Password1
254
+ echo '<%= Ref "Password1" %>' | base64 -d | openssl enc -d -aes256 -pass pass:"$ENCRYPTION_PASSWORD" > password1
255
+
256
+ # Decrypt Password2
257
+ echo '<%= Ref "Password2" %>' | base64 -d | openssl enc -d -aes256 -pass pass:"$ENCRYPTION_PASSWORD" > password2
258
+ EOS
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end # myEC2Instance
265
+ end # Resources
266
+ ```
267
+
211
268
  ## Demo
212
269
 
213
270
  * Create resources
data/bin/kumogata CHANGED
@@ -5,15 +5,15 @@ require 'kumogata'
5
5
 
6
6
  options = nil
7
7
 
8
- begin
9
- parsed = Kumogata::ArgumentParser.parse! {|parser, cmd, args, opts|
10
- if (opts.access_key_id? and not opts.secret_access_key?) or
11
- (not opts.access_key_id? and opts.secret_access_key?)
12
- puts parser.help
13
- exit 1
14
- end
15
- }
8
+ parsed = Kumogata::ArgumentParser.parse! {|parser, cmd, args, opts|
9
+ if (opts.access_key_id? and not opts.secret_access_key?) or
10
+ (not opts.access_key_id? and opts.secret_access_key?)
11
+ puts parser.help
12
+ exit 1
13
+ end
14
+ }
16
15
 
16
+ begin
17
17
  command, arguments, options = parsed
18
18
 
19
19
  aws_opts = {}
@@ -1,6 +1,11 @@
1
1
  Version = Kumogata::VERSION
2
2
 
3
+ module Kumogata
4
+ ENCRYPTION_PASSWORD = 'EncryptionPassword'
5
+ end
6
+
3
7
  class Kumogata::ArgumentParser
8
+
4
9
  DEFAULT_OPTIONS = {
5
10
  :replace_underscore => true,
6
11
  :delete_stack => true,
@@ -71,21 +76,24 @@ class Kumogata::ArgumentParser
71
76
  update_usage(opt)
72
77
 
73
78
  begin
74
- opt.on('-k', '--access-key ACCESS_KEY') {|v| options[:access_key_id] = v }
75
- opt.on('-s', '--secret-key SECRET_KEY') {|v| options[:secret_access_key] = v }
76
- opt.on('-r', '--region REGION') {|v| options[:region] = v }
77
- opt.on('' , '--skip-replace-underscore') { options[:replace_underscore] = false }
78
- opt.on('' , '--skip-delete-stack') { options[:delete_stack] = false }
79
- opt.on('-p', '--parameters KEY_VALUES', Array) {|v| options[:parameters] = v }
80
- opt.on('' , '--capabilities CAPABILITIES', Array) {|v| options[:capabilities] = v }
81
- opt.on('' , '--disable-rollback') { options[:disable_rollback] = true }
82
- opt.on('' , '--notify SNS_TOPICS', Array) {|v| options[:notify] = v }
83
- opt.on('' , '--timeout MINUTES', Integer) {|v| options[:timeout] = v }
84
- opt.on('' , '--result-log PATH') {|v| options[:result_log] = v }
85
- opt.on('' , '--force') { options[:force] = true }
86
- opt.on('-w', '--ignore-all-space') { options[:ignore_all_space] = true }
87
- opt.on('' , '--no-color') { options[:color] = false }
88
- opt.on('' , '--debug') { options[:debug] = true }
79
+ opt.on('-k', '--access-key ACCESS_KEY') {|v| options[:access_key_id] = v }
80
+ opt.on('-s', '--secret-key SECRET_KEY') {|v| options[:secret_access_key] = v }
81
+ opt.on('-r', '--region REGION') {|v| options[:region] = v }
82
+ opt.on('' , '--skip-replace-underscore') { options[:replace_underscore] = false }
83
+ opt.on('' , '--skip-delete-stack') { options[:delete_stack] = false }
84
+ opt.on('-p', '--parameters KEY_VALUES', Array) {|v| options[:parameters] = v }
85
+ opt.on('-e', '--encrypt-parameters KEYS', Array) {|v| options[:encrypt_parameters] = v }
86
+ opt.on('', '--encryption-password PASS') {|v| options[:encryption_password] = v }
87
+ opt.on('', '--skip-send-password') { options[:skip_send_password] = true }
88
+ opt.on('' , '--capabilities CAPABILITIES', Array) {|v| options[:capabilities] = v }
89
+ opt.on('' , '--disable-rollback') { options[:disable_rollback] = true }
90
+ opt.on('' , '--notify SNS_TOPICS', Array) {|v| options[:notify] = v }
91
+ opt.on('' , '--timeout MINUTES', Integer) {|v| options[:timeout] = v }
92
+ opt.on('' , '--result-log PATH') {|v| options[:result_log] = v }
93
+ opt.on('' , '--force') { options[:force] = true }
94
+ opt.on('-w', '--ignore-all-space') { options[:ignore_all_space] = true }
95
+ opt.on('' , '--no-color') { options[:color] = false }
96
+ opt.on('' , '--debug') { options[:debug] = true }
89
97
  opt.parse!
90
98
 
91
99
  unless (command = ARGV.shift)
@@ -108,8 +116,13 @@ class Kumogata::ArgumentParser
108
116
  if block_given?
109
117
  yield(opt, command, arguments, options)
110
118
  end
119
+
120
+ if options.parameters?
121
+ update_parameters(options)
122
+ end
111
123
  rescue => e
112
124
  $stderr.puts("#{e.message}")
125
+ raise e if options[:debug]
113
126
  exit 1
114
127
  end
115
128
  end
@@ -128,8 +141,8 @@ class Kumogata::ArgumentParser
128
141
  cmd_max = COMMANDS.keys.map {|i| i.to_s.length }.max
129
142
 
130
143
  cmd_arg_descs = COMMANDS.map {|command, attributes|
131
- arguments = attributes[:arguments]
132
144
  description = attributes[:description]
145
+ arguments = attributes[:arguments]
133
146
 
134
147
  [
135
148
  '%-*s %s' % [cmd_max, command, arguments_to_message(arguments)],
@@ -161,4 +174,26 @@ class Kumogata::ArgumentParser
161
174
  def arguments_to_message(arguments)
162
175
  arguments.map {|i| i.to_s.sub(/(.+)\?\Z/) { "[#{$1}]" }.upcase }.join(' ')
163
176
  end
177
+
178
+ def update_parameters(options)
179
+ parameters = {}
180
+ passwd = options.encryption_password || Kumogata::Crypt.mkpasswd(16)
181
+ enc_params = options.encrypt_parameters
182
+
183
+ options.parameters.each do |i|
184
+ key, value = i.split('=', 2)
185
+
186
+ if enc_params and (enc_params.include?('*') or enc_params.include?(key))
187
+ value = Kumogata::Crypt.encrypt(passwd, value)
188
+ end
189
+
190
+ parameters[key] = value
191
+ end
192
+
193
+ options.parameters = parameters
194
+
195
+ if options.encrypt_parameters? and not options.skip_send_password?
196
+ options.parameters[Kumogata::ENCRYPTION_PASSWORD] = passwd.encode64
197
+ end
198
+ end
164
199
  end
@@ -6,15 +6,12 @@ class Kumogata::Client
6
6
  end
7
7
 
8
8
  def create(path_or_url, stack_name = nil)
9
+ validate_stack_name(stack_name)
10
+
9
11
  @options.delete_stack = false if stack_name
10
12
  template = open_template(path_or_url)
11
-
12
- if @options.delete_stack?
13
- template['Resources'].each do |k, v|
14
- v['DeletionPolicy'] = 'Retain'
15
- end
16
- end
17
-
13
+ update_deletion_policy(template)
14
+ add_encryption_password(template)
18
15
  create_stack(template, stack_name)
19
16
  nil
20
17
  end
@@ -36,15 +33,20 @@ class Kumogata::Client
36
33
  end
37
34
 
38
35
  def update(path_or_url, stack_name)
36
+ validate_stack_name(stack_name)
37
+
39
38
  template = open(path_or_url) do |f|
40
39
  evaluate_template(f)
41
40
  end
42
41
 
42
+ add_encryption_password(template)
43
43
  update_stack(template, stack_name)
44
44
  nil
45
45
  end
46
46
 
47
47
  def delete(stack_name)
48
+ validate_stack_name(stack_name)
49
+
48
50
  if @options.force? or agree("Are you sure you want to delete `#{stack_name}`? ".yellow)
49
51
  delete_stack(stack_name)
50
52
  end
@@ -53,26 +55,36 @@ class Kumogata::Client
53
55
  end
54
56
 
55
57
  def list(stack_name = nil)
58
+ validate_stack_name(stack_name)
59
+
56
60
  stacks = describe_stacks(stack_name)
57
61
  JSON.pretty_generate(stacks).colorize_as(:json)
58
62
  end
59
63
 
60
64
  def export(stack_name)
65
+ validate_stack_name(stack_name)
66
+
61
67
  template = export_template(stack_name)
62
68
  devaluate_template(template).chomp.colorize_as(:ruby)
63
69
  end
64
70
 
65
71
  def show_events(stack_name)
72
+ validate_stack_name(stack_name)
73
+
66
74
  events = describe_events(stack_name)
67
75
  JSON.pretty_generate(events).colorize_as(:json)
68
76
  end
69
77
 
70
78
  def show_outputs(stack_name)
79
+ validate_stack_name(stack_name)
80
+
71
81
  outputs = describe_outputs(stack_name)
72
82
  JSON.pretty_generate(outputs).colorize_as(:json)
73
83
  end
74
84
 
75
85
  def show_resources(stack_name)
86
+ validate_stack_name(stack_name)
87
+
76
88
  resources = describe_resources(stack_name)
77
89
  JSON.pretty_generate(resources).colorize_as(:json)
78
90
  end
@@ -333,8 +345,7 @@ class Kumogata::Client
333
345
  if @options.parameters?
334
346
  parameters = {}
335
347
 
336
- @options.parameters.each do |i|
337
- key, value = i.split('=', 2)
348
+ @options.parameters.each do |key, value|
338
349
  parameters[key] = value
339
350
  end
340
351
 
@@ -342,6 +353,25 @@ class Kumogata::Client
342
353
  end
343
354
  end
344
355
 
356
+ def update_deletion_policy(template)
357
+ if @options.delete_stack?
358
+ template['Resources'].each do |k, v|
359
+ v['DeletionPolicy'] = 'Retain'
360
+ end
361
+ end
362
+ end
363
+
364
+ def add_encryption_password(template)
365
+ if @options.encrypt_parameters? and not @options.skip_send_password?
366
+ template['Parameters'] ||= {}
367
+
368
+ template['Parameters'][Kumogata::ENCRYPTION_PASSWORD] = {
369
+ 'Type' => 'String',
370
+ 'NoEcho' => 'true',
371
+ }
372
+ end
373
+ end
374
+
345
375
  def validate_template(template)
346
376
  result = @cloud_formation.validate_template(template.to_json)
347
377
 
@@ -364,7 +394,6 @@ class Kumogata::Client
364
394
  :resource_status,
365
395
  :resource_status_reason,
366
396
  :resource_type,
367
- :stack,
368
397
  :stack_id,
369
398
  :stack_name,
370
399
  :timestamp,
@@ -431,4 +460,12 @@ Stack Resource Summaries:
431
460
  i[0, 1].upcase + i[1..-1].downcase
432
461
  }.join
433
462
  end
463
+
464
+ def validate_stack_name(stack_name)
465
+ return unless stack_name
466
+
467
+ unless /\A[a-zA-Z][-a-zA-Z0-9]*\Z/i =~ stack_name
468
+ 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]*"
469
+ end
470
+ end
434
471
  end
@@ -0,0 +1,32 @@
1
+ class Kumogata::Crypt
2
+ ALGORITHM = 'aes256'
3
+ PASSWORD_CHARS = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789_*;:@{}()[]#$%&=-'
4
+
5
+ class << self
6
+ def encrypt(pass, str)
7
+ IO.popen("openssl enc -e -#{ALGORITHM} -pass pass:#{enquote(pass)}", "r+") {|io|
8
+ io.print str
9
+ io.close_write
10
+ io.read
11
+ }.encode64
12
+ end
13
+
14
+ def decrypt(pass, str)
15
+ IO.popen("openssl enc -d -#{ALGORITHM} -pass pass:#{enquote(pass)}", "r+") {|io|
16
+ io.print Base64.decode64(str)
17
+ io.close_write
18
+ io.read
19
+ }
20
+ end
21
+
22
+ def mkpasswd(n)
23
+ PASSWORD_CHARS.split(//).sample(n).join
24
+ end
25
+
26
+ private
27
+
28
+ def enquote(str)
29
+ "'" + str.gsub("'", %!'"'"'!) + "'"
30
+ end
31
+ end # of class methods
32
+ end
@@ -0,0 +1,18 @@
1
+ CodeRay::Encoders::Terminal::TOKEN_COLORS[:constant] = "\e[1;34m"
2
+ CodeRay::Encoders::Terminal::TOKEN_COLORS[:float] = "\e[36m"
3
+ CodeRay::Encoders::Terminal::TOKEN_COLORS[:integer] = "\e[36m"
4
+ CodeRay::Encoders::Terminal::TOKEN_COLORS[:keyword] = "\e[1;31m"
5
+
6
+ CodeRay::Encoders::Terminal::TOKEN_COLORS[:key] = {
7
+ :self => "\e[1;34m",
8
+ :char => "\e[1;34m",
9
+ :delimiter => "\e[1;34m",
10
+ }
11
+
12
+ CodeRay::Encoders::Terminal::TOKEN_COLORS[:string] = {
13
+ :self => "\e[32m",
14
+ :modifier => "\e[1;32m",
15
+ :char => "\e[1;32m",
16
+ :delimiter => "\e[1;32m",
17
+ :escape => "\e[1;32m",
18
+ }
@@ -1,3 +1,3 @@
1
1
  module Kumogata
2
- VERSION = '0.2.12'
2
+ VERSION = '0.2.13'
3
3
  end
data/lib/kumogata.rb CHANGED
@@ -18,22 +18,8 @@ require 'uuidtools'
18
18
 
19
19
  require 'kumogata/argument_parser'
20
20
  require 'kumogata/client'
21
+ require 'kumogata/crypt'
22
+ require 'kumogata/ext/coderay_ext'
21
23
  require 'kumogata/ext/json_ext'
22
24
  require 'kumogata/ext/string_ext'
23
25
  require 'kumogata/logger'
24
-
25
- CodeRay::Encoders::Terminal::TOKEN_COLORS[:key] = {
26
- :self => "\e[1;34m",
27
- :char => "\e[1;34m",
28
- :delimiter => "\e[1;34m",
29
- }
30
-
31
- CodeRay::Encoders::Terminal::TOKEN_COLORS[:string] = {
32
- :self => "\e[32m",
33
- :modifier => "\e[1;32m",
34
- :char => "\e[1;32m",
35
- :delimiter => "\e[1;32m",
36
- :escape => "\e[1;32m",
37
- }
38
-
39
- CodeRay::Encoders::Terminal::TOKEN_COLORS[:keyword] = "\e[31m"
@@ -86,7 +86,7 @@ Outputs do
86
86
  end
87
87
  EOS
88
88
 
89
- run_client(:create, :template => template, :options => {:parameters => ['InstanceType=m1.large']}) do |client, cf|
89
+ run_client(:create, :template => template, :options => {:parameters => {'InstanceType'=>'m1.large'}}) do |client, cf|
90
90
  json = eval_template(template, :update_deletion_policy => true).to_json
91
91
 
92
92
  output = make_double('output') do |obj|
@@ -177,4 +177,96 @@ end
177
177
  cf.should_receive(:stacks) { stacks }
178
178
  end
179
179
  end
180
+
181
+ it 'create a stack from Ruby template with invalid stack name' do
182
+ template = <<-EOS
183
+ Resources do
184
+ myEC2Instance do
185
+ Type "AWS::EC2::Instance"
186
+ Properties do
187
+ ImageId "ami-XXXXXXXX"
188
+ InstanceType "t1.micro"
189
+ end
190
+ end
191
+ end
192
+
193
+ Outputs do
194
+ AZ do
195
+ Value do
196
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
197
+ end
198
+ end
199
+ end
200
+ EOS
201
+
202
+ expect {
203
+ run_client(:create, :arguments => ['0MyStack'], :template => template)
204
+ }.to raise_error("1 validation error detected: Value '0MyStack' at 'stackName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*")
205
+ end
206
+
207
+ it 'create a stack from Ruby template with encrypted parameters' do
208
+ template = <<-EOS
209
+ Parameters do
210
+ InstanceType do
211
+ Default "t1.micro"
212
+ Description "Instance Type"
213
+ Type "String"
214
+ end
215
+ end
216
+
217
+ Resources do
218
+ myEC2Instance do
219
+ Type "AWS::EC2::Instance"
220
+ Properties do
221
+ ImageId "ami-XXXXXXXX"
222
+ InstanceType { Ref "InstanceType" }
223
+ end
224
+ end
225
+ end
226
+
227
+ Outputs do
228
+ AZ do
229
+ Value do
230
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
231
+ end
232
+ end
233
+ end
234
+ EOS
235
+
236
+ run_client(:create, :template => template, :options => {:parameters => {'InstanceType'=>'m1.large'}, :encrypt_parameters => true}) do |client, cf|
237
+ json = eval_template(template, :update_deletion_policy => true, :add_encryption_password => true).to_json
238
+
239
+ output = make_double('output') do |obj|
240
+ obj.should_receive(:key) { 'AZ' }
241
+ obj.should_receive(:value) { 'ap-northeast-1b' }
242
+ end
243
+
244
+ resource_summary = make_double('resource_summary') do |obj|
245
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
246
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
247
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
248
+ obj.should_receive(:[]).with(:resource_status) { 'CREATE_COMPLETE' }
249
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
250
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
251
+ end
252
+
253
+ stack = make_double('stack') do |obj|
254
+ obj.should_receive(:status).and_return(
255
+ 'CREATE_COMPLETE', 'CREATE_COMPLETE',
256
+ 'DELETE_COMPLETE', 'DELETE_COMPLETE', 'DELETE_COMPLETE')
257
+ obj.should_receive(:outputs) { [output] }
258
+ obj.should_receive(:resource_summaries) { [resource_summary] }
259
+ obj.should_receive(:delete)
260
+ end
261
+
262
+ stacks = make_double('stacks') do |obj|
263
+ obj.should_receive(:create)
264
+ .with('kumogata-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', json, {:parameters=>{"InstanceType"=>"m1.large"}}) { stack }
265
+ obj.should_receive(:[])
266
+ .with('kumogata-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') { stack }
267
+ end
268
+
269
+ cf.should_receive(:stacks).twice { stacks }
270
+ end
271
+ end
180
272
  end
@@ -0,0 +1,19 @@
1
+ describe Kumogata::Crypt do
2
+ it 'encrypt string' do
3
+ encrypted = Kumogata::Crypt.encrypt("my_password", "jugem jugem")
4
+ decrypted = Kumogata::Crypt.decrypt("my_password", encrypted)
5
+ expect(decrypted).to eq("jugem jugem")
6
+ end
7
+
8
+ it 'encrypt long string' do
9
+ encrypted = Kumogata::Crypt.encrypt("my_password", "jugem jugem" * 10240)
10
+ decrypted = Kumogata::Crypt.decrypt("my_password", encrypted)
11
+ expect(decrypted).to eq("jugem jugem" * 10240)
12
+ end
13
+
14
+ it 'make password' do
15
+ passwd = Kumogata::Crypt.mkpasswd(16)
16
+ expect(passwd).to be_kind_of(String)
17
+ expect(passwd.length).to eq(16)
18
+ end
19
+ end
@@ -10,7 +10,6 @@ describe 'Kumogata::Client#show_events' do
10
10
  obj.should_receive(:resource_status) { "CREATE_FAILED" }
11
11
  obj.should_receive(:resource_status_reason) { "The following resource(s) failed to create: [myEC2Instance]. " }
12
12
  obj.should_receive(:resource_type) { "AWS::CloudFormation::Stack" }
13
- obj.should_receive(:stack) { "#<AWS::CloudFormation::Stack:0x007ffe4384ca80>" }
14
13
  obj.should_receive(:stack_id) { "arn:aws:cloudformation:ap-northeast-1:822997939312:stack/kumogata-f11118a4-a4f7-11e3-8183-98fe943e66ca/f1381a30-a4f7-11e3-a340-506cf9a1c096" }
15
14
  obj.should_receive(:stack_name) { "kumogata-f11118a4-a4f7-11e3-8183-98fe943e66ca" }
16
15
  obj.should_receive(:timestamp) { "2014-03-06 06:24:21 UTC" }
@@ -38,7 +37,6 @@ describe 'Kumogata::Client#show_events' do
38
37
  "ResourceStatus": "CREATE_FAILED",
39
38
  "ResourceStatusReason": "The following resource(s) failed to create: [myEC2Instance]. ",
40
39
  "ResourceType": "AWS::CloudFormation::Stack",
41
- "Stack": "#<AWS::CloudFormation::Stack:0x007ffe4384ca80>",
42
40
  "StackId": "arn:aws:cloudformation:ap-northeast-1:822997939312:stack/kumogata-f11118a4-a4f7-11e3-8183-98fe943e66ca/f1381a30-a4f7-11e3-a340-506cf9a1c096",
43
41
  "StackName": "kumogata-f11118a4-a4f7-11e3-8183-98fe943e66ca",
44
42
  "Timestamp": "2014-03-06 06:24:21 UTC"
@@ -83,7 +83,7 @@ Outputs do
83
83
  end
84
84
  EOS
85
85
 
86
- run_client(:update, :arguments => ['MyStack'], :template => template, :options => {:parameters => ['InstanceType=m1.large']}) do |client, cf|
86
+ run_client(:update, :arguments => ['MyStack'], :template => template, :options => {:parameters => {'InstanceType'=>'m1.large'}}) do |client, cf|
87
87
  json = eval_template(template).to_json
88
88
 
89
89
  output = make_double('output') do |obj|
@@ -116,4 +116,92 @@ end
116
116
  cf.should_receive(:stacks) { stacks }
117
117
  end
118
118
  end
119
+
120
+ it 'update a stack from Ruby template with invalid stack name' do
121
+ template = <<-EOS
122
+ Resources do
123
+ myEC2Instance do
124
+ Type "AWS::EC2::Instance"
125
+ Properties do
126
+ ImageId "ami-XXXXXXXX"
127
+ end
128
+ end
129
+ end
130
+
131
+ Outputs do
132
+ AZ do
133
+ Value do
134
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
135
+ end
136
+ end
137
+ end
138
+ EOS
139
+
140
+ expect {
141
+ run_client(:update, :arguments => ['0MyStack'], :template => template)
142
+ }.to raise_error("1 validation error detected: Value '0MyStack' at 'stackName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*")
143
+ end
144
+
145
+ it 'update a stack from Ruby template with encrypted parameters' do
146
+ template = <<-EOS
147
+ Parameters do
148
+ InstanceType do
149
+ Default "t1.micro"
150
+ Description "Instance Type"
151
+ Type "String"
152
+ end
153
+ end
154
+
155
+ Resources do
156
+ myEC2Instance do
157
+ Type "AWS::EC2::Instance"
158
+ Properties do
159
+ ImageId "ami-XXXXXXXX"
160
+ InstanceType { Ref "InstanceType" }
161
+ end
162
+ end
163
+ end
164
+
165
+ Outputs do
166
+ AZ do
167
+ Value do
168
+ Fn__GetAtt "myEC2Instance", "AvailabilityZone"
169
+ end
170
+ end
171
+ end
172
+ EOS
173
+
174
+ run_client(:update, :arguments => ['MyStack'], :template => template, :options => {:parameters => {'InstanceType'=>'m1.large'}, :encrypt_parameters => true}) do |client, cf|
175
+ json = eval_template(template, :add_encryption_password => true).to_json
176
+
177
+ output = make_double('output') do |obj|
178
+ obj.should_receive(:key) { 'AZ' }
179
+ obj.should_receive(:value) { 'ap-northeast-1b' }
180
+ end
181
+
182
+ resource_summary = make_double('resource_summary') do |obj|
183
+ obj.should_receive(:[]).with(:logical_resource_id) { 'myEC2Instance' }
184
+ obj.should_receive(:[]).with(:physical_resource_id) { 'i-XXXXXXXX' }
185
+ obj.should_receive(:[]).with(:resource_type) { 'AWS::EC2::Instance' }
186
+ obj.should_receive(:[]).with(:resource_status) { 'UPDATE_COMPLETE' }
187
+ obj.should_receive(:[]).with(:resource_status_reason) { nil }
188
+ obj.should_receive(:[]).with(:last_updated_timestamp) { '2014-03-02 04:35:12 UTC' }
189
+ end
190
+
191
+ stack = make_double('stack') do |obj|
192
+ obj.should_receive(:update).with(:template => json, :parameters=>{"InstanceType"=>"m1.large"})
193
+ obj.should_receive(:status).and_return(
194
+ 'UPDATE_COMPLETE', 'UPDATE_COMPLETE', 'UPDATE_COMPLETE')
195
+ obj.should_receive(:outputs) { [output] }
196
+ obj.should_receive(:resource_summaries) { [resource_summary] }
197
+ end
198
+
199
+ stacks = make_double('stacks') do |obj|
200
+ obj.should_receive(:[])
201
+ .with('MyStack') { stack }
202
+ end
203
+
204
+ cf.should_receive(:stacks) { stacks }
205
+ end
206
+ end
119
207
  end
data/spec/spec_helper.rb CHANGED
@@ -24,6 +24,7 @@ def run_client(command, options = {})
24
24
  kumogata_template = options[:template]
25
25
  kumogata_arguments = options[:arguments] || []
26
26
  kumogata_options = Kumogata::ArgumentParser::DEFAULT_OPTIONS.merge(options[:options] || {})
27
+ kumogata_options[:result_log] = '/dev/null'
27
28
  template_ext = options[:template_ext] || '.rb'
28
29
 
29
30
  client = Kumogata::Client.new(kumogata_options)
@@ -52,6 +53,10 @@ def eval_template(template, options = {})
52
53
  update_deletion_policy(template)
53
54
  end
54
55
 
56
+ if options[:add_encryption_password]
57
+ add_encryption_password(template)
58
+ end
59
+
55
60
  return template
56
61
  end
57
62
 
@@ -61,6 +66,15 @@ def update_deletion_policy(template)
61
66
  end
62
67
  end
63
68
 
69
+ def add_encryption_password(template)
70
+ template['Parameters'] ||= {}
71
+
72
+ template['Parameters'][Kumogata::ENCRYPTION_PASSWORD] = {
73
+ 'Type' => 'String',
74
+ 'NoEcho' => 'true',
75
+ }
76
+ end
77
+
64
78
  def make_double(name)
65
79
  obj = double(name)
66
80
  yield(obj)
metadata CHANGED
@@ -1,181 +1,181 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumogata
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.2.13
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-06 00:00:00.000000000 Z
11
+ date: 2014-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ! '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: coderay
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ! '>='
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ! '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: diffy
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ! '>='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ! '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: dslh
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: 0.2.4
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
68
  version: 0.2.4
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: hashie
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ! '>='
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ! '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: highline
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ! '>='
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ! '>='
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: json
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '>='
101
+ - - ! '>='
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ! '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: term-ansicolor
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - '>='
115
+ - - ! '>='
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - '>='
122
+ - - ! '>='
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: uuidtools
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - '>='
129
+ - - ! '>='
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - '>='
136
+ - - ! '>='
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: bundler
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - '>='
143
+ - - ! '>='
144
144
  - !ruby/object:Gem::Version
145
145
  version: '0'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - '>='
150
+ - - ! '>='
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: rake
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - '>='
157
+ - - ! '>='
158
158
  - !ruby/object:Gem::Version
159
159
  version: '0'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
- - - '>='
164
+ - - ! '>='
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: rspec
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
- - - '>='
171
+ - - ! '>='
172
172
  - !ruby/object:Gem::Version
173
173
  version: 2.11.0
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
- - - '>='
178
+ - - ! '>='
179
179
  - !ruby/object:Gem::Version
180
180
  version: 2.11.0
181
181
  description: A tool for AWS CloudFormation. It can define a template in Ruby DSL.
@@ -197,6 +197,8 @@ files:
197
197
  - lib/kumogata.rb
198
198
  - lib/kumogata/argument_parser.rb
199
199
  - lib/kumogata/client.rb
200
+ - lib/kumogata/crypt.rb
201
+ - lib/kumogata/ext/coderay_ext.rb
200
202
  - lib/kumogata/ext/json_ext.rb
201
203
  - lib/kumogata/ext/string_ext.rb
202
204
  - lib/kumogata/logger.rb
@@ -207,6 +209,7 @@ files:
207
209
  - spec/Drupal_Single_Instance.template.rb
208
210
  - spec/kumogata_convert_spec.rb
209
211
  - spec/kumogata_create_spec.rb
212
+ - spec/kumogata_crypt_spec.rb
210
213
  - spec/kumogata_delete_spec.rb
211
214
  - spec/kumogata_diff_spec.rb
212
215
  - spec/kumogata_export_spec.rb
@@ -227,17 +230,17 @@ require_paths:
227
230
  - lib
228
231
  required_ruby_version: !ruby/object:Gem::Requirement
229
232
  requirements:
230
- - - '>='
233
+ - - ! '>='
231
234
  - !ruby/object:Gem::Version
232
235
  version: '0'
233
236
  required_rubygems_version: !ruby/object:Gem::Requirement
234
237
  requirements:
235
- - - '>='
238
+ - - ! '>='
236
239
  - !ruby/object:Gem::Version
237
240
  version: '0'
238
241
  requirements: []
239
242
  rubyforge_project:
240
- rubygems_version: 2.0.14
243
+ rubygems_version: 2.1.11
241
244
  signing_key:
242
245
  specification_version: 4
243
246
  summary: A tool for AWS CloudFormation.
@@ -246,6 +249,7 @@ test_files:
246
249
  - spec/Drupal_Single_Instance.template.rb
247
250
  - spec/kumogata_convert_spec.rb
248
251
  - spec/kumogata_create_spec.rb
252
+ - spec/kumogata_crypt_spec.rb
249
253
  - spec/kumogata_delete_spec.rb
250
254
  - spec/kumogata_diff_spec.rb
251
255
  - spec/kumogata_export_spec.rb
@@ -256,4 +260,3 @@ test_files:
256
260
  - spec/kumogata_update_spec.rb
257
261
  - spec/kumogata_validate_spec.rb
258
262
  - spec/spec_helper.rb
259
- has_rdoc: