chef-config 17.10.29 → 17.10.68

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,633 +1,633 @@
1
- #
2
- # Author:: Daniel DeLeo (<dan@chef.io>)
3
- # Copyright:: Copyright (c) Chef Software Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require "spec_helper"
20
- require "tempfile" unless defined?(Tempfile)
21
-
22
- require "chef-config/exceptions"
23
- require "chef-utils"
24
- require "chef-config/workstation_config_loader"
25
-
26
- RSpec.describe ChefConfig::WorkstationConfigLoader do
27
-
28
- let(:explicit_config_location) { nil }
29
-
30
- let(:env) { {} }
31
-
32
- let(:config_loader) do
33
- described_class.new(explicit_config_location).tap do |c|
34
- allow(c).to receive(:env).and_return(env)
35
- end
36
- end
37
-
38
- before do
39
- # We set this to nil so that a dev workstation will
40
- # not interfere with the tests.
41
- ChefConfig::Config.reset
42
- ChefConfig::Config[:config_d_dir] = nil
43
- end
44
-
45
- # Test methods that do I/O or reference external state which are stubbed out
46
- # elsewhere.
47
- describe "external dependencies" do
48
- let(:config_loader) { described_class.new(nil) }
49
-
50
- it "delegates to ENV for env" do
51
- expect(config_loader.env).to equal(ENV)
52
- end
53
-
54
- it "tests a path's existence" do
55
- expect(config_loader.path_exists?("/nope/nope/nope/nope/slab/jab/nab")).to be(false)
56
- expect(config_loader.path_exists?(__FILE__)).to be(true)
57
- end
58
-
59
- end
60
-
61
- describe "locating the config file" do
62
- context "without an explicit config" do
63
-
64
- before do
65
- allow(config_loader).to receive(:path_exists?).with(an_instance_of(String)).and_return(false)
66
- end
67
-
68
- it "has no config if HOME is not set" do
69
- expect(config_loader.config_location).to be(nil)
70
- expect(config_loader.no_config_found?).to be(true)
71
- end
72
-
73
- context "when HOME is set and contains a knife.rb" do
74
-
75
- let(:home) { "/Users/example.user" }
76
-
77
- before do
78
- allow(ChefConfig::PathHelper).to receive(:home).with(".chef").and_yield(File.join(home, ".chef"))
79
- allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/knife.rb").and_return(true)
80
- end
81
-
82
- it "uses the config in HOME/.chef/knife.rb" do
83
- expect(config_loader.config_location).to eq("#{home}/.chef/knife.rb")
84
- end
85
-
86
- context "and has a config.rb" do
87
-
88
- before do
89
- allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/config.rb").and_return(true)
90
- end
91
-
92
- it "uses the config in HOME/.chef/config.rb" do
93
- expect(config_loader.config_location).to eq("#{home}/.chef/config.rb")
94
- end
95
-
96
- context "and/or a parent dir contains a .chef dir" do
97
-
98
- let(:env_pwd) { "/path/to/cwd" }
99
-
100
- before do
101
- if ChefUtils.windows?
102
- env["CD"] = env_pwd
103
- else
104
- env["PWD"] = env_pwd
105
- end
106
-
107
- allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/knife.rb").and_return(true)
108
- allow(File).to receive(:exist?).with("#{env_pwd}/.chef").and_return(true)
109
- allow(File).to receive(:directory?).with("#{env_pwd}/.chef").and_return(true)
110
- end
111
-
112
- it "prefers the config from parent_dir/.chef" do
113
- expect(config_loader.config_location).to eq("#{env_pwd}/.chef/knife.rb")
114
- end
115
-
116
- context "and the parent dir's .chef dir has a config.rb" do
117
-
118
- before do
119
- allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/config.rb").and_return(true)
120
- end
121
-
122
- it "prefers the config from parent_dir/.chef" do
123
- expect(config_loader.config_location).to eq("#{env_pwd}/.chef/config.rb")
124
- end
125
-
126
- context "and/or the current working directory contains a .chef dir" do
127
-
128
- let(:cwd) { Dir.pwd }
129
-
130
- before do
131
- allow(config_loader).to receive(:path_exists?).with("#{cwd}/knife.rb").and_return(true)
132
- end
133
-
134
- it "prefers a knife.rb located in the cwd" do
135
- expect(config_loader.config_location).to eq("#{cwd}/knife.rb")
136
- end
137
-
138
- context "and the CWD's .chef dir has a config.rb" do
139
-
140
- before do
141
- allow(config_loader).to receive(:path_exists?).with("#{cwd}/config.rb").and_return(true)
142
- end
143
-
144
- it "prefers a config located in the cwd" do
145
- expect(config_loader.config_location).to eq("#{cwd}/config.rb")
146
- end
147
-
148
- context "and/or KNIFE_HOME is set" do
149
-
150
- let(:knife_home) { "/path/to/knife/home" }
151
-
152
- before do
153
- env["KNIFE_HOME"] = knife_home
154
- allow(config_loader).to receive(:path_exists?).with("#{knife_home}/knife.rb").and_return(true)
155
- end
156
-
157
- it "prefers a knife located in KNIFE_HOME" do
158
- expect(config_loader.config_location).to eq("/path/to/knife/home/knife.rb")
159
- end
160
-
161
- context "and KNIFE_HOME contains a config.rb" do
162
-
163
- before do
164
- env["KNIFE_HOME"] = knife_home
165
- allow(config_loader).to receive(:path_exists?).with("#{knife_home}/config.rb").and_return(true)
166
- end
167
-
168
- it "prefers a config.rb located in KNIFE_HOME" do
169
- expect(config_loader.config_location).to eq("/path/to/knife/home/config.rb")
170
- end
171
-
172
- end
173
-
174
- end
175
- end
176
- end
177
- end
178
- end
179
- end
180
- end
181
-
182
- context "when the current working dir is inside a symlinked directory" do
183
- before do
184
- # pwd according to your shell is /home/someuser/prod/chef-repo, but
185
- # chef-repo is a symlink to /home/someuser/codes/chef-repo
186
- env["CD"] = "/home/someuser/prod/chef-repo" # windows
187
- env["PWD"] = "/home/someuser/prod/chef-repo" # unix
188
-
189
- allow(Dir).to receive(:pwd).and_return("/home/someuser/codes/chef-repo")
190
- end
191
-
192
- it "loads the config from the non-dereferenced directory path" do
193
- expect(File).to receive(:exist?).with("/home/someuser/prod/chef-repo/.chef").and_return(false)
194
- expect(File).to receive(:exist?).with("/home/someuser/prod/.chef").and_return(true)
195
- expect(File).to receive(:directory?).with("/home/someuser/prod/.chef").and_return(true)
196
-
197
- expect(config_loader).to receive(:path_exists?).with("/home/someuser/prod/.chef/knife.rb").and_return(true)
198
-
199
- expect(config_loader.config_location).to eq("/home/someuser/prod/.chef/knife.rb")
200
- end
201
- end
202
- end
203
-
204
- context "when given an explicit config to load" do
205
-
206
- let(:explicit_config_location) { "/path/to/explicit/config.rb" }
207
-
208
- it "prefers the explicit config" do
209
- expect(config_loader.config_location).to eq(explicit_config_location)
210
- end
211
-
212
- end
213
- end
214
-
215
- describe "loading the config file" do
216
-
217
- context "when no explicit config is specified and no implicit config is found" do
218
-
219
- before do
220
- allow(config_loader).to receive(:path_exists?).with(an_instance_of(String)).and_return(false)
221
- end
222
-
223
- it "skips loading" do
224
- expect(config_loader.config_location).to be(nil)
225
- expect(config_loader).not_to receive(:apply_config)
226
- config_loader.load
227
- end
228
-
229
- end
230
-
231
- context "when an explicit config is given but it doesn't exist" do
232
-
233
- let(:explicit_config_location) { "/nope/nope/nope/slab/jab/nab" }
234
-
235
- it "raises a configuration error" do
236
- expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
237
- end
238
-
239
- end
240
-
241
- context "when the config file exists" do
242
-
243
- let(:config_content) { "" }
244
-
245
- # We need to keep a reference to the tempfile because while #close does
246
- # not unlink the file, the object being GC'd will.
247
- let(:tempfile) do
248
- Tempfile.new("Chef-WorkstationConfigLoader-rspec-test").tap do |t|
249
- t.print(config_content)
250
- t.close
251
- end
252
- end
253
-
254
- let(:explicit_config_location) do
255
- tempfile.path
256
- end
257
-
258
- after { File.unlink(explicit_config_location) if File.exist?(explicit_config_location) }
259
-
260
- context "and is valid" do
261
-
262
- let(:config_content) { "config_file_evaluated(true)" }
263
-
264
- it "loads the config" do
265
- expect(config_loader).to receive(:apply_config).and_call_original
266
- config_loader.load
267
- expect(ChefConfig::Config.config_file_evaluated).to be(true)
268
- end
269
-
270
- it "sets ChefConfig::Config.config_file" do
271
- config_loader.load
272
- expect(ChefConfig::Config.config_file).to eq(explicit_config_location)
273
- end
274
-
275
- it "loads a default value for node_name" do
276
- allow(Etc).to receive(:getlogin).and_return("notauser")
277
- config_loader.load
278
- expect(ChefConfig::Config.node_name).to eq("notauser")
279
- end
280
-
281
- context "with a user.pem" do
282
- before do
283
- allow(Etc).to receive(:getlogin).and_return("notauser")
284
- allow(FileTest).to receive(:exist?).and_call_original
285
- allow(FileTest).to receive(:exist?).with(File.expand_path("../notauser.pem", explicit_config_location)).and_return(false)
286
- allow(FileTest).to receive(:exist?).with(File.expand_path("../user.pem", explicit_config_location)).and_return(true)
287
- end
288
-
289
- it "loads a default value for client_key" do
290
- config_loader.load
291
- expect(ChefConfig::Config.client_key).to eq(File.expand_path("../user.pem", explicit_config_location))
292
- end
293
- end
294
-
295
- context "with a notauser.pem" do
296
- before do
297
- allow(Etc).to receive(:getlogin).and_return("notauser")
298
- allow(FileTest).to receive(:exist?).and_call_original
299
- allow(FileTest).to receive(:exist?).with(File.expand_path("../notauser.pem", explicit_config_location)).and_return(true)
300
- allow(FileTest).to receive(:exist?).with(File.expand_path("../user.pem", explicit_config_location)).and_return(false)
301
- end
302
-
303
- it "loads a default value for client_key" do
304
- config_loader.load
305
- expect(ChefConfig::Config.client_key).to eq(File.expand_path("../notauser.pem", explicit_config_location))
306
- end
307
- end
308
-
309
- context "with a valclient.pem" do
310
- before do
311
- ChefConfig::Config.validation_client_name = "valclient"
312
- allow(FileTest).to receive(:exist?).and_call_original
313
- allow(FileTest).to receive(:exist?).with(File.expand_path("../valclient.pem", explicit_config_location)).and_return(true)
314
- allow(FileTest).to receive(:exist?).with(File.expand_path("../validator.pem", explicit_config_location)).and_return(false)
315
- allow(FileTest).to receive(:exist?).with(File.expand_path("../validation.pem", explicit_config_location)).and_return(false)
316
- end
317
-
318
- it "loads a default value for validation_key" do
319
- config_loader.load
320
- expect(ChefConfig::Config.validation_key).to eq(File.expand_path("../valclient.pem", explicit_config_location))
321
- end
322
- end
323
-
324
- context "with a validator.pem" do
325
- before do
326
- ChefConfig::Config.validation_client_name = "valclient"
327
- allow(FileTest).to receive(:exist?).and_call_original
328
- allow(FileTest).to receive(:exist?).with(File.expand_path("../valclient.pem", explicit_config_location)).and_return(false)
329
- allow(FileTest).to receive(:exist?).with(File.expand_path("../validator.pem", explicit_config_location)).and_return(true)
330
- allow(FileTest).to receive(:exist?).with(File.expand_path("../validation.pem", explicit_config_location)).and_return(false)
331
- end
332
-
333
- it "loads a default value for validation_key" do
334
- config_loader.load
335
- expect(ChefConfig::Config.validation_key).to eq(File.expand_path("../validator.pem", explicit_config_location))
336
- end
337
- end
338
- end
339
-
340
- context "and has a syntax error" do
341
-
342
- let(:config_content) { "{{{{{:{{" }
343
-
344
- it "raises a ConfigurationError" do
345
- expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
346
- end
347
- end
348
-
349
- context "and raises a ruby exception during evaluation" do
350
-
351
- let(:config_content) { ":foo\n:bar\nraise 'oops'\n:baz\n" }
352
-
353
- it "raises a ConfigurationError" do
354
- expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
355
- end
356
- end
357
-
358
- end
359
-
360
- end
361
-
362
- describe "when loading config.d" do
363
- context "when the conf.d directory exists" do
364
- let(:config_content) { "" }
365
-
366
- let(:tempdir) { Dir.mktmpdir("chef-workstation-test") }
367
-
368
- let!(:confd_file) do
369
- Tempfile.new(["Chef-WorkstationConfigLoader-rspec-test", ".rb"], tempdir).tap do |t|
370
- t.print(config_content)
371
- t.close
372
- end
373
- end
374
-
375
- before do
376
- ChefConfig::Config[:config_d_dir] = tempdir
377
- allow(config_loader).to receive(:path_exists?).with(
378
- an_instance_of(String)
379
- ).and_return(false)
380
- end
381
-
382
- after do
383
- FileUtils.remove_entry_secure tempdir
384
- end
385
-
386
- context "and is valid" do
387
- let(:config_content) { "config_d_file_evaluated(true)" }
388
-
389
- it "loads the config" do
390
- expect(config_loader).to receive(:apply_config).and_call_original
391
- config_loader.load
392
- expect(ChefConfig::Config.config_d_file_evaluated).to be(true)
393
- end
394
- end
395
-
396
- context "and has a syntax error" do
397
- let(:config_content) { "{{{{{:{{" }
398
-
399
- it "raises a ConfigurationError" do
400
- expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
401
- end
402
- end
403
-
404
- context "has a non rb file" do
405
- let(:syntax_error_content) { "{{{{{:{{" }
406
- let(:config_content) { "config_d_file_evaluated(true)" }
407
-
408
- let!(:not_confd_file) do
409
- Tempfile.new(["Chef-WorkstationConfigLoader-rspec-test", ".foorb"], tempdir).tap do |t|
410
- t.print(syntax_error_content)
411
- t.close
412
- end
413
- end
414
-
415
- it "does not load the non rb file" do
416
- expect { config_loader.load }.not_to raise_error
417
- expect(ChefConfig::Config.config_d_file_evaluated).to be(true)
418
- end
419
- end
420
- end
421
-
422
- context "when the conf.d directory does not exist" do
423
- before do
424
- ChefConfig::Config[:config_d_dir] = "/nope/nope/nope/nope/notdoingit"
425
- end
426
-
427
- it "does not load anything" do
428
- expect(config_loader).not_to receive(:apply_config)
429
- end
430
- end
431
- end
432
-
433
- describe "when loading a credentials file" do
434
- if ChefUtils.windows?
435
- let(:home) { "C:/Users/example.user" }
436
- else
437
- let(:home) { "/Users/example.user" }
438
- end
439
- let(:credentials_file) { "#{home}/.chef/credentials" }
440
- let(:context_file) { "#{home}/.chef/context" }
441
-
442
- before do
443
- allow(ChefConfig::PathHelper).to receive(:home).with(".chef").and_return(File.join(home, ".chef"))
444
- allow(ChefConfig::PathHelper).to receive(:home).with(".chef", "credentials").and_return(credentials_file)
445
- allow(ChefConfig::PathHelper).to receive(:home).with(".chef", "context").and_return(context_file)
446
- allow(File).to receive(:file?).with(context_file).and_return false
447
- end
448
-
449
- context "when the file exists" do
450
- before do
451
- expect(File).to receive(:read).with(credentials_file, { encoding: "utf-8" }).and_return(content)
452
- allow(File).to receive(:file?).with(credentials_file).and_return true
453
- end
454
-
455
- context "and has a default profile" do
456
- let(:content) do
457
- content = <<~EOH
458
- [default]
459
- node_name = 'barney'
460
- client_key = "barney_rubble.pem"
461
- chef_server_url = "https://api.chef.io/organizations/bedrock"
462
- invalid_config_option1234 = "foobar"
463
- EOH
464
- content
465
- end
466
-
467
- it "applies the expected config" do
468
- expect { config_loader.load_credentials }.not_to raise_error
469
- expect(ChefConfig::Config.chef_server_url).to eq("https://api.chef.io/organizations/bedrock")
470
- expect(ChefConfig::Config.client_key.to_s).to eq("#{home}/.chef/barney_rubble.pem")
471
- expect(ChefConfig::Config.profile.to_s).to eq("default")
472
- expect(ChefConfig::Config[:invalid_config_option1234]).to eq("foobar")
473
- end
474
- end
475
-
476
- context "and has a default profile with knife settings" do
477
- let(:content) do
478
- content = <<~EOH
479
- [default]
480
- node_name = 'barney'
481
- client_key = "barney_rubble.pem"
482
- chef_server_url = "https://api.chef.io/organizations/bedrock"
483
- knife = {
484
- secret_file = "/home/barney/.chef/encrypted_data_bag_secret.pem"
485
- }
486
- [default.knife]
487
- ssh_user = "knife_ssh_user"
488
- EOH
489
- content
490
- end
491
-
492
- it "applies the expected knife config" do
493
- expect { config_loader.load_credentials }.not_to raise_error
494
- expect(ChefConfig::Config.chef_server_url).to eq("https://api.chef.io/organizations/bedrock")
495
- expect(ChefConfig::Config.client_key.to_s).to eq("#{home}/.chef/barney_rubble.pem")
496
- expect(ChefConfig::Config.knife[:ssh_user].to_s).to eq("knife_ssh_user")
497
- expect(ChefConfig::Config.knife[:secret_file].to_s).to eq("/home/barney/.chef/encrypted_data_bag_secret.pem")
498
- expect(ChefConfig::Config.profile.to_s).to eq("default")
499
- end
500
- end
501
-
502
- context "and has a profile containing a full key" do
503
- let(:content) do
504
- content = <<~EOH
505
- [default]
506
- client_key = """
507
- -----BEGIN RSA PRIVATE KEY-----
508
- foo
509
- """
510
- EOH
511
- content
512
- end
513
-
514
- it "applies the expected config" do
515
- expect { config_loader.load_credentials }.not_to raise_error
516
- expect(ChefConfig::Config.client_key_contents).to eq(<<~EOH
517
- -----BEGIN RSA PRIVATE KEY-----
518
- foo
519
- EOH
520
- )
521
- end
522
- end
523
-
524
- context "and has several profiles" do
525
- let(:content) do
526
- content = <<~EOH
527
- [default]
528
- client_name = "default"
529
- [environment]
530
- client_name = "environment"
531
- [explicit]
532
- client_name = "explicit"
533
- [context]
534
- client_name = "context"
535
- EOH
536
- content
537
- end
538
-
539
- let(:env) { {} }
540
- before do
541
- stub_const("ENV", env)
542
- end
543
-
544
- it "selects the correct profile explicitly" do
545
- expect { config_loader.load_credentials("explicit") }.not_to raise_error
546
- expect(ChefConfig::Config.node_name).to eq("explicit")
547
- end
548
-
549
- context "with an environment variable" do
550
- let(:env) { { "CHEF_PROFILE" => "environment" } }
551
-
552
- it "selects the correct profile" do
553
- expect { config_loader.load_credentials }.not_to raise_error
554
- expect(ChefConfig::Config.node_name).to eq("environment")
555
- end
556
- end
557
-
558
- it "selects the correct profile with a context file" do
559
- allow(File).to receive(:file?).with(context_file).and_return true
560
- expect(File).to receive(:read).with(context_file).and_return "context"
561
- expect { config_loader.load_credentials }.not_to raise_error
562
- expect(ChefConfig::Config.node_name).to eq("context")
563
- end
564
-
565
- it "falls back to the default" do
566
- expect { config_loader.load_credentials }.not_to raise_error
567
- expect(ChefConfig::Config.node_name).to eq("default")
568
- end
569
- end
570
-
571
- context "and contains both node_name and client_name" do
572
- let(:content) do
573
- content = <<~EOH
574
- [default]
575
- node_name = 'barney'
576
- client_name = 'barney'
577
- EOH
578
- content
579
- end
580
-
581
- it "raises a ConfigurationError" do
582
- expect { config_loader.load_credentials }.to raise_error(ChefConfig::ConfigurationError)
583
- end
584
- end
585
-
586
- context "and ssl_verify_mode is a symbol string" do
587
- let(:content) do
588
- content = <<~EOH
589
- [default]
590
- ssl_verify_mode = ":verify_none"
591
- EOH
592
- content
593
- end
594
-
595
- it "raises a ConfigurationError" do
596
- expect { config_loader.load_credentials }.not_to raise_error
597
- expect(ChefConfig::Config.ssl_verify_mode).to eq(:verify_none)
598
- end
599
- end
600
-
601
- context "and ssl_verify_mode is a string" do
602
- let(:content) do
603
- content = <<~EOH
604
- [default]
605
- ssl_verify_mode = "verify_none"
606
- EOH
607
- content
608
- end
609
-
610
- it "raises a ConfigurationError" do
611
- expect { config_loader.load_credentials }.not_to raise_error
612
- expect(ChefConfig::Config.ssl_verify_mode).to eq(:verify_none)
613
- end
614
- end
615
-
616
- context "and has a syntax error" do
617
- let(:content) { "<<<<<" }
618
-
619
- it "raises a ConfigurationError" do
620
- expect { config_loader.load_credentials }.to raise_error(ChefConfig::ConfigurationError)
621
- end
622
- end
623
- end
624
-
625
- context "when the file does not exist" do
626
- it "does not load anything" do
627
- allow(File).to receive(:file?).with(credentials_file).and_return false
628
- expect(Tomlrb).not_to receive(:load_file)
629
- config_loader.load_credentials
630
- end
631
- end
632
- end
633
- end
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@chef.io>)
3
+ # Copyright:: Copyright (c) Chef Software Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require "spec_helper"
20
+ require "tempfile" unless defined?(Tempfile)
21
+
22
+ require "chef-config/exceptions"
23
+ require "chef-utils"
24
+ require "chef-config/workstation_config_loader"
25
+
26
+ RSpec.describe ChefConfig::WorkstationConfigLoader do
27
+
28
+ let(:explicit_config_location) { nil }
29
+
30
+ let(:env) { {} }
31
+
32
+ let(:config_loader) do
33
+ described_class.new(explicit_config_location).tap do |c|
34
+ allow(c).to receive(:env).and_return(env)
35
+ end
36
+ end
37
+
38
+ before do
39
+ # We set this to nil so that a dev workstation will
40
+ # not interfere with the tests.
41
+ ChefConfig::Config.reset
42
+ ChefConfig::Config[:config_d_dir] = nil
43
+ end
44
+
45
+ # Test methods that do I/O or reference external state which are stubbed out
46
+ # elsewhere.
47
+ describe "external dependencies" do
48
+ let(:config_loader) { described_class.new(nil) }
49
+
50
+ it "delegates to ENV for env" do
51
+ expect(config_loader.env).to equal(ENV)
52
+ end
53
+
54
+ it "tests a path's existence" do
55
+ expect(config_loader.path_exists?("/nope/nope/nope/nope/slab/jab/nab")).to be(false)
56
+ expect(config_loader.path_exists?(__FILE__)).to be(true)
57
+ end
58
+
59
+ end
60
+
61
+ describe "locating the config file" do
62
+ context "without an explicit config" do
63
+
64
+ before do
65
+ allow(config_loader).to receive(:path_exists?).with(an_instance_of(String)).and_return(false)
66
+ end
67
+
68
+ it "has no config if HOME is not set" do
69
+ expect(config_loader.config_location).to be(nil)
70
+ expect(config_loader.no_config_found?).to be(true)
71
+ end
72
+
73
+ context "when HOME is set and contains a knife.rb" do
74
+
75
+ let(:home) { "/Users/example.user" }
76
+
77
+ before do
78
+ allow(ChefConfig::PathHelper).to receive(:home).with(".chef").and_yield(File.join(home, ".chef"))
79
+ allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/knife.rb").and_return(true)
80
+ end
81
+
82
+ it "uses the config in HOME/.chef/knife.rb" do
83
+ expect(config_loader.config_location).to eq("#{home}/.chef/knife.rb")
84
+ end
85
+
86
+ context "and has a config.rb" do
87
+
88
+ before do
89
+ allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/config.rb").and_return(true)
90
+ end
91
+
92
+ it "uses the config in HOME/.chef/config.rb" do
93
+ expect(config_loader.config_location).to eq("#{home}/.chef/config.rb")
94
+ end
95
+
96
+ context "and/or a parent dir contains a .chef dir" do
97
+
98
+ let(:env_pwd) { "/path/to/cwd" }
99
+
100
+ before do
101
+ if ChefUtils.windows?
102
+ env["CD"] = env_pwd
103
+ else
104
+ env["PWD"] = env_pwd
105
+ end
106
+
107
+ allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/knife.rb").and_return(true)
108
+ allow(File).to receive(:exist?).with("#{env_pwd}/.chef").and_return(true)
109
+ allow(File).to receive(:directory?).with("#{env_pwd}/.chef").and_return(true)
110
+ end
111
+
112
+ it "prefers the config from parent_dir/.chef" do
113
+ expect(config_loader.config_location).to eq("#{env_pwd}/.chef/knife.rb")
114
+ end
115
+
116
+ context "and the parent dir's .chef dir has a config.rb" do
117
+
118
+ before do
119
+ allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/config.rb").and_return(true)
120
+ end
121
+
122
+ it "prefers the config from parent_dir/.chef" do
123
+ expect(config_loader.config_location).to eq("#{env_pwd}/.chef/config.rb")
124
+ end
125
+
126
+ context "and/or the current working directory contains a .chef dir" do
127
+
128
+ let(:cwd) { Dir.pwd }
129
+
130
+ before do
131
+ allow(config_loader).to receive(:path_exists?).with("#{cwd}/knife.rb").and_return(true)
132
+ end
133
+
134
+ it "prefers a knife.rb located in the cwd" do
135
+ expect(config_loader.config_location).to eq("#{cwd}/knife.rb")
136
+ end
137
+
138
+ context "and the CWD's .chef dir has a config.rb" do
139
+
140
+ before do
141
+ allow(config_loader).to receive(:path_exists?).with("#{cwd}/config.rb").and_return(true)
142
+ end
143
+
144
+ it "prefers a config located in the cwd" do
145
+ expect(config_loader.config_location).to eq("#{cwd}/config.rb")
146
+ end
147
+
148
+ context "and/or KNIFE_HOME is set" do
149
+
150
+ let(:knife_home) { "/path/to/knife/home" }
151
+
152
+ before do
153
+ env["KNIFE_HOME"] = knife_home
154
+ allow(config_loader).to receive(:path_exists?).with("#{knife_home}/knife.rb").and_return(true)
155
+ end
156
+
157
+ it "prefers a knife located in KNIFE_HOME" do
158
+ expect(config_loader.config_location).to eq("/path/to/knife/home/knife.rb")
159
+ end
160
+
161
+ context "and KNIFE_HOME contains a config.rb" do
162
+
163
+ before do
164
+ env["KNIFE_HOME"] = knife_home
165
+ allow(config_loader).to receive(:path_exists?).with("#{knife_home}/config.rb").and_return(true)
166
+ end
167
+
168
+ it "prefers a config.rb located in KNIFE_HOME" do
169
+ expect(config_loader.config_location).to eq("/path/to/knife/home/config.rb")
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ context "when the current working dir is inside a symlinked directory" do
183
+ before do
184
+ # pwd according to your shell is /home/someuser/prod/chef-repo, but
185
+ # chef-repo is a symlink to /home/someuser/codes/chef-repo
186
+ env["CD"] = "/home/someuser/prod/chef-repo" # windows
187
+ env["PWD"] = "/home/someuser/prod/chef-repo" # unix
188
+
189
+ allow(Dir).to receive(:pwd).and_return("/home/someuser/codes/chef-repo")
190
+ end
191
+
192
+ it "loads the config from the non-dereferenced directory path" do
193
+ expect(File).to receive(:exist?).with("/home/someuser/prod/chef-repo/.chef").and_return(false)
194
+ expect(File).to receive(:exist?).with("/home/someuser/prod/.chef").and_return(true)
195
+ expect(File).to receive(:directory?).with("/home/someuser/prod/.chef").and_return(true)
196
+
197
+ expect(config_loader).to receive(:path_exists?).with("/home/someuser/prod/.chef/knife.rb").and_return(true)
198
+
199
+ expect(config_loader.config_location).to eq("/home/someuser/prod/.chef/knife.rb")
200
+ end
201
+ end
202
+ end
203
+
204
+ context "when given an explicit config to load" do
205
+
206
+ let(:explicit_config_location) { "/path/to/explicit/config.rb" }
207
+
208
+ it "prefers the explicit config" do
209
+ expect(config_loader.config_location).to eq(explicit_config_location)
210
+ end
211
+
212
+ end
213
+ end
214
+
215
+ describe "loading the config file" do
216
+
217
+ context "when no explicit config is specified and no implicit config is found" do
218
+
219
+ before do
220
+ allow(config_loader).to receive(:path_exists?).with(an_instance_of(String)).and_return(false)
221
+ end
222
+
223
+ it "skips loading" do
224
+ expect(config_loader.config_location).to be(nil)
225
+ expect(config_loader).not_to receive(:apply_config)
226
+ config_loader.load
227
+ end
228
+
229
+ end
230
+
231
+ context "when an explicit config is given but it doesn't exist" do
232
+
233
+ let(:explicit_config_location) { "/nope/nope/nope/slab/jab/nab" }
234
+
235
+ it "raises a configuration error" do
236
+ expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
237
+ end
238
+
239
+ end
240
+
241
+ context "when the config file exists" do
242
+
243
+ let(:config_content) { "" }
244
+
245
+ # We need to keep a reference to the tempfile because while #close does
246
+ # not unlink the file, the object being GC'd will.
247
+ let(:tempfile) do
248
+ Tempfile.new("Chef-WorkstationConfigLoader-rspec-test").tap do |t|
249
+ t.print(config_content)
250
+ t.close
251
+ end
252
+ end
253
+
254
+ let(:explicit_config_location) do
255
+ tempfile.path
256
+ end
257
+
258
+ after { File.unlink(explicit_config_location) if File.exist?(explicit_config_location) }
259
+
260
+ context "and is valid" do
261
+
262
+ let(:config_content) { "config_file_evaluated(true)" }
263
+
264
+ it "loads the config" do
265
+ expect(config_loader).to receive(:apply_config).and_call_original
266
+ config_loader.load
267
+ expect(ChefConfig::Config.config_file_evaluated).to be(true)
268
+ end
269
+
270
+ it "sets ChefConfig::Config.config_file" do
271
+ config_loader.load
272
+ expect(ChefConfig::Config.config_file).to eq(explicit_config_location)
273
+ end
274
+
275
+ it "loads a default value for node_name" do
276
+ allow(Etc).to receive(:getlogin).and_return("notauser")
277
+ config_loader.load
278
+ expect(ChefConfig::Config.node_name).to eq("notauser")
279
+ end
280
+
281
+ context "with a user.pem" do
282
+ before do
283
+ allow(Etc).to receive(:getlogin).and_return("notauser")
284
+ allow(FileTest).to receive(:exist?).and_call_original
285
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../notauser.pem", explicit_config_location)).and_return(false)
286
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../user.pem", explicit_config_location)).and_return(true)
287
+ end
288
+
289
+ it "loads a default value for client_key" do
290
+ config_loader.load
291
+ expect(ChefConfig::Config.client_key).to eq(File.expand_path("../user.pem", explicit_config_location))
292
+ end
293
+ end
294
+
295
+ context "with a notauser.pem" do
296
+ before do
297
+ allow(Etc).to receive(:getlogin).and_return("notauser")
298
+ allow(FileTest).to receive(:exist?).and_call_original
299
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../notauser.pem", explicit_config_location)).and_return(true)
300
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../user.pem", explicit_config_location)).and_return(false)
301
+ end
302
+
303
+ it "loads a default value for client_key" do
304
+ config_loader.load
305
+ expect(ChefConfig::Config.client_key).to eq(File.expand_path("../notauser.pem", explicit_config_location))
306
+ end
307
+ end
308
+
309
+ context "with a valclient.pem" do
310
+ before do
311
+ ChefConfig::Config.validation_client_name = "valclient"
312
+ allow(FileTest).to receive(:exist?).and_call_original
313
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../valclient.pem", explicit_config_location)).and_return(true)
314
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../validator.pem", explicit_config_location)).and_return(false)
315
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../validation.pem", explicit_config_location)).and_return(false)
316
+ end
317
+
318
+ it "loads a default value for validation_key" do
319
+ config_loader.load
320
+ expect(ChefConfig::Config.validation_key).to eq(File.expand_path("../valclient.pem", explicit_config_location))
321
+ end
322
+ end
323
+
324
+ context "with a validator.pem" do
325
+ before do
326
+ ChefConfig::Config.validation_client_name = "valclient"
327
+ allow(FileTest).to receive(:exist?).and_call_original
328
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../valclient.pem", explicit_config_location)).and_return(false)
329
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../validator.pem", explicit_config_location)).and_return(true)
330
+ allow(FileTest).to receive(:exist?).with(File.expand_path("../validation.pem", explicit_config_location)).and_return(false)
331
+ end
332
+
333
+ it "loads a default value for validation_key" do
334
+ config_loader.load
335
+ expect(ChefConfig::Config.validation_key).to eq(File.expand_path("../validator.pem", explicit_config_location))
336
+ end
337
+ end
338
+ end
339
+
340
+ context "and has a syntax error" do
341
+
342
+ let(:config_content) { "{{{{{:{{" }
343
+
344
+ it "raises a ConfigurationError" do
345
+ expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
346
+ end
347
+ end
348
+
349
+ context "and raises a ruby exception during evaluation" do
350
+
351
+ let(:config_content) { ":foo\n:bar\nraise 'oops'\n:baz\n" }
352
+
353
+ it "raises a ConfigurationError" do
354
+ expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
355
+ end
356
+ end
357
+
358
+ end
359
+
360
+ end
361
+
362
+ describe "when loading config.d" do
363
+ context "when the conf.d directory exists" do
364
+ let(:config_content) { "" }
365
+
366
+ let(:tempdir) { Dir.mktmpdir("chef-workstation-test") }
367
+
368
+ let!(:confd_file) do
369
+ Tempfile.new(["Chef-WorkstationConfigLoader-rspec-test", ".rb"], tempdir).tap do |t|
370
+ t.print(config_content)
371
+ t.close
372
+ end
373
+ end
374
+
375
+ before do
376
+ ChefConfig::Config[:config_d_dir] = tempdir
377
+ allow(config_loader).to receive(:path_exists?).with(
378
+ an_instance_of(String)
379
+ ).and_return(false)
380
+ end
381
+
382
+ after do
383
+ FileUtils.remove_entry_secure tempdir
384
+ end
385
+
386
+ context "and is valid" do
387
+ let(:config_content) { "config_d_file_evaluated(true)" }
388
+
389
+ it "loads the config" do
390
+ expect(config_loader).to receive(:apply_config).and_call_original
391
+ config_loader.load
392
+ expect(ChefConfig::Config.config_d_file_evaluated).to be(true)
393
+ end
394
+ end
395
+
396
+ context "and has a syntax error" do
397
+ let(:config_content) { "{{{{{:{{" }
398
+
399
+ it "raises a ConfigurationError" do
400
+ expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
401
+ end
402
+ end
403
+
404
+ context "has a non rb file" do
405
+ let(:syntax_error_content) { "{{{{{:{{" }
406
+ let(:config_content) { "config_d_file_evaluated(true)" }
407
+
408
+ let!(:not_confd_file) do
409
+ Tempfile.new(["Chef-WorkstationConfigLoader-rspec-test", ".foorb"], tempdir).tap do |t|
410
+ t.print(syntax_error_content)
411
+ t.close
412
+ end
413
+ end
414
+
415
+ it "does not load the non rb file" do
416
+ expect { config_loader.load }.not_to raise_error
417
+ expect(ChefConfig::Config.config_d_file_evaluated).to be(true)
418
+ end
419
+ end
420
+ end
421
+
422
+ context "when the conf.d directory does not exist" do
423
+ before do
424
+ ChefConfig::Config[:config_d_dir] = "/nope/nope/nope/nope/notdoingit"
425
+ end
426
+
427
+ it "does not load anything" do
428
+ expect(config_loader).not_to receive(:apply_config)
429
+ end
430
+ end
431
+ end
432
+
433
+ describe "when loading a credentials file" do
434
+ if ChefUtils.windows?
435
+ let(:home) { "C:/Users/example.user" }
436
+ else
437
+ let(:home) { "/Users/example.user" }
438
+ end
439
+ let(:credentials_file) { "#{home}/.chef/credentials" }
440
+ let(:context_file) { "#{home}/.chef/context" }
441
+
442
+ before do
443
+ allow(ChefConfig::PathHelper).to receive(:home).with(".chef").and_return(File.join(home, ".chef"))
444
+ allow(ChefConfig::PathHelper).to receive(:home).with(".chef", "credentials").and_return(credentials_file)
445
+ allow(ChefConfig::PathHelper).to receive(:home).with(".chef", "context").and_return(context_file)
446
+ allow(File).to receive(:file?).with(context_file).and_return false
447
+ end
448
+
449
+ context "when the file exists" do
450
+ before do
451
+ expect(File).to receive(:read).with(credentials_file, { encoding: "utf-8" }).and_return(content)
452
+ allow(File).to receive(:file?).with(credentials_file).and_return true
453
+ end
454
+
455
+ context "and has a default profile" do
456
+ let(:content) do
457
+ content = <<~EOH
458
+ [default]
459
+ node_name = 'barney'
460
+ client_key = "barney_rubble.pem"
461
+ chef_server_url = "https://api.chef.io/organizations/bedrock"
462
+ invalid_config_option1234 = "foobar"
463
+ EOH
464
+ content
465
+ end
466
+
467
+ it "applies the expected config" do
468
+ expect { config_loader.load_credentials }.not_to raise_error
469
+ expect(ChefConfig::Config.chef_server_url).to eq("https://api.chef.io/organizations/bedrock")
470
+ expect(ChefConfig::Config.client_key.to_s).to eq("#{home}/.chef/barney_rubble.pem")
471
+ expect(ChefConfig::Config.profile.to_s).to eq("default")
472
+ expect(ChefConfig::Config[:invalid_config_option1234]).to eq("foobar")
473
+ end
474
+ end
475
+
476
+ context "and has a default profile with knife settings" do
477
+ let(:content) do
478
+ content = <<~EOH
479
+ [default]
480
+ node_name = 'barney'
481
+ client_key = "barney_rubble.pem"
482
+ chef_server_url = "https://api.chef.io/organizations/bedrock"
483
+ knife = {
484
+ secret_file = "/home/barney/.chef/encrypted_data_bag_secret.pem"
485
+ }
486
+ [default.knife]
487
+ ssh_user = "knife_ssh_user"
488
+ EOH
489
+ content
490
+ end
491
+
492
+ it "applies the expected knife config" do
493
+ expect { config_loader.load_credentials }.not_to raise_error
494
+ expect(ChefConfig::Config.chef_server_url).to eq("https://api.chef.io/organizations/bedrock")
495
+ expect(ChefConfig::Config.client_key.to_s).to eq("#{home}/.chef/barney_rubble.pem")
496
+ expect(ChefConfig::Config.knife[:ssh_user].to_s).to eq("knife_ssh_user")
497
+ expect(ChefConfig::Config.knife[:secret_file].to_s).to eq("/home/barney/.chef/encrypted_data_bag_secret.pem")
498
+ expect(ChefConfig::Config.profile.to_s).to eq("default")
499
+ end
500
+ end
501
+
502
+ context "and has a profile containing a full key" do
503
+ let(:content) do
504
+ content = <<~EOH
505
+ [default]
506
+ client_key = """
507
+ -----BEGIN RSA PRIVATE KEY-----
508
+ foo
509
+ """
510
+ EOH
511
+ content
512
+ end
513
+
514
+ it "applies the expected config" do
515
+ expect { config_loader.load_credentials }.not_to raise_error
516
+ expect(ChefConfig::Config.client_key_contents).to eq(<<~EOH
517
+ -----BEGIN RSA PRIVATE KEY-----
518
+ foo
519
+ EOH
520
+ )
521
+ end
522
+ end
523
+
524
+ context "and has several profiles" do
525
+ let(:content) do
526
+ content = <<~EOH
527
+ [default]
528
+ client_name = "default"
529
+ [environment]
530
+ client_name = "environment"
531
+ [explicit]
532
+ client_name = "explicit"
533
+ [context]
534
+ client_name = "context"
535
+ EOH
536
+ content
537
+ end
538
+
539
+ let(:env) { {} }
540
+ before do
541
+ stub_const("ENV", env)
542
+ end
543
+
544
+ it "selects the correct profile explicitly" do
545
+ expect { config_loader.load_credentials("explicit") }.not_to raise_error
546
+ expect(ChefConfig::Config.node_name).to eq("explicit")
547
+ end
548
+
549
+ context "with an environment variable" do
550
+ let(:env) { { "CHEF_PROFILE" => "environment" } }
551
+
552
+ it "selects the correct profile" do
553
+ expect { config_loader.load_credentials }.not_to raise_error
554
+ expect(ChefConfig::Config.node_name).to eq("environment")
555
+ end
556
+ end
557
+
558
+ it "selects the correct profile with a context file" do
559
+ allow(File).to receive(:file?).with(context_file).and_return true
560
+ expect(File).to receive(:read).with(context_file).and_return "context"
561
+ expect { config_loader.load_credentials }.not_to raise_error
562
+ expect(ChefConfig::Config.node_name).to eq("context")
563
+ end
564
+
565
+ it "falls back to the default" do
566
+ expect { config_loader.load_credentials }.not_to raise_error
567
+ expect(ChefConfig::Config.node_name).to eq("default")
568
+ end
569
+ end
570
+
571
+ context "and contains both node_name and client_name" do
572
+ let(:content) do
573
+ content = <<~EOH
574
+ [default]
575
+ node_name = 'barney'
576
+ client_name = 'barney'
577
+ EOH
578
+ content
579
+ end
580
+
581
+ it "raises a ConfigurationError" do
582
+ expect { config_loader.load_credentials }.to raise_error(ChefConfig::ConfigurationError)
583
+ end
584
+ end
585
+
586
+ context "and ssl_verify_mode is a symbol string" do
587
+ let(:content) do
588
+ content = <<~EOH
589
+ [default]
590
+ ssl_verify_mode = ":verify_none"
591
+ EOH
592
+ content
593
+ end
594
+
595
+ it "raises a ConfigurationError" do
596
+ expect { config_loader.load_credentials }.not_to raise_error
597
+ expect(ChefConfig::Config.ssl_verify_mode).to eq(:verify_none)
598
+ end
599
+ end
600
+
601
+ context "and ssl_verify_mode is a string" do
602
+ let(:content) do
603
+ content = <<~EOH
604
+ [default]
605
+ ssl_verify_mode = "verify_none"
606
+ EOH
607
+ content
608
+ end
609
+
610
+ it "raises a ConfigurationError" do
611
+ expect { config_loader.load_credentials }.not_to raise_error
612
+ expect(ChefConfig::Config.ssl_verify_mode).to eq(:verify_none)
613
+ end
614
+ end
615
+
616
+ context "and has a syntax error" do
617
+ let(:content) { "<<<<<" }
618
+
619
+ it "raises a ConfigurationError" do
620
+ expect { config_loader.load_credentials }.to raise_error(ChefConfig::ConfigurationError)
621
+ end
622
+ end
623
+ end
624
+
625
+ context "when the file does not exist" do
626
+ it "does not load anything" do
627
+ allow(File).to receive(:file?).with(credentials_file).and_return false
628
+ expect(Tomlrb).not_to receive(:load_file)
629
+ config_loader.load_credentials
630
+ end
631
+ end
632
+ end
633
+ end