chef-apply 0.1.17 → 0.1.18
Sign up to get free protection for your applications and to get access to all the features.
- 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
|