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.
- checksums.yaml +4 -4
- data/Gemfile +0 -7
- data/Gemfile.lock +176 -84
- data/chef-apply.gemspec +2 -2
- data/lib/chef_apply/version.rb +1 -1
- data/spec/fixtures/custom_config.toml +2 -0
- data/spec/integration/chef-run_spec.rb +41 -0
- data/spec/integration/fixtures/chef_help.out +69 -0
- data/spec/integration/fixtures/chef_version.out +1 -0
- data/spec/integration/spec_helper.rb +55 -0
- data/spec/spec_helper.rb +114 -0
- data/spec/support/matchers/output_to_terminal.rb +36 -0
- data/spec/unit/action/base_spec.rb +89 -0
- data/spec/unit/action/converge_target_spec.rb +292 -0
- data/spec/unit/action/generate_local_policy_spec.rb +114 -0
- data/spec/unit/action/generate_temp_cookbook_spec.rb +75 -0
- data/spec/unit/action/install_chef/base_spec.rb +234 -0
- data/spec/unit/action/install_chef_spec.rb +69 -0
- data/spec/unit/cli/options_spec.rb +75 -0
- data/spec/unit/cli/validation_spec.rb +78 -0
- data/spec/unit/cli_spec.rb +440 -0
- data/spec/unit/config_spec.rb +70 -0
- data/spec/unit/errors/ccr_failure_mapper_spec.rb +103 -0
- data/spec/unit/file_fetcher_spec.rb +40 -0
- data/spec/unit/fixtures/multi-error.out +2 -0
- data/spec/unit/log_spec.rb +37 -0
- data/spec/unit/recipe_lookup_spec.rb +122 -0
- data/spec/unit/startup_spec.rb +283 -0
- data/spec/unit/target_host_spec.rb +231 -0
- data/spec/unit/target_resolver_spec.rb +380 -0
- data/spec/unit/telemeter/sender_spec.rb +140 -0
- data/spec/unit/telemeter_spec.rb +191 -0
- data/spec/unit/temp_cookbook_spec.rb +199 -0
- data/spec/unit/ui/error_printer_spec.rb +173 -0
- data/spec/unit/ui/terminal_spec.rb +109 -0
- data/spec/unit/version_spec.rb +31 -0
- data/warning.txt +3 -0
- 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,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
|