cfndk 0.1.1.2 → 0.1.2

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.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +1 -1
  3. data/.gitignore +1 -0
  4. data/.rspec +2 -0
  5. data/Gemfile.lock +9 -5
  6. data/README.md +124 -10
  7. data/cfndk.gemspec +3 -0
  8. data/docker/Dockerfile +8 -0
  9. data/docker/build.sh +3 -0
  10. data/docker/cfndk.sh +14 -0
  11. data/lib/cfndk.rb +9 -0
  12. data/lib/cfndk/change_set_command.rb +14 -8
  13. data/lib/cfndk/command.rb +14 -6
  14. data/lib/cfndk/credential_provider_chain.rb +12 -42
  15. data/lib/cfndk/credential_resolvable.rb +10 -0
  16. data/lib/cfndk/diff.rb +38 -0
  17. data/lib/cfndk/global_config.rb +32 -2
  18. data/lib/cfndk/key_pair.rb +33 -1
  19. data/lib/cfndk/key_pair_command.rb +10 -3
  20. data/lib/cfndk/key_pairs.rb +12 -0
  21. data/lib/cfndk/stack.rb +58 -59
  22. data/lib/cfndk/stack_command.rb +26 -8
  23. data/lib/cfndk/stacks.rb +16 -0
  24. data/lib/cfndk/template_packager.rb +210 -0
  25. data/lib/cfndk/uuid.rb +10 -0
  26. data/lib/cfndk/version.rb +1 -1
  27. data/spec/cfndk_spec.rb +1 -1
  28. data/spec/cfndk_stack_create_spec.rb +365 -5
  29. data/spec/cfndk_stack_destroy_spec.rb +64 -0
  30. data/spec/cfndk_stack_update_spec.rb +86 -0
  31. data/spec/fixtures/big_vpc.yaml +533 -0
  32. data/spec/fixtures/lambda_function/index.js +4 -0
  33. data/spec/fixtures/lambda_function/lambda_function.json +4 -0
  34. data/spec/fixtures/lambda_function/lambda_function.yaml +28 -0
  35. data/spec/fixtures/nested_stack.json +35 -0
  36. data/spec/fixtures/nested_stack.yaml +20 -0
  37. data/spec/fixtures/serverless_function/index.js +4 -0
  38. data/spec/fixtures/serverless_function/serverless_function.json +4 -0
  39. data/spec/fixtures/serverless_function/serverless_function.yaml +21 -0
  40. data/spec/fixtures/stack.json +8 -0
  41. data/spec/fixtures/stack.template.json +39 -0
  42. data/spec/fixtures/stack.yaml +22 -0
  43. data/spec/fixtures/vpc.template.json +40 -0
  44. data/vagrant/Vagrantfile +89 -0
  45. metadata +80 -4
@@ -2,11 +2,12 @@ module CFnDK
2
2
  class StackCommand < Thor
3
3
  include SubcommandHelpReturnable
4
4
  include ConfigFileLoadable
5
+ include CredentialResolvable
5
6
 
6
7
  class_option :verbose, type: :boolean, aliases: 'v', desc: 'More verbose output.'
7
8
  class_option :color, type: :boolean, default: true, desc: 'Use colored output'
8
9
  class_option :config_path, type: :string, aliases: 'c', default: "#{Dir.getwd}/cfndk.yml", desc: 'The configuration file to use'
9
- class_option :stack_names, type: :array, desc: 'Target stack names'
10
+ class_option :stack_names, type: :array, aliases: 's', desc: 'Target stack names'
10
11
 
11
12
  desc 'create', 'Create stack'
12
13
  option :uuid, type: :string, aliases: 'u', default: ENV['CFNDK_UUID'] || nil, desc: 'Use UUID'
@@ -14,11 +15,16 @@ module CFnDK
14
15
  def create
15
16
  CFnDK.logger.info 'create...'.color(:green)
16
17
  data = load_config_data(options)
17
-
18
- credentials = CFnDK::CredentialProviderChain.new.resolve
18
+ credentials = resolve_credential(data, options)
19
+ global_config = CFnDK::GlobalConfig.new(data, options)
19
20
  stacks = CFnDK::Stacks.new(data, options, credentials)
21
+
22
+ global_config.pre_command_execute
23
+ stacks.pre_command_execute
20
24
  stacks.validate
21
25
  stacks.create
26
+ stacks.post_command_execute
27
+ global_config.post_command_execute
22
28
  return 0
23
29
  rescue => e
24
30
  CFnDK.logger.error "#{e.class}: #{e.message}".color(:red)
@@ -34,11 +40,16 @@ module CFnDK
34
40
  def update
35
41
  CFnDK.logger.info 'update...'.color(:green)
36
42
  data = load_config_data(options)
37
-
38
- credentials = CFnDK::CredentialProviderChain.new.resolve
43
+ credentials = resolve_credential(data, options)
44
+ global_config = CFnDK::GlobalConfig.new(data, options)
39
45
  stacks = CFnDK::Stacks.new(data, options, credentials)
46
+
47
+ global_config.pre_command_execute
48
+ stacks.pre_command_execute
40
49
  stacks.validate
41
50
  stacks.update
51
+ stacks.post_command_execute
52
+ global_config.post_command_execute
42
53
  return 0
43
54
  rescue => e
44
55
  CFnDK.logger.error "#{e.class}: #{e.message}".color(:red)
@@ -54,8 +65,8 @@ module CFnDK
54
65
  def destroy
55
66
  CFnDK.logger.info 'destroy...'.color(:green)
56
67
  data = load_config_data(options)
68
+ credentials = resolve_credential(data, options)
57
69
 
58
- credentials = CFnDK::CredentialProviderChain.new.resolve
59
70
  stacks = CFnDK::Stacks.new(data, options, credentials)
60
71
 
61
72
  if options[:force] || yes?('Are you sure you want to destroy? (y/n)', :yellow)
@@ -77,9 +88,15 @@ module CFnDK
77
88
  def validate
78
89
  CFnDK.logger.info 'validate...'.color(:green)
79
90
  data = load_config_data(options)
80
- credentials = CFnDK::CredentialProviderChain.new.resolve
91
+ credentials = resolve_credential(data, options)
92
+ global_config = CFnDK::GlobalConfig.new(data, options)
81
93
  stacks = CFnDK::Stacks.new(data, options, credentials)
94
+
95
+ global_config.pre_command_execute
96
+ stacks.pre_command_execute
82
97
  stacks.validate
98
+ stacks.post_command_execute
99
+ global_config.post_command_execute
83
100
  return 0
84
101
  rescue => e
85
102
  CFnDK.logger.error "#{e.class}: #{e.message}".color(:red)
@@ -95,7 +112,8 @@ module CFnDK
95
112
  def report
96
113
  CFnDK.logger.info 'report...'.color(:green)
97
114
  data = load_config_data(options)
98
- credentials = CFnDK::CredentialProviderChain.new.resolve
115
+ credentials = resolve_credential(data, options)
116
+
99
117
  stacks = CFnDK::Stacks.new(data, options, credentials)
100
118
  stacks.report
101
119
  return 0
@@ -107,6 +107,22 @@ module CFnDK
107
107
  end
108
108
  end
109
109
 
110
+ def pre_command_execute
111
+ @sequence.each do |stacks|
112
+ stacks.each do |name|
113
+ @stacks[name].pre_command_execute
114
+ end
115
+ end
116
+ end
117
+
118
+ def post_command_execute
119
+ @sequence.each do |stacks|
120
+ stacks.each do |name|
121
+ @stacks[name].post_command_execute
122
+ end
123
+ end
124
+ end
125
+
110
126
  private
111
127
 
112
128
  def prepare_stack(data)
@@ -0,0 +1,210 @@
1
+ using Polyfill(
2
+ String: %w[#delete_prefix]
3
+ )
4
+
5
+ module CFnDK
6
+ class TemplatePackager
7
+ def initialize(template_file, region, package, global_config, s3_client, sts_client)
8
+ @template_file = template_file
9
+ @region = region
10
+ @package = package
11
+ @global_config = global_config
12
+ @s3_client = s3_client
13
+ @sts_client = sts_client
14
+ @template_body = nil
15
+ @is_uploaded = false
16
+ end
17
+
18
+ def large_template?
19
+ template_body.size > 51200
20
+ end
21
+
22
+ def template_body
23
+ package_templte
24
+ end
25
+
26
+ def upload_template_file
27
+ key = [@global_config.s3_template_hash, @template_file].compact.join('/')
28
+ url = "https://s3.amazonaws.com/#{bucket_name}/#{key}"
29
+
30
+ unless @is_uploaded
31
+ create_bucket
32
+ @s3_client.put_object(
33
+ body: template_body,
34
+ bucket: bucket_name,
35
+ key: key
36
+ )
37
+ @is_uploaded = true
38
+ CFnDK.logger.info('Put S3 object: ' + url + ' Size: ' + template_body.size.to_s)
39
+ end
40
+ url
41
+ end
42
+
43
+ def package_templte
44
+ if !@template_body
45
+ if !@package
46
+ @template_body = File.open(@template_file, 'r').read
47
+ return @template_body
48
+ end
49
+ orgTemplate = File.open(@template_file, 'r').read
50
+ CFnDK.logger.debug('Original Template:' + orgTemplate)
51
+ if is_json?(orgTemplate)
52
+ data = JSON.parse(orgTemplate)
53
+ else
54
+ data = YAML.load(orgTemplate.gsub(/!/, '____CFNDK!____'))
55
+ end
56
+
57
+ if data['Resources']
58
+ data['Resources'].each do |k, v|
59
+ next unless v.key?('Type')
60
+ t = v['Type']
61
+ properties = v['Properties'] || {}
62
+ case t
63
+ when 'AWS::CloudFormation::Stack' then
64
+ if properties['TemplateURL'] =~ /^\s*./
65
+ tp = TemplatePackager.new(File.dirname(@template_file) + '/' + properties['TemplateURL'].sub(/^\s*.\//, ''), @region, @package, @global_config, @s3_client, @sts_client)
66
+ v['Properties']['TemplateURL'] = tp.upload_template_file
67
+ end
68
+ when 'AWS::Lambda::Function' then
69
+ if properties['Code'].kind_of?(String)
70
+ result = upload_zip_file(File.dirname(@template_file) + '/' + properties['Code'].sub(/^\s*.\//, ''))
71
+ v['Properties']['Code'] = {
72
+ 'S3Bucket' => result['bucket'],
73
+ 'S3Key' => result['key']
74
+ }
75
+ end
76
+ when 'AWS::Serverless::Function' then
77
+ if properties['CodeUri'].kind_of?(String)
78
+ result = upload_zip_file(File.dirname(@template_file) + '/' + properties['CodeUri'].sub(/^\s*.\//, ''))
79
+ v['Properties']['CodeUri'] = {
80
+ 'Bucket' => result['bucket'],
81
+ 'Key' => result['key']
82
+ }
83
+ end
84
+ when 'AWS::Serverless::Api' then
85
+ if properties['DefinitionUri'].kind_of?(String)
86
+ result = upload_file(File.dirname(@template_file) + '/' + properties['DefinitionUri'].sub(/^\s*.\//, ''))
87
+ v['Properties']['DefinitionUri'] = {
88
+ 'Bucket' => result['bucket'],
89
+ 'Key' => result['key']
90
+ }
91
+ end
92
+ when 'AWS::ApiGateway::RestApi' then
93
+ if properties['BodyS3Location'].kind_of?(String)
94
+ result = upload_file(File.dirname(@template_file) + '/' + properties['BodyS3Location'].sub(/^\s*.\//, ''))
95
+ v['Properties']['BodyS3Location'] = {
96
+ 'Bucket' => result['bucket'],
97
+ 'Key' => result['key']
98
+ }
99
+ end
100
+ end
101
+ ## TODO support resources
102
+ # * AWS::AppSync::GraphQLSchema DefinitionS3Location
103
+ # * AWS::AppSync::Resolver RequestMappingTemplateS3Location
104
+ # * AWS::AppSync::Resolver ResponseMappingTemplateS3Location
105
+ # * AWS::ElasticBeanstalk::ApplicationVersion SourceBundle
106
+ # * AWS::Glue::Job Command ScriptLocation
107
+ # * AWS::Include Location
108
+ end
109
+ end
110
+
111
+ if is_json?(orgTemplate)
112
+ @template_body = JSON.dump(data)
113
+ else
114
+ @template_body = YAML.dump_stream(data).gsub(/____CFNDK!____/, '!')
115
+ end
116
+ CFnDK.logger.info('Template Packager diff: ' + @template_file)
117
+ CFnDK.logger.info(CFnDK.diff(orgTemplate, @template_body).to_s)
118
+ CFnDK.logger.debug('Package Template size: ' + @template_body.size.to_s)
119
+ CFnDK.logger.debug('Package Template:' + @template_body)
120
+ end
121
+ @template_body
122
+ end
123
+
124
+ private
125
+
126
+ def upload_zip_file(path)
127
+ create_bucket
128
+ key = [@global_config.s3_template_hash, path.sub(/^.\//, '') + ".zip"].compact.join('/')
129
+
130
+
131
+ buffer = Zip::OutputStream.write_buffer do |out|
132
+ Dir.glob(path + '/**/*') do |file|
133
+ if (!File.directory?(file))
134
+ out.put_next_entry(file.delete_prefix(path + '/'))
135
+ out.write(File.open(file, 'r').read)
136
+ end
137
+ end
138
+ end
139
+
140
+ @s3_client.put_object(
141
+ body: buffer.string,
142
+ bucket: bucket_name,
143
+ key: key
144
+ )
145
+ url = "https://s3.amazonaws.com/#{bucket_name}/#{key}"
146
+ CFnDK.logger.info('Put S3 object: ' + url)
147
+ {
148
+ 'bucket' => bucket_name,
149
+ 'key' => key
150
+ }
151
+ end
152
+
153
+ def upload_file(path)
154
+ create_bucket
155
+ key = [@global_config.s3_template_hash, path.sub(/^.\//, '')].compact.join('/')
156
+
157
+ @s3_client.put_object(
158
+ body: File.open(path, 'r').read,
159
+ bucket: bucket_name,
160
+ key: key
161
+ )
162
+ url = "https://s3.amazonaws.com/#{bucket_name}/#{key}"
163
+ CFnDK.logger.info('Put S3 object: ' + url)
164
+ {
165
+ 'bucket' => bucket_name,
166
+ 'key' => key
167
+ }
168
+ end
169
+
170
+ def create_bucket
171
+ begin
172
+ @s3_client.head_bucket(bucket: bucket_name)
173
+ rescue Aws::S3::Errors::NotFound, Aws::S3::Errors::Forbidden
174
+ @s3_client.create_bucket(bucket: bucket_name)
175
+ CFnDK.logger.info('Creatt S3 bucket: ' + bucket_name)
176
+ @s3_client.put_bucket_lifecycle_configuration(
177
+ bucket: bucket_name,
178
+ lifecycle_configuration: {
179
+ rules: [
180
+ {
181
+ expiration: {
182
+ days: 1,
183
+ },
184
+ status: 'Enabled',
185
+ id: 'Delete Old Files',
186
+ prefix: '',
187
+ abort_incomplete_multipart_upload: {
188
+ days_after_initiation: 1,
189
+ },
190
+ },
191
+ ],
192
+ }
193
+ )
194
+ end
195
+ end
196
+
197
+ def bucket_name
198
+ resp = @sts_client.get_caller_identity({})
199
+ resp.account + '-' + @region + '-' + @global_config.s3_template_bucket
200
+ end
201
+
202
+ def is_json?(str)
203
+ begin
204
+ !!JSON.parse(str)
205
+ rescue
206
+ false
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,10 @@
1
+ module CFnDK
2
+ class Uuid
3
+ include Singleton
4
+
5
+ attr_reader :uuid
6
+ def initialize()
7
+ @uuid = SecureRandom.uuid
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module CFnDK
2
- VERSION = '0.1.1.2'.freeze
2
+ VERSION = '0.1.2'.freeze
3
3
  end
@@ -36,7 +36,7 @@ RSpec.describe 'CFnDK', type: :aruba do
36
36
  it 'displays version' do
37
37
  aggregate_failures do
38
38
  expect(last_command_started).to be_successfully_executed
39
- expect(last_command_started).to have_output(/0.1.1/)
39
+ expect(last_command_started).to have_output(/0.1.2/)
40
40
  end
41
41
  end
42
42
  end
@@ -97,7 +97,7 @@ RSpec.describe 'CFnDK', type: :aruba do
97
97
  end
98
98
  after(:each) { run_command('cfndk destroy -f') }
99
99
  end
100
- context 'with a 51200byte template stack' do
100
+ context 'with a stack and enabled is true', enabled: true do
101
101
  yaml = <<-"YAML"
102
102
  global:
103
103
  stacks:
@@ -105,11 +105,59 @@ RSpec.describe 'CFnDK', type: :aruba do
105
105
  template_file: vpc.yaml
106
106
  parameter_input: vpc.json
107
107
  timeout_in_minutes: 2
108
+ enabled: true
108
109
  YAML
109
110
  before(:each) { write_file(file, yaml) }
110
111
  before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
111
112
  before(:each) { copy('%/vpc.json', 'vpc.json') }
112
- before(:each) { append_to_file('vpc.yaml', ' ' * (51200 - file_size('vpc.yaml').to_i)) }
113
+ before(:each) { run_command('cfndk stack create') }
114
+ it do
115
+ aggregate_failures do
116
+ expect(last_command_started).to be_successfully_executed
117
+ expect(last_command_started).to have_output(/INFO validate stack: Test$/)
118
+ expect(last_command_started).to have_output(/INFO creating stack: Test$/)
119
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
120
+ end
121
+ end
122
+ after(:each) { run_command('cfndk destroy -f') }
123
+ end
124
+ context 'with a stack and enabled is false', enabled: true do
125
+ yaml = <<-"YAML"
126
+ global:
127
+ stacks:
128
+ Test:
129
+ template_file: vpc.yaml
130
+ parameter_input: vpc.json
131
+ timeout_in_minutes: 2
132
+ enabled: false
133
+ YAML
134
+ before(:each) { write_file(file, yaml) }
135
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
136
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
137
+ before(:each) { run_command('cfndk stack create') }
138
+ it do
139
+ aggregate_failures do
140
+ expect(last_command_started).to be_successfully_executed
141
+ expect(last_command_started).to have_output(/INFO create.../)
142
+ expect(last_command_started).not_to have_output(/INFO validate stack: Test$/)
143
+ expect(last_command_started).not_to have_output(/INFO creating stack: Test$/)
144
+ expect(last_command_started).not_to have_output(/INFO created stack: Test$/)
145
+ end
146
+ end
147
+ after(:each) { run_command('cfndk destroy -f') }
148
+ end
149
+ context 'with a 51200byte template stack' do
150
+ yaml = <<-"YAML"
151
+ global:
152
+ stacks:
153
+ Test:
154
+ template_file: vpc.yaml
155
+ parameter_input: vpc.json
156
+ timeout_in_minutes: 2
157
+ YAML
158
+ before(:each) { write_file(file, yaml) }
159
+ before(:each) { copy('%/big_vpc.yaml', 'vpc.yaml') }
160
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
113
161
  before(:each) { run_command('cfndk stack create') }
114
162
  it 'displays created stack log' do
115
163
  aggregate_failures do
@@ -122,7 +170,7 @@ RSpec.describe 'CFnDK', type: :aruba do
122
170
  end
123
171
  after(:each) { run_command('cfndk destroy -f') }
124
172
  end
125
- context 'with a 51201byte template stack', big: true do
173
+ context 'with a 51201byte template stack', big: true, bigbig: true do
126
174
  yaml = <<-"YAML"
127
175
  global:
128
176
  stacks:
@@ -132,9 +180,9 @@ RSpec.describe 'CFnDK', type: :aruba do
132
180
  timeout_in_minutes: 2
133
181
  YAML
134
182
  before(:each) { write_file(file, yaml) }
135
- before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
183
+ before(:each) { copy('%/big_vpc.yaml', 'vpc.yaml') }
136
184
  before(:each) { copy('%/vpc.json', 'vpc.json') }
137
- before(:each) { append_to_file('vpc.yaml', ' ' * (51200 + 1 - file_size('vpc.yaml').to_i)) }
185
+ before(:each) { append_to_file('vpc.yaml', '1') }
138
186
  before(:each) { run_command('cfndk stack create') }
139
187
  it 'displays created stack log' do
140
188
  aggregate_failures do
@@ -147,6 +195,183 @@ RSpec.describe 'CFnDK', type: :aruba do
147
195
  end
148
196
  after(:each) { run_command('cfndk destroy -f') }
149
197
  end
198
+ context 'with stack and nested stack', nested: true do
199
+ yaml = <<-"YAML"
200
+ global:
201
+ stacks:
202
+ Test:
203
+ template_file: vpc.yaml
204
+ parameter_input: vpc.json
205
+ timeout_in_minutes: 2
206
+ package: true
207
+ YAML
208
+ before(:each) { write_file(file, yaml) }
209
+ before(:each) { copy('%/stack.yaml', 'vpc.yaml') }
210
+ before(:each) { copy('%/stack.json', 'vpc.json') }
211
+ before(:each) { copy('%/nested_stack.yaml', 'nested_stack.yaml') }
212
+ before(:each) { run_command('cfndk stack create') }
213
+ it 'displays created stack log' do
214
+ aggregate_failures do
215
+ expect(last_command_started).to be_successfully_executed
216
+ expect(last_command_started).to have_output(/INFO validate stack: Test$/)
217
+ expect(last_command_started).to have_output(/INFO creating stack: Test$/)
218
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
219
+ expect(last_command_started).to have_output(%r{INFO Put S3 object: https://s3.amazonaws.com/[0-9]+-ap-northeast-1-cfndk-templates/.+/nested_stack.yaml})
220
+ end
221
+ end
222
+ after(:each) { run_command('cfndk destroy -f') }
223
+ end
224
+ context 'with stack with directory and nested stack', directory_nested: true do
225
+ yaml = <<-"YAML"
226
+ global:
227
+ stacks:
228
+ Test:
229
+ template_file: vpc/vpc.yaml
230
+ parameter_input: vpc/vpc.json
231
+ timeout_in_minutes: 2
232
+ package: true
233
+ YAML
234
+ before(:each) { write_file(file, yaml) }
235
+ before(:each) { copy('%/stack.yaml', 'vpc/vpc.yaml') }
236
+ before(:each) { copy('%/stack.json', 'vpc/vpc.json') }
237
+ before(:each) { copy('%/nested_stack.yaml', 'vpc/nested_stack.yaml') }
238
+ before(:each) { run_command('cfndk stack create') }
239
+ it 'displays created stack log' do
240
+ aggregate_failures do
241
+ expect(last_command_started).to be_successfully_executed
242
+ expect(last_command_started).to have_output(/INFO validate stack: Test$/)
243
+ expect(last_command_started).to have_output(/INFO creating stack: Test$/)
244
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
245
+ expect(last_command_started).to have_output(%r{INFO Put S3 object: https://s3.amazonaws.com/[0-9]+-ap-northeast-1-cfndk-templates/.+/nested_stack.yaml})
246
+ end
247
+ end
248
+ after(:each) { run_command('cfndk destroy -f') }
249
+ end
250
+ context 'with a 51201byte template stack and nested stack', nested: true, big: true, nested_big: true do
251
+ yaml = <<-"YAML"
252
+ global:
253
+ stacks:
254
+ Test:
255
+ template_file: vpc.yaml
256
+ parameter_input: vpc.json
257
+ timeout_in_minutes: 2
258
+ package: true
259
+ YAML
260
+ before(:each) { write_file(file, yaml) }
261
+ before(:each) { copy('%/stack.yaml', 'vpc.yaml') }
262
+ before(:each) { copy('%/stack.json', 'vpc.json') }
263
+ before(:each) { copy('%/nested_stack.yaml', 'nested_stack.yaml') }
264
+ before(:each) {
265
+ append_to_file('vpc.yaml', "\nOutputs:\n")
266
+ for number in 1..40 do
267
+ stack_append = <<-"YAML"
268
+ VpcId012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345#{number.to_s}:
269
+ Description: 01234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678901234567890123
270
+ Value: !Ref Vpc
271
+ Export:
272
+ Name: !Sub ${VpcName}-VpcId012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345#{number.to_s}
273
+ YAML
274
+ append_to_file('vpc.yaml', stack_append)
275
+ # p read('vpc.yaml').join("\n").length
276
+ end
277
+ }
278
+ before(:each) { append_to_file('nested_stack.yaml', "\n" + '#' * (51200 + 1 - 2 - file_size('nested_stack.yaml').to_i)) }
279
+ before(:each) { run_command('cfndk stack create') }
280
+ it 'displays created stack log' do
281
+ aggregate_failures do
282
+ expect(last_command_started).to be_successfully_executed
283
+ expect(last_command_started).to have_output(/INFO validate stack: Test$/)
284
+ expect(last_command_started).to have_output(/INFO creating stack: Test$/)
285
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
286
+ expect(last_command_started).to have_output(%r{INFO Put S3 object: https://s3.amazonaws.com/[0-9]+-ap-northeast-1-cfndk-templates/.+/vpc.yaml})
287
+ expect(last_command_started).to have_output(%r{INFO Put S3 object: https://s3.amazonaws.com/[0-9]+-ap-northeast-1-cfndk-templates/.+/nested_stack.yaml})
288
+ end
289
+ end
290
+ after(:each) { run_command('cfndk destroy -f') }
291
+ end
292
+ context 'with json stack and json nested stack', nested: true, json: true do
293
+ yaml = <<-"YAML"
294
+ global:
295
+ stacks:
296
+ Test:
297
+ template_file: vpc.template.json
298
+ parameter_input: vpc.json
299
+ timeout_in_minutes: 2
300
+ package: true
301
+ YAML
302
+ before(:each) { write_file(file, yaml) }
303
+ before(:each) { copy('%/stack.template.json', 'vpc.template.json') }
304
+ before(:each) { copy('%/stack.json', 'vpc.json') }
305
+ before(:each) { copy('%/nested_stack.json', 'nested_stack.json') }
306
+ before(:each) { run_command('cfndk stack create') }
307
+ it 'displays created stack log' do
308
+ aggregate_failures do
309
+ expect(last_command_started).to be_successfully_executed
310
+ expect(last_command_started).to have_output(/INFO validate stack: Test$/)
311
+ expect(last_command_started).to have_output(/INFO creating stack: Test$/)
312
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
313
+ expect(last_command_started).to have_output(%r{INFO Put S3 object: https://s3.amazonaws.com/[0-9]+-ap-northeast-1-cfndk-templates/.+/nested_stack.json})
314
+ end
315
+ end
316
+ after(:each) { run_command('cfndk destroy -f') }
317
+ end
318
+ context 'with lambda function and zip file', lambda_function: true do
319
+ yaml = <<-"YAML"
320
+ global:
321
+ stacks:
322
+ Test:
323
+ template_file: lambda_function.yaml
324
+ parameter_input: lambda_function.json
325
+ timeout_in_minutes: 2
326
+ capabilities:
327
+ - CAPABILITY_IAM
328
+ package: true
329
+ YAML
330
+ before(:each) { write_file(file, yaml) }
331
+ before(:each) { copy('%/lambda_function/lambda_function.yaml', 'lambda_function.yaml') }
332
+ before(:each) { copy('%/lambda_function/lambda_function.json', 'lambda_function.json') }
333
+ before(:each) { copy('%/lambda_function/index.js', 'lambda_function/index.js') }
334
+ before(:each) { run_command('cfndk stack create') }
335
+ it 'displays created stack log' do
336
+ aggregate_failures do
337
+ expect(last_command_started).to be_successfully_executed
338
+ expect(last_command_started).to have_output(/INFO validate stack: Test$/)
339
+ expect(last_command_started).to have_output(/INFO creating stack: Test$/)
340
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
341
+ expect(last_command_started).to have_output(%r{INFO Put S3 object: https://s3.amazonaws.com/[0-9]+-ap-northeast-1-cfndk-templates/.+/lambda_function.zip})
342
+ end
343
+ end
344
+ after(:each) { run_command('cfndk destroy -f') }
345
+ end
346
+ context 'with serverless function and zip file', serverless_function: true do
347
+ yaml = <<-"YAML"
348
+ global:
349
+ stacks:
350
+ Test:
351
+ template_file: serverless_function.yaml
352
+ parameter_input: serverless_function.json
353
+ timeout_in_minutes: 2
354
+ capabilities:
355
+ - CAPABILITY_AUTO_EXPAND
356
+ - CAPABILITY_IAM
357
+ package: true
358
+ YAML
359
+ before(:each) { write_file(file, yaml) }
360
+ before(:each) { copy('%/serverless_function/serverless_function.yaml', 'serverless_function.yaml') }
361
+ before(:each) { copy('%/serverless_function/serverless_function.json', 'serverless_function.json') }
362
+ before(:each) { copy('%/serverless_function/index.js', 'serverless_function/index.js') }
363
+ before(:each) { run_command('cfndk stack create') }
364
+ it 'displays created stack log' do
365
+ aggregate_failures do
366
+ expect(last_command_started).to be_successfully_executed
367
+ expect(last_command_started).to have_output(/INFO validate stack: Test$/)
368
+ expect(last_command_started).to have_output(/INFO creating stack: Test$/)
369
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
370
+ expect(last_command_started).to have_output(%r{INFO Put S3 object: https://s3.amazonaws.com/[0-9]+-ap-northeast-1-cfndk-templates/.+/serverless_function.zip})
371
+ end
372
+ end
373
+ after(:each) { run_command('cfndk destroy -f') }
374
+ end
150
375
  context 'with two stacks' do
151
376
  yaml = <<-"YAML"
152
377
  stacks:
@@ -180,6 +405,141 @@ RSpec.describe 'CFnDK', type: :aruba do
180
405
  end
181
406
  after(:each) { run_command('cfndk destroy -f') }
182
407
  end
408
+ context 'with stack and command', global_pre_command: true, global_post_command: true, pre_command: true, post_command: true do
409
+ yaml = <<-"YAML"
410
+ global:
411
+ pre_command: echo "global pre command"
412
+ post_command: echo "global post command"
413
+ stacks:
414
+ Test:
415
+ template_file: vpc.yaml
416
+ parameter_input: vpc.json
417
+ timeout_in_minutes: 2
418
+ pre_command: echo "Test pre command"
419
+ post_command: echo "Test post command"
420
+ YAML
421
+
422
+ before(:each) { write_file(file, yaml) }
423
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
424
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
425
+ before(:each) { run_command('cfndk stack create') }
426
+ it do
427
+ aggregate_failures do
428
+ expect(last_command_started).to be_successfully_executed
429
+ expect(last_command_started).to have_output(/INFO execute global pre command: echo "global pre command"$/)
430
+ expect(last_command_started).to have_output(/INFO global pre command$/)
431
+ expect(last_command_started).to have_output(/INFO execute pre command: echo "Test pre command"$/)
432
+ expect(last_command_started).to have_output(/INFO Test pre command$/)
433
+ expect(last_command_started).to have_output(/INFO validate stack: Test$/)
434
+ expect(last_command_started).to have_output(/INFO creating stack: Test$/)
435
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
436
+ expect(last_command_started).to have_output(/INFO execute post command: echo "Test post command"$/)
437
+ expect(last_command_started).to have_output(/INFO Test post command$/)
438
+ expect(last_command_started).to have_output(/INFO execute global post command: echo "global post command"$/)
439
+ expect(last_command_started).to have_output(/INFO global post command$/)
440
+ end
441
+ end
442
+ after(:each) { run_command('cfndk destroy -f') }
443
+ end
444
+
445
+ context 'with stack and error global pre command', global_pre_command: true do
446
+ yaml = <<-"YAML"
447
+ global:
448
+ pre_command: exit 1
449
+ stacks:
450
+ Test:
451
+ template_file: vpc.yaml
452
+ parameter_input: vpc.json
453
+ timeout_in_minutes: 2
454
+ YAML
455
+
456
+ before(:each) { write_file(file, yaml) }
457
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
458
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
459
+ before(:each) { run_command('cfndk stack create') }
460
+ it do
461
+ aggregate_failures do
462
+ expect(last_command_started).not_to be_successfully_executed
463
+ expect(last_command_started).to have_output(/INFO execute global pre command: exit 1$/)
464
+ expect(last_command_started).to have_output(/ERROR RuntimeError: global pre command is error. status: 1 command: exit 1$/)
465
+ end
466
+ end
467
+ after(:each) { run_command('cfndk destroy -f') }
468
+ end
469
+
470
+ context 'with stack and error global post command', global_post_command: true do
471
+ yaml = <<-"YAML"
472
+ global:
473
+ post_command: exit 1
474
+ stacks:
475
+ Test:
476
+ template_file: vpc.yaml
477
+ parameter_input: vpc.json
478
+ timeout_in_minutes: 2
479
+ YAML
480
+
481
+ before(:each) { write_file(file, yaml) }
482
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
483
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
484
+ before(:each) { run_command('cfndk stack create') }
485
+ it do
486
+ aggregate_failures do
487
+ expect(last_command_started).not_to be_successfully_executed
488
+ expect(last_command_started).to have_output(/INFO execute global post command: exit 1$/)
489
+ expect(last_command_started).to have_output(/ERROR RuntimeError: global post command is error. status: 1 command: exit 1$/)
490
+ end
491
+ end
492
+ after(:each) { run_command('cfndk destroy -f') }
493
+ end
494
+
495
+ context 'with stack and error pre command', pre_command: true do
496
+ yaml = <<-"YAML"
497
+ stacks:
498
+ Test:
499
+ template_file: vpc.yaml
500
+ parameter_input: vpc.json
501
+ timeout_in_minutes: 2
502
+ pre_command: exit 1
503
+ YAML
504
+
505
+ before(:each) { write_file(file, yaml) }
506
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
507
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
508
+ before(:each) { run_command('cfndk stack create') }
509
+ it do
510
+ aggregate_failures do
511
+ expect(last_command_started).not_to be_successfully_executed
512
+ expect(last_command_started).to have_output(/INFO execute pre command: exit 1$/)
513
+ expect(last_command_started).to have_output(/ERROR RuntimeError: pre command is error. status: 1 command: exit 1$/)
514
+ end
515
+ end
516
+ after(:each) { run_command('cfndk destroy -f') }
517
+ end
518
+
519
+ context 'with stack and error post command', post_command: true do
520
+ yaml = <<-"YAML"
521
+ stacks:
522
+ Test:
523
+ template_file: vpc.yaml
524
+ parameter_input: vpc.json
525
+ timeout_in_minutes: 2
526
+ post_command: exit 1
527
+ YAML
528
+
529
+ before(:each) { write_file(file, yaml) }
530
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
531
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
532
+ before(:each) { run_command('cfndk stack create') }
533
+ it do
534
+ aggregate_failures do
535
+ expect(last_command_started).not_to be_successfully_executed
536
+ expect(last_command_started).to have_output(/INFO execute post command: exit 1$/)
537
+ expect(last_command_started).to have_output(/ERROR RuntimeError: post command is error. status: 1 command: exit 1$/)
538
+ end
539
+ end
540
+ after(:each) { run_command('cfndk destroy -f') }
541
+ end
542
+
183
543
  context 'when invalid dependency', dependency: true do
184
544
  yaml = <<-"YAML"
185
545
  stacks: