chef-config 17.9.52 → 17.10.19

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.
@@ -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