chef-apply 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -7
  3. data/Gemfile.lock +176 -84
  4. data/chef-apply.gemspec +2 -2
  5. data/lib/chef_apply/version.rb +1 -1
  6. data/spec/fixtures/custom_config.toml +2 -0
  7. data/spec/integration/chef-run_spec.rb +41 -0
  8. data/spec/integration/fixtures/chef_help.out +69 -0
  9. data/spec/integration/fixtures/chef_version.out +1 -0
  10. data/spec/integration/spec_helper.rb +55 -0
  11. data/spec/spec_helper.rb +114 -0
  12. data/spec/support/matchers/output_to_terminal.rb +36 -0
  13. data/spec/unit/action/base_spec.rb +89 -0
  14. data/spec/unit/action/converge_target_spec.rb +292 -0
  15. data/spec/unit/action/generate_local_policy_spec.rb +114 -0
  16. data/spec/unit/action/generate_temp_cookbook_spec.rb +75 -0
  17. data/spec/unit/action/install_chef/base_spec.rb +234 -0
  18. data/spec/unit/action/install_chef_spec.rb +69 -0
  19. data/spec/unit/cli/options_spec.rb +75 -0
  20. data/spec/unit/cli/validation_spec.rb +78 -0
  21. data/spec/unit/cli_spec.rb +440 -0
  22. data/spec/unit/config_spec.rb +70 -0
  23. data/spec/unit/errors/ccr_failure_mapper_spec.rb +103 -0
  24. data/spec/unit/file_fetcher_spec.rb +40 -0
  25. data/spec/unit/fixtures/multi-error.out +2 -0
  26. data/spec/unit/log_spec.rb +37 -0
  27. data/spec/unit/recipe_lookup_spec.rb +122 -0
  28. data/spec/unit/startup_spec.rb +283 -0
  29. data/spec/unit/target_host_spec.rb +231 -0
  30. data/spec/unit/target_resolver_spec.rb +380 -0
  31. data/spec/unit/telemeter/sender_spec.rb +140 -0
  32. data/spec/unit/telemeter_spec.rb +191 -0
  33. data/spec/unit/temp_cookbook_spec.rb +199 -0
  34. data/spec/unit/ui/error_printer_spec.rb +173 -0
  35. data/spec/unit/ui/terminal_spec.rb +109 -0
  36. data/spec/unit/version_spec.rb +31 -0
  37. data/warning.txt +3 -0
  38. metadata +34 -2
@@ -0,0 +1,70 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "spec_helper"
19
+ require "chef_apply/config"
20
+
21
+ RSpec.describe ChefApply::Config do
22
+ subject(:config) do
23
+ ChefApply::Config
24
+ end
25
+
26
+ before(:each) do
27
+ ChefApply::Config.reset
28
+ end
29
+
30
+ it "raises an error when trying to specify non-existing config location" do
31
+ expect { config.custom_location("/does/not/exist") }.to raise_error(RuntimeError, /No config file/)
32
+ end
33
+
34
+ it "should use default location by default" do
35
+ expect(config.using_default_location?).to eq(true)
36
+ end
37
+
38
+ context "when there is a custom config" do
39
+ let(:custom_config) { File.expand_path("../../fixtures/custom_config.toml", __FILE__) }
40
+
41
+ it "successfully loads the config" do
42
+ config.custom_location(custom_config)
43
+ expect(config.using_default_location?).to eq(false)
44
+ expect(config.exist?).to eq(true)
45
+ config.load
46
+ expect(config.telemetry.dev).to eq(true)
47
+ end
48
+ end
49
+ describe "#load" do
50
+ before do
51
+ expect(subject).to receive(:exist?).and_return(exists)
52
+ end
53
+
54
+ context "when the config file exists" do
55
+ let(:exists) { true }
56
+ it "loads the file" do
57
+ expect(subject).to receive(:from_file)
58
+ subject.load
59
+ end
60
+ end
61
+
62
+ context "when the config file does not exist" do
63
+ let(:exists) { false }
64
+ it "does not try to load the file" do
65
+ expect(subject).to_not receive(:from_file)
66
+ subject.load
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,103 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "spec_helper"
19
+ require "chef_apply/errors/ccr_failure_mapper"
20
+
21
+ RSpec.describe ChefApply::Errors::CCRFailureMapper do
22
+ let(:cause_line) { nil }
23
+ let(:resource) { "apt_package" }
24
+ let(:params) do
25
+ { resource: resource, resource_name: "a-test-thing",
26
+ stderr: "an error", stdout: "other output" }
27
+ end
28
+ subject { ChefApply::Errors::CCRFailureMapper.new(cause_line, params) }
29
+
30
+ describe "#exception_args_from_cause" do
31
+ context "when resource properties have valid names but invalid values" do
32
+ context "and the property is 'action'" do
33
+ let(:cause_line) { "Chef::Exceptions::ValidationFailed: Option action must be equal to one of: nothing, install, upgrade, remove, purge, reconfig, lock, unlock! You passed :marve." }
34
+ it "returns a correct CHEFCCR003" do
35
+ expect(subject.exception_args_from_cause).to eq(
36
+ ["CHEFCCR003", "marve",
37
+ "nothing, install, upgrade, remove, purge, reconfig, lock, unlock"]
38
+ )
39
+ end
40
+ end
41
+
42
+ context "and the property is something else" do
43
+ context "and details are available" do
44
+ let(:cause_line) { "Chef::Exceptions::ValidationFailed: Option force must be a kind of [TrueClass, FalseClass]! You passed \"purle\"." }
45
+ it "returns a correct CHEFCCR004 when details are available" do
46
+ expect(subject.exception_args_from_cause).to eq(
47
+ ["CHEFCCR004",
48
+ "Option force must be a kind of [TrueClass, FalseClass]! You passed \"purle\"."])
49
+ end
50
+ end
51
+ context "And less detail is available" do
52
+ let(:cause_line) { "Chef::Exceptions::User: linux_user[marc] ((chef-client cookbook)::(chef-client recipe) line 1) had an error: Chef::Exceptions::User: Couldn't lookup integer GID for group name blah" }
53
+ it "returns a correct CHEFCCR002" do
54
+ expect(subject.exception_args_from_cause).to eq(
55
+ ["CHEFCCR002", "Couldn't lookup integer GID for group name blah"])
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ context "when resource is not a known Chef resource" do
62
+ let(:cause_line) { "NoMethodError: undefined method `useraaa' for cookbook: (chef-client cookbook), recipe: (chef-client recipe) :Chef::Recipe" }
63
+ let(:resource) { "useraaa" }
64
+ it "returns a correct CHEFCCR005" do
65
+ expect(subject.exception_args_from_cause).to eq(["CHEFCCR005", resource])
66
+ end
67
+ end
68
+
69
+ context "when a resource property does not exist for the given resource" do
70
+ let(:cause_line) { "NoMethodError: undefined method `badresourceprop' for Chef::Resource::User::LinuxUser" }
71
+ it "returns a correct CHEFCCR006 " do
72
+ expect(subject.exception_args_from_cause).to eq(
73
+ ["CHEFCCR006", "badresourceprop", "Chef::Resource::User::LinuxUser"])
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "#raise_mapped_exception!" do
79
+ context "when no cause is provided" do
80
+ let(:cause_line) { nil }
81
+ it "raises a RemoteChefRunFailedToResolveError" do
82
+ expect { subject.raise_mapped_exception! }.to raise_error(ChefApply::Errors::CCRFailureMapper::RemoteChefRunFailedToResolveError)
83
+
84
+ end
85
+ end
86
+
87
+ context "when a cause is provided" do
88
+ context "but can't resolve it" do
89
+ let(:cause_line) { "unparseable mess" }
90
+ it "raises a RemoteChefClientRunFailedUnknownReason" do
91
+ expect { subject.raise_mapped_exception! }.to raise_error(ChefApply::Errors::CCRFailureMapper::RemoteChefClientRunFailedUnknownReason)
92
+ end
93
+ end
94
+
95
+ context "and can resolve the cause" do
96
+ let(:cause_line) { "NoMethodError: undefined method `badresourceprop' for Chef::Resource::User::LinuxUser" }
97
+ it "raises a RemoteChefClientRunFailed" do
98
+ expect { subject.raise_mapped_exception! }.to raise_error(ChefApply::Errors::CCRFailureMapper::RemoteChefClientRunFailed)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "chef_apply/file_fetcher"
19
+ require "spec_helper"
20
+
21
+ RSpec.describe ChefApply::FileFetcher do
22
+ let(:expected_local_location) { File.join(ChefApply::Config.cache.path, "example.txt") }
23
+ subject { ChefApply::FileFetcher }
24
+ describe ".fetch" do
25
+ it "returns the local path when the file is cached" do
26
+ allow(FileUtils).to receive(:mkdir)
27
+ expect(File).to receive(:exist?).with(expected_local_location).and_return(true)
28
+ result = subject.fetch("https://example.com/example.txt")
29
+ expect(result).to eq expected_local_location
30
+ end
31
+
32
+ it "returns the local path when the file is fetched" do
33
+ allow(FileUtils).to receive(:mkdir)
34
+ expect(File).to receive(:exist?).with(expected_local_location).and_return(false)
35
+ expect(subject).to receive(:download_file)
36
+ result = subject.fetch("https://example.com/example.txt")
37
+ expect(result).to eq expected_local_location
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,2 @@
1
+ Host: host1 Error: CHEFUPL005: Uploading policy bundle to target failed.
2
+ Host: host2 : An unexpected error has occurred: Hello World
@@ -0,0 +1,37 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "spec_helper"
19
+ require "chef_apply/log"
20
+
21
+ RSpec.describe ChefApply::Log do
22
+ Log = ChefApply::Log
23
+ let(:output) { StringIO.new }
24
+
25
+ before do
26
+ Log.setup output, :debug
27
+ end
28
+
29
+ after do
30
+ Log.setup "/dev/null", :error
31
+ end
32
+
33
+ it "correctly logs to stdout" do
34
+ Log.debug("test")
35
+ expect(output.string).to match(/DEBUG: test$/)
36
+ end
37
+ end
@@ -0,0 +1,122 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2018 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "spec_helper"
19
+ require "chef_apply/recipe_lookup"
20
+ require "chef/exceptions"
21
+ require "chef/cookbook/cookbook_version_loader"
22
+ require "chef/cookbook_version"
23
+ require "chef/cookbook_loader"
24
+
25
+ RSpec.describe ChefApply::RecipeLookup do
26
+ let(:repo_path) { "repo_path" }
27
+ subject(:rp) { ChefApply::RecipeLookup.new([repo_path]) }
28
+ VL = Chef::Cookbook::CookbookVersionLoader
29
+ let(:version_loader) { instance_double(VL) }
30
+ let(:cookbook_version) { instance_double(Chef::CookbookVersion, root_dir: "dir", name: "name") }
31
+ let(:cookbook_loader) { instance_double(Chef::CookbookLoader, load_cookbooks_without_shadow_warning: nil) }
32
+
33
+ describe "#split" do
34
+ it "splits a customer provided specifier into a cookbook part and possible recipe part" do
35
+ expect(rp.split("/some/path")).to eq(%w{/some/path})
36
+ expect(rp.split("cookbook::recipe")).to eq(%w{cookbook recipe})
37
+ end
38
+ end
39
+
40
+ describe "#load_cookbook" do
41
+ context "when a directory is provided" do
42
+ let(:recipe_specifier) { "/some/directory" }
43
+ let(:default_recipe) { File.join(recipe_specifier, "default.rb") }
44
+ let(:recipes_by_name) { { "default" => default_recipe } }
45
+ before do
46
+ expect(File).to receive(:directory?).with(recipe_specifier).and_return(true)
47
+ expect(VL).to receive(:new).with(recipe_specifier).and_return(version_loader)
48
+ end
49
+
50
+ it "loads the cookbook and returns the path to the default recipe" do
51
+ expect(version_loader).to receive(:load!)
52
+ expect(version_loader).to receive(:cookbook_version).and_return(cookbook_version)
53
+ expect(rp.load_cookbook(recipe_specifier)).to eq(cookbook_version)
54
+ end
55
+
56
+ context "the directory is not a cookbook" do
57
+ it "raise an InvalidCookbook error" do
58
+ expect(version_loader).to receive(:load!).and_raise(Chef::Exceptions::CookbookNotFoundInRepo.new)
59
+ expect { rp.load_cookbook(recipe_specifier) }.to raise_error(ChefApply::RecipeLookup::InvalidCookbook)
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when a cookbook name is provided" do
65
+ let(:recipe_specifier) { "cb" }
66
+ before do
67
+ expect(File).to receive(:directory?).with(recipe_specifier).and_return(false)
68
+ expect(Chef::CookbookLoader).to receive(:new).and_return(cookbook_loader)
69
+ end
70
+
71
+ context "and a cookbook in the cookbook repository exists with that name" do
72
+ it "returns the default cookbook" do
73
+ expect(cookbook_loader).to receive(:[]).with(recipe_specifier).and_return(cookbook_version)
74
+ expect(rp.load_cookbook(recipe_specifier)).to eq(cookbook_version)
75
+ end
76
+ end
77
+
78
+ context "and a cookbook exists but it is invalid" do
79
+ it "raises an InvalidCookbook error" do
80
+ expect(cookbook_loader).to receive(:[]).with(recipe_specifier).and_raise(Chef::Exceptions::CookbookNotFoundInRepo.new())
81
+ expect(File).to receive(:directory?).with(File.join(repo_path, recipe_specifier)).and_return(true)
82
+ expect { rp.load_cookbook(recipe_specifier) }.to raise_error(ChefApply::RecipeLookup::InvalidCookbook)
83
+ end
84
+ end
85
+
86
+ context "and a cookbook does not exist" do
87
+ it "raises an CookbookNotFound error" do
88
+ expect(cookbook_loader).to receive(:[]).with(recipe_specifier).and_raise(Chef::Exceptions::CookbookNotFoundInRepo.new())
89
+ expect(File).to receive(:directory?).with(File.join(repo_path, recipe_specifier)).and_return(false)
90
+ expect { rp.load_cookbook(recipe_specifier) }.to raise_error(ChefApply::RecipeLookup::CookbookNotFound)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "#find_recipe" do
97
+ let(:recipe) { double("recipe") }
98
+
99
+ context "no recipe is specified" do
100
+ it "finds a default recipe" do
101
+ expect(cookbook_version).to receive(:recipe_filenames_by_name).and_return({ "default" => recipe })
102
+ expect(rp.find_recipe(cookbook_version)).to eq(recipe)
103
+ end
104
+ it "when there is no default recipe it raises a NoDefaultRecipe error" do
105
+ expect(cookbook_version).to receive(:recipe_filenames_by_name).and_return({})
106
+ expect { rp.find_recipe(cookbook_version) }.to raise_error(ChefApply::RecipeLookup::NoDefaultRecipe)
107
+ end
108
+ end
109
+
110
+ context "a recipe is specified" do
111
+ let(:desired_recipe) { "a_recipe" }
112
+ it "finds the specified recipe" do
113
+ expect(cookbook_version).to receive(:recipe_filenames_by_name).and_return({ desired_recipe => recipe })
114
+ expect(rp.find_recipe(cookbook_version, desired_recipe)).to eq(recipe)
115
+ end
116
+ it "when there is no recipe with that name it raises a RecipeNotFound error" do
117
+ expect(cookbook_version).to receive(:recipe_filenames_by_name).and_return({})
118
+ expect { rp.find_recipe(cookbook_version, desired_recipe) }.to raise_error(ChefApply::RecipeLookup::RecipeNotFound)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,283 @@
1
+ require "chef_apply/startup"
2
+ require "chef_apply/text"
3
+ require "chef_apply/ui/terminal"
4
+
5
+ RSpec.describe ChefApply::Startup do
6
+ let(:argv) { [] }
7
+ let(:telemetry) { ChefApply::Telemeter.instance }
8
+ subject do
9
+ ChefApply::Startup.new(argv)
10
+ end
11
+ before do
12
+ allow(ChefApply::UI::Terminal).to receive(:init)
13
+ end
14
+
15
+ after do
16
+ ChefApply::Config.reset
17
+ end
18
+
19
+ describe "#initalize" do
20
+ it "initializes the terminal" do
21
+ expect_any_instance_of(ChefApply::Startup).to receive(:init_terminal)
22
+ ChefApply::Startup.new([])
23
+ end
24
+ end
25
+
26
+ describe "#run" do
27
+ it "performs ordered startup tasks and invokes the CLI" do
28
+ ordered_messages = [:first_run_tasks,
29
+ :setup_workstation_user_directories,
30
+ :setup_error_handling,
31
+ :load_config,
32
+ :setup_logging,
33
+ :start_telemeter_upload,
34
+ :start_chef_apply]
35
+ ordered_messages.each do |msg|
36
+ expect(subject).to receive(msg).ordered
37
+ end
38
+ subject.run()
39
+ end
40
+ context "when errors happen" do
41
+ let(:error) { nil }
42
+ let(:error_text) { ChefApply::Text.cli.error }
43
+ before do
44
+ # Force the error to happen in first_run_tasks, since it's always... well, first.
45
+ expect(subject).to receive(:first_run_tasks).and_raise(error)
46
+ end
47
+
48
+ context "when an UnknownConfigOptionError is raised" do
49
+ let(:bad_path) { "bad/path" }
50
+ let(:bad_option) { "bad_option" }
51
+
52
+ context "and it matches the expected text form" do
53
+ let(:error) { Mixlib::Config::UnknownConfigOptionError.new("unsupported config value #{bad_option}.") }
54
+ it "shows the correct error" do
55
+ expected_text = error_text.invalid_config_key(bad_option, ChefApply::Config.location)
56
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
57
+ subject.run
58
+ end
59
+ end
60
+
61
+ context "and it does not match the expeted text form" do
62
+ let(:msg) { "something bad happened" }
63
+ let(:error) { Mixlib::Config::UnknownConfigOptionError.new(msg) }
64
+ it "shows the correct error" do
65
+ expected_text = error_text.unknown_config_error(msg, ChefApply::Config.location)
66
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
67
+ subject.run
68
+ end
69
+ end
70
+ end
71
+
72
+ context "when a ConfigPathInvalid is raised" do
73
+ let(:bad_path) { "bad/path" }
74
+ let(:error) { ChefApply::Startup::ConfigPathInvalid.new(bad_path) }
75
+
76
+ it "shows the correct error" do
77
+ expected_text = error_text.bad_config_file(bad_path)
78
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
79
+ subject.run
80
+ end
81
+ end
82
+
83
+ context "when a ConfigPathNotProvided is raised" do
84
+ let(:error) { ChefApply::Startup::ConfigPathNotProvided.new }
85
+
86
+ it "shows the correct error" do
87
+ expected_text = error_text.missing_config_path
88
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
89
+ subject.run
90
+ end
91
+ end
92
+
93
+ context "when a Tomlrb::ParserError is raised" do
94
+ let(:msg) { "Parse failed." }
95
+ let(:error) { Tomlrb::ParseError.new(msg) }
96
+
97
+ it "shows the correct error" do
98
+ expected_text = error_text.unknown_config_error(msg, ChefApply::Config.location)
99
+ expect(ChefApply::UI::Terminal).to receive(:output).with(expected_text)
100
+ subject.run
101
+ end
102
+ end
103
+ end
104
+ end
105
+ describe "#init_terminal" do
106
+ it "initializees the terminal for stdout" do
107
+ expect(ChefApply::UI::Terminal).to receive(:init).with($stdout)
108
+ subject.init_terminal
109
+ end
110
+ end
111
+ describe "#first_run_tasks" do
112
+ let(:first_run_complete) { true }
113
+ before do
114
+ allow(Dir).to receive(:exist?).with(ChefApply::Config::WS_BASE_PATH).and_return first_run_complete
115
+ end
116
+
117
+ context "when first run has already occurred" do
118
+ let(:first_run_complete) { true }
119
+ it "returns without taking action" do
120
+ expect(subject).to_not receive(:create_default_config)
121
+ expect(subject).to_not receive(:setup_telemetry)
122
+ subject.first_run_tasks
123
+ end
124
+ end
125
+ context "when first run has not already occurred" do
126
+ let(:first_run_complete) { false }
127
+ it "Performs required first-run tasks" do
128
+ expect(subject).to receive(:create_default_config)
129
+ expect(subject).to receive(:setup_telemetry)
130
+ subject.first_run_tasks
131
+ end
132
+ end
133
+ end
134
+
135
+ describe "#create_default_config" do
136
+ it "touches the configuration file to create it and notifies that it has done so" do
137
+ expected_config_path = ChefApply::Config.default_location
138
+ expected_message = ChefApply::Text.cli.creating_config(expected_config_path)
139
+ expect(ChefApply::UI::Terminal).to receive(:output).
140
+ with(expected_message)
141
+ expect(ChefApply::UI::Terminal).to receive(:output).
142
+ with("")
143
+ expect(FileUtils).to receive(:touch).
144
+ with(expected_config_path)
145
+ subject.create_default_config
146
+
147
+ end
148
+ end
149
+
150
+ describe "#setup_telemetry" do
151
+ let(:mock_guid) { "1234" }
152
+ it "sets up a telemetry installation id and notifies the operator that telemetry is enabled" do
153
+ expect(SecureRandom).to receive(:uuid).and_return(mock_guid)
154
+ expect(File).to receive(:write).
155
+ with(ChefApply::Config.telemetry_installation_identifier_file, mock_guid)
156
+ subject.setup_telemetry
157
+ end
158
+ end
159
+
160
+ describe "#start_telemeter_upload" do
161
+ it "launches telemetry uploads" do
162
+ expect(ChefApply::Telemeter::Sender).to receive(:start_upload_thread)
163
+ subject.start_telemeter_upload
164
+ end
165
+ end
166
+
167
+ describe "setup_workstation_user_directories" do
168
+ it "creates the required chef-workstation directories in HOME" do
169
+ expect(FileUtils).to receive(:mkdir_p).with(ChefApply::Config::WS_BASE_PATH)
170
+ expect(FileUtils).to receive(:mkdir_p).with(ChefApply::Config.base_log_directory)
171
+ expect(FileUtils).to receive(:mkdir_p).with(ChefApply::Config.telemetry_path)
172
+ subject.setup_workstation_user_directories
173
+ end
174
+ end
175
+
176
+ describe "#custom_config_path" do
177
+ context "when a custom config path is not provided as an option" do
178
+ let(:args) { [] }
179
+ it "returns nil" do
180
+ expect(subject.custom_config_path).to be_nil
181
+ end
182
+ end
183
+
184
+ context "when a --config-path is provided" do
185
+ context "but the actual path parameter is not provided" do
186
+ let(:argv) { %w{--config-path} }
187
+ it "raises ConfigPathNotProvided" do
188
+ expect { subject.custom_config_path }.to raise_error(ChefApply::Startup::ConfigPathNotProvided)
189
+ end
190
+ end
191
+
192
+ context "and the path is provided" do
193
+ let(:path) { "/mock/file.toml" }
194
+ let(:argv) { ["--config-path", path] }
195
+
196
+ context "but the path is not a file" do
197
+ before do
198
+ allow(File).to receive(:file?).with(path).and_return false
199
+ end
200
+ it "raises an error ConfigPathInvalid" do
201
+ expect { subject.custom_config_path }.to raise_error(ChefApply::Startup::ConfigPathInvalid)
202
+ end
203
+ end
204
+
205
+ context "and the path exists and is a valid file" do
206
+ before do
207
+ allow(File).to receive(:file?).with(path).and_return true
208
+ end
209
+
210
+ context "but it is not readable" do
211
+ before do
212
+ allow(File).to receive(:readable?).with(path).and_return false
213
+ end
214
+ it "raises an error ConfigPathInvalid" do
215
+ expect { subject.custom_config_path }.to raise_error(ChefApply::Startup::ConfigPathInvalid)
216
+ end
217
+ end
218
+
219
+ context "and it is readable" do
220
+ before do
221
+ allow(File).to receive(:readable?).with(path).and_return true
222
+ end
223
+ it "returns the custom path" do
224
+ expect(subject.custom_config_path).to eq path
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ describe "#load_config" do
233
+ context "when a custom configuraton path is provided" do
234
+ let(:config_path) { nil }
235
+ it "loads the config at the custom path" do
236
+ expect(subject).to receive(:custom_config_path).and_return config_path
237
+ expect(ChefApply::Config).to receive(:custom_location).with config_path
238
+ expect(ChefApply::Config).to receive(:load)
239
+ subject.load_config
240
+ end
241
+ let(:config_path) { "/tmp/workstation-mock-config.toml" }
242
+ end
243
+
244
+ context "when no custom configuration path is provided" do
245
+ let(:config_path) { nil }
246
+ it "loads it at the default configuration path" do
247
+ expect(subject).to receive(:custom_config_path).and_return config_path
248
+ expect(ChefApply::Config).not_to receive(:custom_location)
249
+ expect(ChefApply::Config).to receive(:load)
250
+ subject.load_config
251
+ end
252
+ end
253
+
254
+ end
255
+
256
+ describe "#setup_logging" do
257
+ let(:log_path) { "/tmp/logs" }
258
+ let(:log_level) { :debug }
259
+ before do
260
+ ChefApply::Config.log.location = log_path
261
+ ChefApply::Config.log.level = log_level
262
+ end
263
+
264
+ it "sets up the logging for ChefApply and Chef" do
265
+ expect(ChefApply::Log).to receive(:setup).
266
+ with(log_path, log_level)
267
+ expect(Chef::Log).to receive(:init).
268
+ with(ChefApply::Log)
269
+ subject.setup_logging
270
+ expect(ChefConfig.logger).to eq(ChefApply::Log)
271
+ end
272
+ end
273
+
274
+ describe "#start_chef_apply" do
275
+ let(:argv) { %w{some arguments} }
276
+ it "runs ChefApply::CLI and passes along arguments it received" do
277
+ run_double = instance_double(ChefApply::CLI)
278
+ expect(ChefApply::CLI).to receive(:new).with(argv).and_return(run_double)
279
+ expect(run_double).to receive(:run)
280
+ subject.start_chef_apply
281
+ end
282
+ end
283
+ end