kumo_keisei 2.1.1 → 2.2.1

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.1
1
+ 2.2.1
@@ -147,7 +147,7 @@ module KumoKeisei
147
147
  rescue Aws::CloudFormation::Errors::ValidationError => ex
148
148
  raise ex unless ex.message == "No updates are to be performed."
149
149
  ConsoleJockey.write_line "No changes need to be applied for #{@stack_name}."
150
- rescue Aws::Waiters::Errors::FailureStateError => ex
150
+ rescue Aws::Waiters::Errors::FailureStateError
151
151
  ConsoleJockey.write_line "Failed to apply the environment update. The stack has been rolled back. It is still safe to apply updates."
152
152
  ConsoleJockey.write_line "Find error details in the AWS CloudFormation console: #{stack_events_url}"
153
153
  raise UpdateError.new("Stack update failed for #{@stack_name}.")
@@ -5,118 +5,121 @@ require 'yaml'
5
5
  require_relative 'file_loader'
6
6
  require_relative 'parameter_builder'
7
7
 
8
- class KumoKeisei::EnvironmentConfig
9
- LOGGER = Logger.new(STDOUT)
10
-
11
- attr_reader :app_name, :env_name
12
-
13
- def initialize(options, logger = LOGGER)
14
- @app_name = options[:app_name]
15
- @env_name = options[:env_name]
16
- @config_dir_path = options[:config_dir_path]
17
- @params_template_file_path = options[:params_template_file_path]
18
- @injected_config = options[:injected_config] || {}
19
- @file_loader = KumoKeisei::FileLoader.new(options)
20
-
21
- @log = logger
22
- end
23
-
24
- def get_binding
25
- binding
26
- end
8
+ module KumoKeisei
9
+ # Environment Configuration for a cloud formation stack
10
+ class EnvironmentConfig
11
+ LOGGER = Logger.new(STDOUT)
12
+
13
+ attr_reader :app_name, :env_name
14
+
15
+ def initialize(options, logger = LOGGER)
16
+ @app_name = options[:app_name]
17
+ @env_name = options[:env_name]
18
+ @config_dir_path = options[:config_dir_path]
19
+ @params_template_file_path = options[:params_template_file_path]
20
+ @injected_config = options[:injected_config] || {}
21
+ @file_loader = KumoKeisei::FileLoader.new(options)
22
+
23
+ @log = logger
24
+ end
27
25
 
28
- def production?
29
- env_name == "production"
30
- end
26
+ def production?
27
+ env_name == 'production'
28
+ end
31
29
 
32
- def development?
33
- !(%w(production staging).include?(env_name))
34
- end
30
+ def development?
31
+ !%w(production staging).include? env_name
32
+ end
35
33
 
36
- def plain_text_secrets
37
- @plain_text_secrets ||= decrypt_secrets(encrypted_secrets)
38
- end
34
+ def plain_text_secrets
35
+ @plain_text_secrets ||= decrypt_secrets(encrypted_secrets)
36
+ end
39
37
 
40
- def config
41
- @config ||= common_config.merge(env_config).merge(@injected_config)
42
- end
38
+ def config
39
+ # a hash of all settings that apply to this environment
40
+ @config ||= common_config.merge(env_config).merge(@injected_config)
41
+ end
43
42
 
44
- def cf_params
45
- return [] unless params_template
46
- KumoKeisei::ParameterBuilder.new(get_stack_params(params_template)).params
47
- end
43
+ def cf_params
44
+ # returns a list of Cfn friendly paramater_value, paramater_key pairs for
45
+ # consumption by cloudformation.
46
+ return [] unless params
47
+ config
48
48
 
49
- private
49
+ stack_params = YAML.load(params.result(binding))
50
+ KumoKeisei::ParameterBuilder.new(stack_params).params
51
+ end
50
52
 
51
- def kms
52
- @kms ||= KumoKi::KMS.new
53
- end
53
+ private
54
54
 
55
- def get_stack_params(params_template)
56
- YAML.load(ERB.new(params_template).result(get_binding))
57
- end
55
+ def kms
56
+ @kms ||= KumoKi::KMS.new
57
+ end
58
58
 
59
- def params_template
60
- return nil unless @params_template_file_path
59
+ def params
60
+ return nil unless @params_template_file_path
61
+ @file_loader.load_erb(@params_template_file_path)
62
+ end
61
63
 
62
- @file_loader.load_config!(@params_template_file_path)
63
- end
64
+ def decrypt_secrets(secrets)
65
+ Hash[
66
+ secrets.map do |name, cipher_text|
67
+ @log.debug "Decrypting '#{name}'"
68
+ decrypt_cipher name, cipher_text
69
+ end
70
+ ]
71
+ end
64
72
 
65
- def decrypt_secrets(secrets)
66
- Hash[
67
- secrets.map do |name, cipher_text|
68
- @log.debug "Decrypting '#{name}'"
69
- if cipher_text.start_with? '[ENC,'
70
- begin
71
- [name, "#{kms.decrypt cipher_text[5,cipher_text.size]}"]
72
- rescue
73
- @log.error "Error decrypting secret '#{name}'"
74
- raise
75
- end
76
- else
77
- [name, cipher_text]
73
+ def decrypt_cipher(name, cipher_text)
74
+ if cipher_text.start_with? '[ENC,'
75
+ begin
76
+ [name, kms.decrypt(cipher_text[5, cipher_text.size]).to_s]
77
+ rescue
78
+ @log.error "Error decrypting secret '#{name}'"
79
+ raise
78
80
  end
81
+ else
82
+ [name, cipher_text]
79
83
  end
80
- ]
81
- end
84
+ end
82
85
 
83
- def env_config_file_name
84
- "#{env_name}.yml"
85
- end
86
+ def env_config_file_name
87
+ "#{env_name}.yml"
88
+ end
86
89
 
87
- def env_secrets_file_name
88
- "#{env_name}_secrets.yml"
89
- end
90
+ def env_secrets_file_name
91
+ "#{env_name}_secrets.yml"
92
+ end
90
93
 
91
- def encrypted_secrets
92
- encrypted_common_secrets.merge(encrypted_env_secrets)
93
- end
94
+ def encrypted_secrets
95
+ encrypted_common_secrets.merge(encrypted_env_secrets)
96
+ end
94
97
 
95
- def encrypted_common_secrets
96
- @file_loader.load_config('common_secrets.yml')
97
- end
98
+ def encrypted_common_secrets
99
+ @file_loader.load_hash('common_secrets.yml')
100
+ end
98
101
 
99
- def encrypted_env_secrets
100
- secrets = @file_loader.load_config(env_secrets_file_name)
102
+ def encrypted_env_secrets
103
+ secrets = @file_loader.load_hash(env_secrets_file_name)
101
104
 
102
- if !secrets.empty?
103
- secrets
104
- else
105
- @file_loader.load_config('development_secrets.yml')
105
+ if !secrets.empty?
106
+ secrets
107
+ else
108
+ @file_loader.load_hash('development_secrets.yml')
109
+ end
106
110
  end
107
- end
108
111
 
109
- def common_config
110
- @file_loader.load_config('common.yml')
111
- end
112
-
113
- def env_config
114
- config = @file_loader.load_config(env_config_file_name)
112
+ def common_config
113
+ @file_loader.load_hash('common.yml')
114
+ end
115
115
 
116
- if !config.empty?
117
- config
118
- else
119
- @file_loader.load_config('development.yml')
116
+ def env_config
117
+ config = @file_loader.load_hash(env_config_file_name)
118
+ if !config.empty?
119
+ config
120
+ else
121
+ @file_loader.load_hash('development.yml')
122
+ end
120
123
  end
121
124
  end
122
125
  end
@@ -4,15 +4,27 @@ module KumoKeisei
4
4
  @config_dir_path = options[:config_dir_path]
5
5
  end
6
6
 
7
- def load_config!(file_name, context = nil)
8
- erb_result = ERB.new(File.read(file_path(file_name))).result(context)
9
- YAML.load(erb_result)
7
+ def load_hash(file_name, optional = true)
8
+ # reads a file presuming it's a yml in form of key: value, returning it as a hash
9
+ path = file_path(file_name)
10
+
11
+ begin
12
+ YAML::load(File.read(path))
13
+ rescue Errno::ENOENT => ex
14
+ # file not found, return empty dictionary if that is ok
15
+ return {} if optional
16
+ raise ex
17
+ rescue StandardError => ex
18
+ # this is an error we weren't expecting
19
+ raise ex
20
+ end
10
21
  end
11
22
 
12
- def load_config(file_name)
23
+ def load_erb(file_name)
24
+ # loads a file, constructs an ERB object from it and returns the ERB object
25
+ # DOES NOT RENDER A RESULT!!
13
26
  path = file_path(file_name)
14
- return {} unless File.exist?(path)
15
- load_config!(file_name)
27
+ ERB.new(File.read(path))
16
28
  end
17
29
 
18
30
  private
@@ -21,11 +21,11 @@ module KumoKeisei
21
21
  end
22
22
 
23
23
  def parsed_file_params
24
- return [] unless (@file_path && File.exists?(@file_path))
24
+ return [] unless (@file_path && File.exist?(@file_path))
25
25
 
26
26
  file_contents = JSON.parse(File.read(@file_path))
27
27
 
28
- file_contents.map do |param|
28
+ file_contents.map do |param|
29
29
  {
30
30
  parameter_key: param["ParameterKey"],
31
31
  parameter_value: param["ParameterValue"]
@@ -11,170 +11,227 @@ describe KumoKeisei::EnvironmentConfig do
11
11
  }
12
12
  end
13
13
  let(:file_loader) { instance_double(KumoKeisei::FileLoader) }
14
- let(:parameter_template) { "stack_name: <%= config['stack_name'] %>" }
14
+ let(:parameters) { ERB.new("") }
15
15
  let(:params_template_file_path) { '/junk.txt' }
16
16
  let(:environment_config_file_name) { "#{env_name}.yml" }
17
17
  let(:kms) { instance_double(KumoKi::KMS) }
18
18
  let(:logger) { double(:test_logger, debug: nil) }
19
+ let(:environment_config) { described_class.new(options, logger) }
19
20
 
20
21
  before do
21
22
  allow(KumoKeisei::FileLoader).to receive(:new).and_return(file_loader)
22
23
  allow(KumoKi::KMS).to receive(:new).and_return(kms)
23
- allow(file_loader).to receive(:load_config!).with(params_template_file_path).and_return(parameter_template)
24
+ allow(file_loader).to receive(:load_erb).with(params_template_file_path).and_return(parameters)
24
25
  end
25
26
 
26
- describe '#cf_params' do
27
- subject { described_class.new(options, logger).cf_params }
27
+ context 'unit tests' do
28
+ let(:fake_environment_binding) { binding }
28
29
 
29
- context 'params template file path is not provided' do
30
- let(:options) do
31
- {
32
- env_name: env_name,
33
- config_dir_path: config_dir_path
34
- }
35
- end
36
-
37
- it 'creates an empty array' do
38
- expect(subject).to eq([])
39
- end
40
- end
30
+ describe '#cf_params' do
31
+ subject { environment_config.cf_params }
41
32
 
42
- context 'params file is empty' do
43
- let(:parameter_template) { nil }
33
+ context 'params template file path is not provided' do
34
+ let(:options) do
35
+ {
36
+ env_name: env_name,
37
+ config_dir_path: config_dir_path
38
+ }
39
+ end
44
40
 
45
- it 'creates an empty array' do
46
- expect(subject).to eq([])
41
+ it 'creates an empty array' do
42
+ expect(subject).to eq([])
43
+ end
47
44
  end
48
- end
49
45
 
50
- context 'a hard-coded param' do
51
- let(:parameter_template) { "parameter_key: <%= 'parameter_value' %>" }
46
+ context 'params is empty' do
47
+ let(:parameters) { nil }
52
48
 
53
- it 'creates a array containing an aws formatted parameter hash' do
54
- expect(subject).to eq([{parameter_key: "parameter_key", parameter_value: "parameter_value"}])
49
+ it 'creates an empty array' do
50
+ expect(subject).to eq([])
51
+ end
55
52
  end
56
- end
57
53
 
58
- context 'templated params' do
59
- let(:environment_parameters) { { "stack_name" => "okonomiyaki" } }
54
+ context 'a hard-coded param' do
55
+ let(:parameters) { ERB.new("stack_name: \"foo-stack\"") }
56
+ let(:parameters) { ERB.new("parameter_key: \"parameter_value\"") }
60
57
 
61
- context 'environment params' do
62
- it 'creates a array containing an aws formatted parameter hash' do
63
- allow(file_loader).to receive(:load_config).with('common.yml').and_return({})
64
- allow(file_loader).to receive(:load_config).with(environment_config_file_name).and_return(environment_parameters)
58
+ before do
59
+ allow(file_loader).to receive(:load_hash).with('common.yml').and_return({})
60
+ allow(file_loader).to receive(:load_hash).with('the_jungle.yml').and_return({})
61
+ allow(file_loader).to receive(:load_hash).with('development.yml').and_return({})
62
+ end
65
63
 
66
- expect(subject).to eq([{parameter_key: "stack_name", parameter_value: "okonomiyaki"}])
64
+ it 'creates a array containing an aws formatted parameter hash' do
65
+ expect(subject).to eq([{parameter_key: "parameter_key", parameter_value: "parameter_value"}])
67
66
  end
68
67
  end
69
- end
70
- end
71
68
 
72
- describe "#config" do
73
- subject { described_class.new(options, logger).config }
69
+ describe "#config" do
70
+ subject { described_class.new(options, logger).config }
74
71
 
75
- context 'injected config' do
72
+ context 'injected config' do
76
73
 
77
- let(:options) do
78
- {
79
- env_name: env_name,
80
- config_dir_path: config_dir_path,
81
- params_template_file_path: params_template_file_path,
82
- injected_config: { "injected" => "yes" }
83
- }
84
- end
74
+ let(:options) do
75
+ {
76
+ env_name: env_name,
77
+ config_dir_path: config_dir_path,
78
+ params_template_file_path: params_template_file_path,
79
+ injected_config: { "injected" => "yes" }
80
+ }
81
+ end
85
82
 
86
- let(:common_parameters) { { "stack_name" => "okonomiyaki" } }
87
- it 'adds injected config to the config hash' do
88
- allow(file_loader).to receive(:load_config).with('common.yml').and_return(common_parameters)
89
- allow(file_loader).to receive(:load_config).with(environment_config_file_name).and_return({})
90
- allow(file_loader).to receive(:load_config).with('development.yml').and_return({})
83
+ let(:common_parameters) { { "stack_name" => "okonomiyaki" } }
84
+ it 'adds injected config to the config hash' do
85
+ expect(file_loader).to receive(:load_hash).with('common.yml').and_return(common_parameters)
86
+ expect(file_loader).to receive(:load_hash).with(environment_config_file_name).and_return({})
87
+ expect(file_loader).to receive(:load_hash).with("development.yml").and_return({})
91
88
 
92
- expect(subject).to eq({ "stack_name" => "okonomiyaki", "injected" => "yes" })
93
- end
94
- end
89
+ expect(subject).to eq({ "stack_name" => "okonomiyaki", "injected" => "yes" })
90
+ end
91
+ end
95
92
 
96
- context 'common config' do
97
- let(:common_parameters) { { "stack_name" => "okonomiyaki" } }
93
+ context 'common config' do
94
+ let(:common_parameters) { { "stack_name" => "okonomiyaki" } }
98
95
 
99
- it 'creates a array containing an aws formatted parameter hash' do
100
- allow(file_loader).to receive(:load_config).with('common.yml').and_return(common_parameters)
101
- allow(file_loader).to receive(:load_config).with(environment_config_file_name).and_return({})
102
- allow(file_loader).to receive(:load_config).with('development.yml').and_return({})
96
+ it 'creates a array containing an aws formatted parameter hash' do
97
+ expect(file_loader).to receive(:load_hash).with('common.yml').and_return(common_parameters)
98
+ expect(file_loader).to receive(:load_hash).with(environment_config_file_name).and_return({})
99
+ expect(file_loader).to receive(:load_hash).with("development.yml").and_return({})
103
100
 
104
- expect(subject).to eq('stack_name' => 'okonomiyaki')
101
+ expect(subject).to eq('stack_name' => 'okonomiyaki')
102
+ end
103
+ end
104
+
105
+ context 'merging common and environment specific configurations' do
106
+ let(:environment_config) { {'image' => 'ami-5678'} }
107
+ let(:development_config) { {'image' => 'ami-9999'} }
108
+
109
+ context 'with environmental overrides' do
110
+ let(:parameter_template) { "image: <%= config['image'] %>" }
111
+ let(:common_config) { {'image' => 'ami-1234'} }
112
+ let(:env_name) { 'development' }
113
+
114
+ it 'replaces the common value with the env value' do
115
+ expect(file_loader).to receive(:load_hash).with('common.yml').and_return(common_config)
116
+ expect(file_loader).to receive(:load_hash).with(environment_config_file_name).and_return(environment_config)
117
+ expect(subject).to eq('image' => 'ami-5678')
118
+ end
119
+ end
120
+
121
+ it 'falls back to a default environment if the requested one does not exist' do
122
+ expect(file_loader).to receive(:load_hash).with('common.yml').and_return({})
123
+ expect(file_loader).to receive(:load_hash).with("#{env_name}.yml").and_return({})
124
+ expect(file_loader).to receive(:load_hash).with("development.yml").and_return(development_config)
125
+
126
+ expect(subject).to eq('image' => 'ami-9999')
127
+ end
128
+ end
105
129
  end
106
- end
107
130
 
108
- context 'merging common and environment specific configurations' do
109
- let(:environment_config) { {'image' => 'ami-5678'} }
131
+ describe "#plain_text_secrets" do
132
+ subject { described_class.new(options, logger).plain_text_secrets }
110
133
 
111
- context 'with environmental overrides' do
112
- let(:parameter_template) { "image: <%= config['image'] %>" }
113
- let(:common_config) { {'image' => 'ami-1234'} }
114
- let(:env_name) { 'development' }
134
+ let(:crypted_password) { 'lookatmyencryptedpasswords' }
135
+ let(:plain_text_password) { 'plain_text_password' }
136
+ let(:secrets) { { 'secret_password' => "[ENC,#{crypted_password}" } }
115
137
 
116
- it 'replaces the common value with the env value' do
117
- allow(file_loader).to receive(:load_config).with('common.yml').and_return(common_config)
118
- allow(file_loader).to receive(:load_config).with('development.yml').and_return(environment_config)
138
+ let(:crypted_env_password) { 'cryptedenvpassword' }
139
+ let(:plain_text_env_password) { 'plain_text_env_password' }
140
+ let(:env_secrets) { { 'secret_password' => "[ENC,#{crypted_env_password}"}}
119
141
 
120
- expect(subject).to eq('image' => 'ami-5678')
142
+ before do
143
+ allow(kms).to receive(:decrypt).with(crypted_password).and_return(plain_text_password)
121
144
  end
122
- end
123
-
124
- it 'falls back to a default environment if the requested one does not exist' do
125
- allow(file_loader).to receive(:load_config).with('common.yml').and_return({})
126
- allow(file_loader).to receive(:load_config).with("#{env_name}.yml").and_return({})
127
- expect(file_loader).to receive(:load_config).with("development.yml").and_return(environment_config)
128
145
 
129
- expect(subject).to eq('image' => 'ami-5678')
130
- end
131
- end
132
- end
146
+ it 'decrypts common secrets' do
147
+ allow(file_loader).to receive(:load_hash).with('common_secrets.yml').and_return(secrets)
148
+ allow(file_loader).to receive(:load_hash).with("#{env_name}_secrets.yml").and_return({})
149
+ allow(file_loader).to receive(:load_hash).with("development_secrets.yml").and_return({})
133
150
 
134
- describe "#plain_text_secrets" do
135
- subject { described_class.new(options, logger).plain_text_secrets }
136
-
137
- let(:crypted_password) { 'lookatmyencryptedpasswords' }
138
- let(:plain_text_password) { 'plain_text_password' }
139
- let(:secrets) { { 'secret_password' => "[ENC,#{crypted_password}" } }
151
+ expect(subject).to eq('secret_password' => plain_text_password)
152
+ end
140
153
 
141
- let(:crypted_env_password) { 'cryptedenvpassword' }
142
- let(:plain_text_env_password) { 'plain_text_env_password' }
143
- let(:env_secrets) { { 'secret_password' => "[ENC,#{crypted_env_password}"}}
154
+ it 'decrypts environment secrets' do
155
+ allow(file_loader).to receive(:load_hash).with('common_secrets.yml').and_return({})
156
+ allow(file_loader).to receive(:load_hash).with("#{env_name}_secrets.yml").and_return(secrets)
144
157
 
145
- before do
146
- allow(kms).to receive(:decrypt).with(crypted_password).and_return(plain_text_password)
147
- end
158
+ expect(subject).to eq('secret_password' => plain_text_password)
159
+ end
148
160
 
149
- it 'decrypts common secrets' do
150
- allow(file_loader).to receive(:load_config).with('common_secrets.yml').and_return(secrets)
151
- allow(file_loader).to receive(:load_config).with("#{env_name}_secrets.yml").and_return({})
152
- allow(file_loader).to receive(:load_config).with("development_secrets.yml").and_return({})
161
+ it 'gives preference to environment secrets' do
162
+ allow(file_loader).to receive(:load_hash).with('common_secrets.yml').and_return(secrets)
163
+ allow(file_loader).to receive(:load_hash).with("#{env_name}_secrets.yml").and_return(env_secrets)
164
+ allow(kms).to receive(:decrypt).with(crypted_env_password).and_return(plain_text_env_password)
153
165
 
154
- expect(subject).to eq('secret_password' => plain_text_password)
155
- end
166
+ expect(subject).to eq('secret_password' => plain_text_env_password)
167
+ end
156
168
 
157
- it 'decrypts environment secrets' do
158
- allow(file_loader).to receive(:load_config).with('common_secrets.yml').and_return({})
159
- allow(file_loader).to receive(:load_config).with("#{env_name}_secrets.yml").and_return(secrets)
169
+ it 'falls back to a default environment if the requested one does not exist' do
170
+ allow(file_loader).to receive(:load_hash).with('common_secrets.yml').and_return({})
171
+ allow(file_loader).to receive(:load_hash).with("#{env_name}_secrets.yml").and_return({})
172
+ expect(file_loader).to receive(:load_hash).with("development_secrets.yml").and_return(secrets)
160
173
 
161
- expect(subject).to eq('secret_password' => plain_text_password)
162
- end
174
+ expect(subject).to eq('secret_password' => plain_text_password)
175
+ end
176
+ end
163
177
 
164
- it 'gives preference to environment secrets' do
165
- allow(file_loader).to receive(:load_config).with('common_secrets.yml').and_return(secrets)
166
- allow(file_loader).to receive(:load_config).with("#{env_name}_secrets.yml").and_return(env_secrets)
167
- allow(kms).to receive(:decrypt).with(crypted_env_password).and_return(plain_text_env_password)
178
+ describe '#development?' do
179
+ %w(production staging).each do |environment|
180
+ it "returns false for #{environment}" do
181
+ expect(
182
+ described_class.new({
183
+ env_name: environment,
184
+ config_dir_path: '',
185
+ params_template_file_path: ''}
186
+ ).development?).to eq false
187
+ end
188
+ end
168
189
 
169
- expect(subject).to eq('secret_password' => plain_text_env_password)
190
+ it 'returns true for anything other than production or staging' do
191
+ expect(
192
+ described_class.new({
193
+ env_name: 'fred',
194
+ config_dir_path: '',
195
+ params_template_file_path: ''}
196
+ ).development?).to eq true
197
+ end
198
+ end
170
199
  end
171
200
 
172
- it 'falls back to a default environment if the requested one does not exist' do
173
- allow(file_loader).to receive(:load_config).with('common_secrets.yml').and_return({})
174
- allow(file_loader).to receive(:load_config).with("#{env_name}_secrets.yml").and_return({})
175
- expect(file_loader).to receive(:load_config).with("development_secrets.yml").and_return(secrets)
176
-
177
- expect(subject).to eq('secret_password' => plain_text_password)
201
+ context 'integration tests' do
202
+
203
+ describe '#cf_params' do
204
+ subject { environment_config.cf_params }
205
+
206
+ context 'templated params' do
207
+ let(:parameters) { ERB.new("stack_name: \"<%= config['stack_name'] %>\"" ) }
208
+ let(:common_config) { { "stack_name" => "common"} }
209
+ let(:staging_config) { { "stack_name" => "staging" } }
210
+ let(:development_config) { { "stack_name" => "development" } }
211
+
212
+ context 'hiearchy of parameters' do
213
+ it 'will load values from the common paramater file' do
214
+ expect(file_loader).to receive(:load_hash).with('common.yml').and_return(common_config)
215
+ expect(file_loader).to receive(:load_hash).with("development.yml").and_return({})
216
+ expect(file_loader).to receive(:load_hash).with(environment_config_file_name).and_return({})
217
+ expect(subject).to eq([{parameter_key: "stack_name", parameter_value: "common"}])
218
+ end
219
+
220
+ it 'will load values from the environment specific file' do
221
+ expect(file_loader).to receive(:load_hash).with('common.yml').and_return({})
222
+ expect(file_loader).to receive(:load_hash).with(environment_config_file_name).and_return(staging_config)
223
+ expect(subject).to eq([{parameter_key: "stack_name", parameter_value: "staging"}])
224
+ end
225
+
226
+ it 'will load values from the shared development file if an environment specific file has no values' do
227
+ expect(file_loader).to receive(:load_hash).with('common.yml').and_return({})
228
+ expect(file_loader).to receive(:load_hash).with(environment_config_file_name).and_return({})
229
+ expect(file_loader).to receive(:load_hash).with("development.yml").and_return(development_config)
230
+ expect(subject).to eq([{parameter_key: "stack_name", parameter_value: "development"}])
231
+ end
232
+ end
233
+ end
234
+ end
178
235
  end
179
236
  end
180
237
  end