cfndk 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +23 -14
  3. data/.gitignore +0 -1
  4. data/.rspec_parallel +6 -0
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +811 -0
  7. data/README.md +122 -10
  8. data/cfndk.gemspec +1 -0
  9. data/lib/cfndk/change_set_command.rb +97 -0
  10. data/lib/cfndk/command.rb +15 -181
  11. data/lib/cfndk/config_file_loadable.rb +13 -0
  12. data/lib/cfndk/global_config.rb +15 -0
  13. data/lib/cfndk/key_pair.rb +7 -4
  14. data/lib/cfndk/key_pair_command.rb +53 -0
  15. data/lib/cfndk/key_pairs.rb +2 -1
  16. data/lib/cfndk/logger.rb +1 -1
  17. data/lib/cfndk/stack.rb +382 -103
  18. data/lib/cfndk/stack_command.rb +110 -0
  19. data/lib/cfndk/stacks.rb +40 -14
  20. data/lib/cfndk/subcommand_help_returnable.rb +16 -0
  21. data/lib/cfndk/version.rb +1 -1
  22. data/lib/cfndk.rb +6 -0
  23. data/skel/cfndk.yml +4 -0
  24. data/spec/cfndk_change_set_create_spec.rb +436 -0
  25. data/spec/cfndk_change_set_destroy_spec.rb +160 -0
  26. data/spec/cfndk_change_set_execute_spec.rb +179 -0
  27. data/spec/cfndk_change_set_report_spec.rb +107 -0
  28. data/spec/cfndk_change_set_spec.rb +37 -0
  29. data/spec/cfndk_create_spec.rb +56 -141
  30. data/spec/cfndk_destroy_spec.rb +4 -2
  31. data/spec/cfndk_keypiar_spec.rb +11 -9
  32. data/spec/cfndk_report_spec.rb +3 -1
  33. data/spec/cfndk_spec.rb +5 -3
  34. data/spec/cfndk_stack_create_spec.rb +454 -0
  35. data/spec/cfndk_stack_destroy_spec.rb +161 -0
  36. data/spec/cfndk_stack_report_spec.rb +181 -0
  37. data/spec/cfndk_stack_spec.rb +6 -1146
  38. data/spec/cfndk_stack_update_spec.rb +467 -0
  39. data/spec/spec_helper.rb +4 -1
  40. data/spec/support/aruba.rb +1 -0
  41. metadata +42 -2
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'CFnDK', type: :aruba do
4
+ before(:each) { set_environment_variable('AWS_REGION', ENV['AWS_REGION']) }
5
+ before(:each) { set_environment_variable('AWS_PROFILE', ENV['AWS_PROFILE']) }
6
+ before(:each) { set_environment_variable('AWS_ACCESS_KEY_ID', ENV["AWS_ACCESS_KEY_ID#{ENV['TEST_ENV_NUMBER']}"]) }
7
+ before(:each) { set_environment_variable('AWS_SECRET_ACCESS_KEY', ENV["AWS_SECRET_ACCESS_KEY#{ENV['TEST_ENV_NUMBER']}"]) }
8
+ describe 'bin/cfndk' do
9
+ before(:each) { setup_aruba }
10
+ let(:file) { 'cfndk.yml' }
11
+ let(:file2) { 'cfndk2.yml' }
12
+ let(:uuid) { '38437346-c75c-47c5-83b4-d504f85e275b' }
13
+ let(:change_set_uuid) { '38437346-c75c-47c5-83b4-d504f85e27ca' }
14
+
15
+ describe 'changeset' do
16
+ describe 'destroy', destroy: true do
17
+ context 'when -f without cfndk.yml' do
18
+ before(:each) { run_command('cfndk changeset destroy -f') }
19
+ it 'displays file does not exist error and status code = 1' do
20
+ aggregate_failures do
21
+ expect(last_command_started).to have_exit_status(1)
22
+ expect(last_command_started).to have_output(/ERROR RuntimeError: File does not exist./)
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'with cfndk2.yml' do
28
+ yaml = <<-"YAML"
29
+ stacks:
30
+ YAML
31
+ before(:each) { write_file(file2, yaml) }
32
+ context 'when -c cfndk2.yml -f and empty stacks' do
33
+ before(:each) { run_command("cfndk changeset destroy -c=#{file2} -f") }
34
+ it 'displays empty stack log' do
35
+ aggregate_failures do
36
+ expect(last_command_started).to be_successfully_executed
37
+ expect(last_command_started).to have_output(/INFO destroy.../)
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'when --config-path cfndk2.yml -f and empty stacks' do
43
+ before(:each) { run_command("cfndk changeset destroy --config-path=#{file2} -f") }
44
+ it 'displays empty stack log' do
45
+ aggregate_failures do
46
+ expect(last_command_started).to be_successfully_executed
47
+ expect(last_command_started).to have_output(/INFO destroy.../)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'with cfndk.yml' do
54
+ context 'when cfndk.yml is empty' do
55
+ before(:each) { touch(file) }
56
+ before(:each) { run_command('cfndk changeset destroy -f') }
57
+ it 'displays File is empty error and status code = 1' do
58
+ aggregate_failures do
59
+ expect(last_command_started).to have_exit_status(1)
60
+ expect(last_command_started).to have_output(/ERROR File is empty./)
61
+ end
62
+ end
63
+ end
64
+ context 'when enter no' do
65
+ yaml = <<-"YAML"
66
+ keypairs:
67
+ Test1:
68
+ stacks:
69
+ Test:
70
+ template_file: vpc.yaml
71
+ parameter_input: vpc.json
72
+ parameters:
73
+ VpcName: sample<%= append_uuid%>
74
+ timeout_in_minutes: 2
75
+ YAML
76
+ before(:each) { write_file(file, yaml) }
77
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
78
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
79
+ before(:each) { run_command('cfndk changeset destroy') }
80
+ before(:each) { type('no') }
81
+ it 'displays confirm message and cancel message and status code = 2' do
82
+ aggregate_failures do
83
+ expect(last_command_started).to have_exit_status(2)
84
+ expect(last_command_started).to have_output(/INFO destroy../)
85
+ expect(last_command_started).to have_output(%r{Are you sure you want to destroy\? \(y/n\)})
86
+ expect(last_command_started).to have_output(/INFO destroy command was canceled/)
87
+ expect(last_command_started).not_to have_output(/INFO deleting change set:/)
88
+ expect(last_command_started).not_to have_output(/INFO deleted change set:/)
89
+ expect(last_command_started).not_to have_output(/INFO do not delete keypair: Test1$/)
90
+ expect(last_command_started).not_to have_output(/INFO do not delete stack: Test$/)
91
+ end
92
+ end
93
+ end
94
+ context 'when enter yes' do
95
+ context 'when keyparis and stacks do not exist' do
96
+ yaml = <<-"YAML"
97
+ keypairs:
98
+ Test1:
99
+ stacks:
100
+ Test:
101
+ template_file: vpc.yaml
102
+ parameter_input: vpc.json
103
+ parameters:
104
+ VpcName: sample<%= append_uuid%>
105
+ timeout_in_minutes: 2
106
+ YAML
107
+ before(:each) { write_file(file, yaml) }
108
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
109
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
110
+ before(:each) { run_command('cfndk destroy -f') }
111
+ before(:each) { stop_all_commands }
112
+ before(:each) { run_command('cfndk changeset destroy') }
113
+ before(:each) { type('yes') }
114
+ before(:each) { stop_all_commands }
115
+ it 'displays confirm message and do not delete message' do
116
+ aggregate_failures do
117
+ expect(last_command_started).to have_exit_status(1)
118
+ expect(last_command_started).to have_output(/INFO destroy../)
119
+ expect(last_command_started).to have_output(%r{Are you sure you want to destroy\? \(y/n\)})
120
+ expect(last_command_started).to have_output(/INFO deleting change set: Test$/)
121
+ expect(last_command_started).to have_output(/ERROR Aws::CloudFormation::Errors::ValidationError: Stack \[Test\] does not exist$/)
122
+ end
123
+ end
124
+ end
125
+ context 'when keyparis and stacks exist' do
126
+ yaml = <<-"YAML"
127
+ keypairs:
128
+ Test1:
129
+ stacks:
130
+ Test:
131
+ template_file: vpc.yaml
132
+ parameter_input: vpc.json
133
+ parameters:
134
+ VpcName: sample<%= append_uuid%>
135
+ timeout_in_minutes: 2
136
+ YAML
137
+ before(:each) { write_file(file, yaml) }
138
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
139
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
140
+ before(:each) { run_command_and_stop('cfndk changeset create') }
141
+ before(:each) { run_command('cfndk changeset destroy') }
142
+ before(:each) { type('yes') }
143
+ before(:each) { stop_all_commands }
144
+ it 'displays confirm message and delete message' do
145
+ aggregate_failures do
146
+ expect(last_command_started).to be_successfully_executed
147
+ expect(last_command_started).to have_output(/INFO destroy../)
148
+ expect(last_command_started).to have_output(%r{Are you sure you want to destroy\? \(y/n\)})
149
+ expect(last_command_started).not_to have_output(/INFO deleted keypair: Test1$/)
150
+ expect(last_command_started).to have_output(/INFO deleted change set: Test$/)
151
+ end
152
+ end
153
+ after(:each) { run_command('cfndk destroy -f') }
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,179 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'CFnDK', type: :aruba do
4
+ before(:each) { set_environment_variable('AWS_REGION', ENV['AWS_REGION']) }
5
+ before(:each) { set_environment_variable('AWS_PROFILE', ENV['AWS_PROFILE']) }
6
+ before(:each) { set_environment_variable('AWS_ACCESS_KEY_ID', ENV["AWS_ACCESS_KEY_ID#{ENV['TEST_ENV_NUMBER']}"]) }
7
+ before(:each) { set_environment_variable('AWS_SECRET_ACCESS_KEY', ENV["AWS_SECRET_ACCESS_KEY#{ENV['TEST_ENV_NUMBER']}"]) }
8
+ describe 'bin/cfndk' do
9
+ before(:each) { setup_aruba }
10
+ let(:file) { 'cfndk.yml' }
11
+ let(:file2) { 'cfndk2.yml' }
12
+ let(:uuid) { '38437346-c75c-47c5-83b4-d504f85e275b' }
13
+ let(:change_set_uuid) { '38437346-c75c-47c5-83b4-d504f85e27ca' }
14
+
15
+ describe 'changeset' do
16
+ describe 'execute', execute: true do
17
+ context 'without cfndk.yml' do
18
+ before(:each) { run_command('cfndk changeset execute') }
19
+ it 'displays file does not exist error and status code = 1' do
20
+ aggregate_failures do
21
+ expect(last_command_started).to have_exit_status(1)
22
+ expect(last_command_started).to have_output(/ERROR RuntimeError: File does not exist./)
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'with cfndk2.yml' do
28
+ yaml = <<-"YAML"
29
+ keypairs:
30
+ YAML
31
+ before(:each) { write_file(file2, yaml) }
32
+ context 'when -c cfndk2.yml and empty stacks' do
33
+ before(:each) { run_command("cfndk changeset execute -c=#{file2}") }
34
+ it 'displays empty stack log' do
35
+ aggregate_failures do
36
+ expect(last_command_started).to be_successfully_executed
37
+ expect(last_command_started).to have_output(/INFO execute.../)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'with cfndk.yml' do
44
+ context 'when cfndk.yml is empty' do
45
+ before(:each) { touch(file) }
46
+ before(:each) { run_command('cfndk changeset execute') }
47
+ it 'displays File is empty error and status code = 1' do
48
+ aggregate_failures do
49
+ expect(last_command_started).to have_exit_status(1)
50
+ expect(last_command_started).to have_output(/ERROR File is empty./)
51
+ end
52
+ end
53
+ end
54
+
55
+ context 'with stacks:' do
56
+ context 'without stack' do
57
+ before(:each) { write_file(file, 'stacks:') }
58
+ before(:each) { run_command('cfndk changeset execute') }
59
+ it 'displays create log' do
60
+ aggregate_failures do
61
+ expect(last_command_started).to be_successfully_executed
62
+ expect(last_command_started).to have_output(/INFO execute.../)
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'with a stack' do
68
+ yaml = <<-"YAML"
69
+ stacks:
70
+ Test:
71
+ template_file: vpc.yaml
72
+ parameter_input: vpc.json
73
+ timeout_in_minutes: 3
74
+ YAML
75
+ before(:each) { write_file(file, yaml) }
76
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
77
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
78
+ before(:each) { run_command_and_stop('cfndk changeset create') }
79
+ before(:each) { run_command('cfndk changeset execute') }
80
+ it 'displays executed log' do
81
+ aggregate_failures do
82
+ expect(last_command_started).to be_successfully_executed
83
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
84
+ end
85
+ end
86
+ after(:each) { run_command('cfndk destroy -f') }
87
+ end
88
+ context 'with two stacks' do
89
+ yaml = <<-"YAML"
90
+ stacks:
91
+ Test:
92
+ template_file: vpc.yaml
93
+ parameter_input: vpc.json
94
+ timeout_in_minutes: 2
95
+ Test2:
96
+ template_file: sg.yaml
97
+ parameter_input: sg.json
98
+ depends:
99
+ - Test
100
+ YAML
101
+
102
+ before(:each) { write_file(file, yaml) }
103
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
104
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
105
+ before(:each) { copy('%/sg.yaml', 'sg.yaml') }
106
+ before(:each) { copy('%/sg.json', 'sg.json') }
107
+ before(:each) { run_command_and_stop('cfndk changeset create') }
108
+ before(:each) { run_command('cfndk changeset execute') }
109
+ it 'displays executed logs' do
110
+ aggregate_failures do
111
+ expect(last_command_started).to be_successfully_executed
112
+ expect(last_command_started).to have_output(/INFO execute change set: Test$/)
113
+ expect(last_command_started).to have_output(/INFO execute change set: Test2$/)
114
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
115
+ expect(last_command_started).to have_output(/INFO created stack: Test2$/)
116
+ end
117
+ end
118
+ after(:each) { run_command('cfndk destroy -f') }
119
+ end
120
+ context 'when invalid dependency', dependency: true do
121
+ yaml = <<-"YAML"
122
+ stacks:
123
+ Test:
124
+ template_file: vpc.yaml
125
+ parameter_input: vpc.json
126
+ timeout_in_minutes: 2
127
+ depends:
128
+ - Test2
129
+ Test2:
130
+ template_file: sg.yaml
131
+ parameter_input: sg.json
132
+ YAML
133
+
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) { copy('%/sg.yaml', 'sg.yaml') }
138
+ before(:each) { copy('%/sg.json', 'sg.json') }
139
+ before(:each) { run_command_and_stop('cfndk changeset create') }
140
+ before(:each) { run_command('cfndk changeset execute') }
141
+ it 'displays executed logs' do
142
+ aggregate_failures do
143
+ expect(last_command_started).to have_exit_status(1)
144
+ expect(last_command_started).to have_output(/INFO execute change set: Test2$/)
145
+ expect(last_command_started).to have_output(/ERROR Aws::Waiters::Errors::FailureStateError: stopped waiting, encountered a failure state$/)
146
+ end
147
+ end
148
+ after(:each) { run_command('cfndk destroy -f') }
149
+ end
150
+ context 'when success with capabilities', capabilities: true do
151
+ yaml = <<-"YAML"
152
+ stacks:
153
+ Test:
154
+ template_file: iam.yaml
155
+ parameter_input: iam.json
156
+ capabilities:
157
+ - CAPABILITY_NAMED_IAM
158
+ timeout_in_minutes: 3
159
+ YAML
160
+
161
+ before(:each) { write_file(file, yaml) }
162
+ before(:each) { copy('%/iam.yaml', 'iam.yaml') }
163
+ before(:each) { copy('%/iam.json', 'iam.json') }
164
+ before(:each) { run_command_and_stop('cfndk changeset create') }
165
+ before(:each) { run_command('cfndk changeset execute') }
166
+ it do
167
+ aggregate_failures do
168
+ expect(last_command_started).to be_successfully_executed
169
+ expect(last_command_started).to have_output(/INFO created stack: Test$/)
170
+ end
171
+ end
172
+ after(:each) { run_command('cfndk destroy -f') }
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'CFnDK', type: :aruba do
4
+ before(:each) { set_environment_variable('AWS_REGION', ENV['AWS_REGION']) }
5
+ before(:each) { set_environment_variable('AWS_PROFILE', ENV['AWS_PROFILE']) }
6
+ before(:each) { set_environment_variable('AWS_ACCESS_KEY_ID', ENV["AWS_ACCESS_KEY_ID#{ENV['TEST_ENV_NUMBER']}"]) }
7
+ before(:each) { set_environment_variable('AWS_SECRET_ACCESS_KEY', ENV["AWS_SECRET_ACCESS_KEY#{ENV['TEST_ENV_NUMBER']}"]) }
8
+ describe 'bin/cfndk' do
9
+ before(:each) { setup_aruba }
10
+ let(:file) { 'cfndk.yml' }
11
+ let(:file2) { 'cfndk2.yml' }
12
+ let(:uuid) { '38437346-c75c-47c5-83b4-d504f85e275b' }
13
+ let(:change_set_uuid) { '38437346-c75c-47c5-83b4-d504f85e27ca' }
14
+
15
+ describe 'changeset' do
16
+ describe 'report', report: true do
17
+ context 'without cfndk.yml' do
18
+ before(:each) { run_command('cfndk changeset report') }
19
+ it 'displays file does not exist error and status code = 1' do
20
+ aggregate_failures do
21
+ expect(last_command_started).to have_exit_status(1)
22
+ expect(last_command_started).to have_output(/ERROR RuntimeError: File does not exist./)
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'with cfndk.yml' do
28
+ context 'when cfndk.yml is empty' do
29
+ before(:each) { touch(file) }
30
+ before(:each) { run_command('cfndk changeset report') }
31
+ it 'displays File is empty error and status code = 1' do
32
+ aggregate_failures do
33
+ expect(last_command_started).to have_exit_status(1)
34
+ expect(last_command_started).to have_output(/ERROR File is empty./)
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'with stacks:' do
40
+ context 'without stack' do
41
+ before(:each) { write_file(file, 'stacks:') }
42
+ before(:each) { run_command('cfndk changeset report') }
43
+ it 'displays report log' do
44
+ aggregate_failures do
45
+ expect(last_command_started).to be_successfully_executed
46
+ expect(last_command_started).to have_output(/INFO report.../)
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'with a stack' do
52
+ yaml = <<-"YAML"
53
+ stacks:
54
+ Test:
55
+ template_file: vpc.yaml
56
+ parameter_input: vpc.json
57
+ timeout_in_minutes: 3
58
+ YAML
59
+ before(:each) { write_file(file, yaml) }
60
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
61
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
62
+ before(:each) { run_command_and_stop('cfndk changeset create') }
63
+ before(:each) { run_command('cfndk changeset report') }
64
+ it 'displays report log' do
65
+ aggregate_failures do
66
+ expect(last_command_started).to be_successfully_executed
67
+ expect(last_command_started).to have_output(/INFO change set: Test$/)
68
+ end
69
+ end
70
+ after(:each) { run_command('cfndk destroy -f') }
71
+ end
72
+ context 'with two stacks' do
73
+ yaml = <<-"YAML"
74
+ stacks:
75
+ Test:
76
+ template_file: vpc.yaml
77
+ parameter_input: vpc.json
78
+ timeout_in_minutes: 2
79
+ Test2:
80
+ template_file: sg.yaml
81
+ parameter_input: sg.json
82
+ depends:
83
+ - Test
84
+ YAML
85
+
86
+ before(:each) { write_file(file, yaml) }
87
+ before(:each) { copy('%/vpc.yaml', 'vpc.yaml') }
88
+ before(:each) { copy('%/vpc.json', 'vpc.json') }
89
+ before(:each) { copy('%/sg.yaml', 'sg.yaml') }
90
+ before(:each) { copy('%/sg.json', 'sg.json') }
91
+ before(:each) { run_command_and_stop('cfndk changeset create') }
92
+ before(:each) { run_command('cfndk changeset report') }
93
+ it 'displays report logs' do
94
+ aggregate_failures do
95
+ expect(last_command_started).to be_successfully_executed
96
+ expect(last_command_started).to have_output(/INFO change set: Test$/)
97
+ expect(last_command_started).to have_output(/INFO change set: Test2$/)
98
+ end
99
+ end
100
+ after(:each) { run_command('cfndk destroy -f') }
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'CFnDK', type: :aruba do
4
+ before(:each) { set_environment_variable('AWS_REGION', ENV['AWS_REGION']) }
5
+ before(:each) { set_environment_variable('AWS_PROFILE', ENV['AWS_PROFILE']) }
6
+ before(:each) { set_environment_variable('AWS_ACCESS_KEY_ID', ENV["AWS_ACCESS_KEY_ID#{ENV['TEST_ENV_NUMBER']}"]) }
7
+ before(:each) { set_environment_variable('AWS_SECRET_ACCESS_KEY', ENV["AWS_SECRET_ACCESS_KEY#{ENV['TEST_ENV_NUMBER']}"]) }
8
+ describe 'bin/cfndk' do
9
+ before(:each) { setup_aruba }
10
+ let(:file) { 'cfndk.yml' }
11
+ let(:file2) { 'cfndk2.yml' }
12
+ let(:uuid) { '38437346-c75c-47c5-83b4-d504f85e275b' }
13
+ let(:change_set_uuid) { '38437346-c75c-47c5-83b4-d504f85e27ca' }
14
+
15
+ describe 'changeset' do
16
+ context 'without subcommand', help: true do
17
+ before(:each) { run_command('cfndk changeset') }
18
+ it 'displays help and status code = 2' do
19
+ aggregate_failures do
20
+ expect(last_command_started).to have_exit_status(2)
21
+ end
22
+ end
23
+ end
24
+
25
+ describe 'help', help: true do
26
+ context 'without subsubcommand' do
27
+ before(:each) { run_command('cfndk changeset help') }
28
+ it 'displays help and status code = 2' do
29
+ aggregate_failures do
30
+ expect(last_command_started).to have_exit_status(2)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end