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,69 @@
|
|
1
|
+
Chef Run is a tool to execute ad-hoc tasks using Chef.
|
2
|
+
|
3
|
+
chef-run <TARGET[S]> <RESOURCE> <RESOURCE_NAME> [PROPERTIES] [FLAGS]
|
4
|
+
|
5
|
+
Runs a single <RESOURCE> on the specified <TARGET[S]>.
|
6
|
+
[PROPERTIES] should be specified as key=value.
|
7
|
+
|
8
|
+
For example:
|
9
|
+
|
10
|
+
chef-run web01 service nginx action=restart
|
11
|
+
chef-run web01,web02 service nginx action=restart
|
12
|
+
chef-run web0[1:2] service nginx action=restart
|
13
|
+
|
14
|
+
chef-run <TARGET[S]> <RECIPE> [FLAGS]
|
15
|
+
|
16
|
+
Runs a single recipe located at <RECIPE> on the specified <TARGET[S]>.
|
17
|
+
|
18
|
+
For example:
|
19
|
+
|
20
|
+
chef-run web01 path/to/cookbook/recipe.rb
|
21
|
+
chef-run web01,web02 path/to/cookbook
|
22
|
+
chef-run web0[1:2] cookbook_name
|
23
|
+
chef-run web01 cookbook_name::recipe_name
|
24
|
+
|
25
|
+
ARGUMENTS:
|
26
|
+
<TARGET[S]> The hosts or IPs to target. Can also be an SSH or WinRM URLs
|
27
|
+
in the form:
|
28
|
+
|
29
|
+
ssh://[USERNAME]@example.com[:PORT]
|
30
|
+
<RESOURCE> A Chef resource, such as 'user' or 'package'
|
31
|
+
<RESOURCE_NAME> The name, usually used to specify what 'thing' to set up with
|
32
|
+
the resource. For example, given resource 'user', 'name' would be
|
33
|
+
the name of the user you wanted to create.
|
34
|
+
<RECIPE> The recipe to converge. This can be provided as one of:
|
35
|
+
1. Full path to a recipe file
|
36
|
+
2. Cookbook name. First we check the working directory for this
|
37
|
+
cookbook, then we check in the chef repository path. If a
|
38
|
+
cookbook is found we run the default recipe.
|
39
|
+
3. This behaves similarly to 'cookbook name' above, but it also allows
|
40
|
+
you to specify which recipe to use from the cookbook.
|
41
|
+
|
42
|
+
FLAGS:
|
43
|
+
-c, --config PATH Location of config file. Default: $HOME/.chef-workstation/config.toml
|
44
|
+
--cookbook-repo-paths PATH Comma separated list of cookbook repository paths.
|
45
|
+
-h, --help Show help and usage for `chef-run`
|
46
|
+
-i, --identity-file PATH SSH identity file to use when connecting. Keys loaded into ssh-agent will also be used.
|
47
|
+
--[no-]install Install Chef client on the target host(s) if it is not installed.
|
48
|
+
This defaults to enabled - the installation will be performed
|
49
|
+
if there is no Chef client on the target(s).
|
50
|
+
--password <PASSWORD> Password to use for authentication to the target(s). The same
|
51
|
+
password will be used for all targets.
|
52
|
+
-p, --protocol <PROTOCOL> The protocol to use for connecting to targets.
|
53
|
+
The default is 'ssh', and it can be changed in config.toml by
|
54
|
+
setting 'connection.default_protocol' to a supported option.
|
55
|
+
--[no-]ssl Use SSL for WinRM. Current default: false
|
56
|
+
--[no-]ssl-verify Verify peer certificate when using SSL for WinRM
|
57
|
+
Use --ssl-no-verify when using SSL for WinRM and
|
58
|
+
the remote host is using a self-signed certificate.
|
59
|
+
Current default: true
|
60
|
+
--[no-]sudo Whether to use root permissions on the target. Default: true
|
61
|
+
--sudo-command <COMMAND> Command to use for administrative/root access. Defaults to 'sudo'.
|
62
|
+
--sudo-options 'OPTIONS...' Options to use with the sudo command. If there are multiple flags,
|
63
|
+
quote them. For example: --sudo-options '-H -P -s'
|
64
|
+
--sudo-password <PASSWORD> Password to use with the sudo command. This must be provided if
|
65
|
+
password is required for sudo on the target(s). The same sudo password
|
66
|
+
will be used for all targets.
|
67
|
+
--user <USER> Username to use for authentication to the target(s). The same
|
68
|
+
username will be used for all targets.
|
69
|
+
-v, --version Show the current version of Chef Run.
|
@@ -0,0 +1 @@
|
|
1
|
+
chef-run: $VERSION
|
@@ -0,0 +1,55 @@
|
|
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/startup"
|
19
|
+
require "chef_apply/version"
|
20
|
+
|
21
|
+
# Create the chef configuration directory and touch the config
|
22
|
+
# file.
|
23
|
+
# this makes sure our output doesn't include
|
24
|
+
# an extra line telling us that it's created,
|
25
|
+
# causing the first integration test to execute to fail on
|
26
|
+
# CI.
|
27
|
+
# TODO this is not ideal... let's look at
|
28
|
+
# testing the output correctly in both cases,
|
29
|
+
# possible forcing a specific test that will also create
|
30
|
+
# the directory to run first.
|
31
|
+
dir = File.join(Dir.home, ".chef-workstation")
|
32
|
+
conf = File.join(dir, "config.toml")
|
33
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
34
|
+
FileUtils.touch(conf) unless File.exist?(conf)
|
35
|
+
|
36
|
+
# Simple wrapper that runs the CLI and prevents it
|
37
|
+
# from aborting all tests with a SystemExit.
|
38
|
+
# We could shell out, but this will run a little faster as we
|
39
|
+
# accumulate more things to test - and will work better to get
|
40
|
+
# accurate simplecov coverage reporting.
|
41
|
+
# usage:
|
42
|
+
# expect {run_with_cli("blah")}.to output("blah").to_stdout
|
43
|
+
def run_cli_with(args)
|
44
|
+
ChefApply::Startup.new(args.split(" ")).run
|
45
|
+
rescue SystemExit
|
46
|
+
end
|
47
|
+
|
48
|
+
def fixture_content(name)
|
49
|
+
content = File.read(File.join("spec/integration/fixtures", "#{name}.out"))
|
50
|
+
# Replace $VERSION if present - this is updated automatically, so we can't include
|
51
|
+
# the literal version value in the fixture without
|
52
|
+
# having expeditor update it there too...
|
53
|
+
content.gsub("$VERSION", ChefApply::VERSION).
|
54
|
+
gsub("$HOME", Dir.home)
|
55
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,114 @@
|
|
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 "bundler/setup"
|
19
|
+
require "simplecov"
|
20
|
+
require "rspec/expectations"
|
21
|
+
require "support/matchers/output_to_terminal"
|
22
|
+
|
23
|
+
RemoteExecResult = Struct.new(:exit_status, :stdout, :stderr)
|
24
|
+
|
25
|
+
class ChefApply::MockReporter
|
26
|
+
def update(msg); ChefApply::UI::Terminal.output msg; end
|
27
|
+
|
28
|
+
def success(msg); ChefApply::UI::Terminal.output "SUCCESS: #{msg}"; end
|
29
|
+
|
30
|
+
def error(msg); ChefApply::UI::Terminal.output "FAILURE: #{msg}"; end
|
31
|
+
end
|
32
|
+
|
33
|
+
RSpec::Matchers.define :exit_with_code do |expected_code|
|
34
|
+
actual_code = nil
|
35
|
+
match do |block|
|
36
|
+
begin
|
37
|
+
block.call
|
38
|
+
rescue SystemExit => e
|
39
|
+
actual_code = e.status
|
40
|
+
end
|
41
|
+
actual_code && actual_code == expected_code
|
42
|
+
end
|
43
|
+
|
44
|
+
failure_message do |block|
|
45
|
+
result = actual.nil? ? " did not call exit" : " called exit(#{actual_code})"
|
46
|
+
"expected exit(#{expected_code}) but it #{result}."
|
47
|
+
end
|
48
|
+
|
49
|
+
failure_message_when_negated do |block|
|
50
|
+
"expected exit(#{expected_code}) but it did."
|
51
|
+
end
|
52
|
+
|
53
|
+
description do
|
54
|
+
"expect exit(#{expected_code})"
|
55
|
+
end
|
56
|
+
|
57
|
+
supports_block_expectations do
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
# TODO would read better to make this a custom matcher.
|
62
|
+
# Simulates a recursive string lookup on the Text object
|
63
|
+
#
|
64
|
+
# assert_string_lookup("tree.tree.tree.leaf", "a returned string")
|
65
|
+
# TODO this can be more cleanly expressed as a custom matcher...
|
66
|
+
def assert_string_lookup(key, retval = "testvalue")
|
67
|
+
it "should look up string #{key}" do
|
68
|
+
top_level_method, *call_seq = key.split(".")
|
69
|
+
terminal_method = call_seq.pop
|
70
|
+
tmock = double()
|
71
|
+
# Because ordering is important
|
72
|
+
# (eg calling errors.hello is different from hello.errors),
|
73
|
+
# we need to add this individually instead of using
|
74
|
+
# `receive_messages`, which doesn't appear to give a way to
|
75
|
+
# guarantee ordering
|
76
|
+
expect(ChefApply::Text).to receive(top_level_method).
|
77
|
+
and_return(tmock)
|
78
|
+
call_seq.each do |m|
|
79
|
+
expect(tmock).to receive(m).ordered.and_return(tmock)
|
80
|
+
end
|
81
|
+
expect(tmock).to receive(terminal_method).
|
82
|
+
ordered.and_return(retval)
|
83
|
+
subject.call
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
RSpec.configure do |config|
|
88
|
+
# Enable flags like --only-failures and --next-failure
|
89
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
90
|
+
config.run_all_when_everything_filtered = true
|
91
|
+
config.filter_run :focus
|
92
|
+
|
93
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
94
|
+
config.disable_monkey_patching!
|
95
|
+
|
96
|
+
config.expect_with :rspec do |c|
|
97
|
+
c.syntax = :expect
|
98
|
+
end
|
99
|
+
|
100
|
+
config.mock_with :rspec do |mocks|
|
101
|
+
mocks.verify_partial_doubles = true
|
102
|
+
end
|
103
|
+
|
104
|
+
config.before(:all) do
|
105
|
+
ChefApply::Log.setup "/dev/null", :error
|
106
|
+
ChefApply::UI::Terminal.init(File.open("/dev/null", "w"))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
if ENV["CIRCLE_ARTIFACTS"]
|
111
|
+
dir = File.join(ENV["CIRCLE_ARTIFACTS"], "coverage")
|
112
|
+
SimpleCov.coverage_dir(dir)
|
113
|
+
end
|
114
|
+
SimpleCov.start
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "rspec/matchers/built_in/output"
|
2
|
+
require "chef_apply/ui/terminal"
|
3
|
+
|
4
|
+
# Custom behavior for the builtin output matcher
|
5
|
+
# to allow it to handle to_terminal, which integrates
|
6
|
+
# with our UI::Terminal interface.
|
7
|
+
module RSpec
|
8
|
+
module Matchers
|
9
|
+
module BuiltIn
|
10
|
+
class Output < BaseMatcher
|
11
|
+
# @api private
|
12
|
+
# Provides the implementation for `output`.
|
13
|
+
# Not intended to be instantiated directly.
|
14
|
+
def to_terminal
|
15
|
+
@stream_capturer = CaptureTerminal
|
16
|
+
self
|
17
|
+
end
|
18
|
+
module CaptureTerminal
|
19
|
+
def self.name
|
20
|
+
"terminal"
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.capture(block)
|
24
|
+
captured_stream = StringIO.new
|
25
|
+
original_stream = ::ChefApply::UI::Terminal.location
|
26
|
+
::ChefApply::UI::Terminal.location = captured_stream
|
27
|
+
block.call
|
28
|
+
captured_stream.string
|
29
|
+
ensure
|
30
|
+
::ChefApply::UI::Terminal.location = original_stream
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,89 @@
|
|
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/action/base"
|
20
|
+
require "chef_apply/telemeter"
|
21
|
+
require "chef_apply/target_host"
|
22
|
+
|
23
|
+
RSpec.describe ChefApply::Action::Base do
|
24
|
+
let(:family) { "windows" }
|
25
|
+
let(:target_host) do
|
26
|
+
p = double("platform", family: family)
|
27
|
+
instance_double(ChefApply::TargetHost, platform: p)
|
28
|
+
end
|
29
|
+
let(:opts) do
|
30
|
+
{ target_host: target_host,
|
31
|
+
other: "something-else" } end
|
32
|
+
subject(:action) { ChefApply::Action::Base.new(opts) }
|
33
|
+
|
34
|
+
context "#initialize" do
|
35
|
+
it "properly initializes exposed attr readers" do
|
36
|
+
expect(action.target_host).to eq target_host
|
37
|
+
expect(action.config).to eq({ other: "something-else" })
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#run" do
|
42
|
+
it "runs the underlying action, capturing timing via telemetry" do
|
43
|
+
expect(ChefApply::Telemeter).to receive(:timed_action_capture).with(subject).and_yield
|
44
|
+
expect(action).to receive(:perform_action)
|
45
|
+
action.run
|
46
|
+
end
|
47
|
+
|
48
|
+
it "invokes an action handler when actions occur and a handler is provided" do
|
49
|
+
@run_action = nil
|
50
|
+
@args = nil
|
51
|
+
expect(ChefApply::Telemeter).to receive(:timed_action_capture).with(subject).and_yield
|
52
|
+
expect(action).to receive(:perform_action) { action.notify(:test_success, "some arg", "some other arg") }
|
53
|
+
action.run { |action, args| @run_action = action; @args = args }
|
54
|
+
expect(@run_action).to eq :test_success
|
55
|
+
expect(@args).to eq ["some arg", "some other arg"]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
shared_examples "check path fetching" do
|
60
|
+
[:chef_client, :cache_path, :read_chef_report, :delete_chef_report, :tempdir, :mktemp, :delete_folder].each do |path|
|
61
|
+
it "correctly returns path #{path}" do
|
62
|
+
expect(action.send(path)).to be_a(String)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "when connecting to a windows target" do
|
68
|
+
include_examples "check path fetching"
|
69
|
+
|
70
|
+
it "correctly returns chef run string" do
|
71
|
+
expect(action.run_chef("a", "b", "c")).to eq(
|
72
|
+
"Set-Location -Path a; " \
|
73
|
+
"chef-client -z --config b --recipe-url c | Out-Null; " \
|
74
|
+
"Set-Location C:/; " \
|
75
|
+
"exit $LASTEXITCODE"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "when connecting to a non-windows target" do
|
81
|
+
let(:family) { "linux" }
|
82
|
+
include_examples "check path fetching"
|
83
|
+
|
84
|
+
it "correctly returns chef run string" do
|
85
|
+
expect(action.run_chef("a", "b", "c")).to eq("bash -c 'cd a; chef-client -z --config a/b --recipe-url a/c'")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,292 @@
|
|
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/action/converge_target"
|
20
|
+
require "chef_apply/target_host"
|
21
|
+
require "chef_apply/errors/ccr_failure_mapper"
|
22
|
+
require "chef_apply/temp_cookbook"
|
23
|
+
|
24
|
+
RSpec.describe ChefApply::Action::ConvergeTarget do
|
25
|
+
let(:archive) { "archive.tgz" }
|
26
|
+
let(:target_host) do
|
27
|
+
p = double("platform", family: "windows")
|
28
|
+
instance_double(ChefApply::TargetHost, platform: p)
|
29
|
+
end
|
30
|
+
let(:local_policy_path) { "/local/policy/path/archive.tgz" }
|
31
|
+
let(:opts) { { target_host: target_host, local_policy_path: local_policy_path } }
|
32
|
+
subject(:action) { ChefApply::Action::ConvergeTarget.new(opts) }
|
33
|
+
|
34
|
+
describe "#create_remote_policy" do
|
35
|
+
let(:remote_folder) { "/tmp/foo" }
|
36
|
+
let(:remote_archive) { File.join(remote_folder, File.basename(archive)) }
|
37
|
+
|
38
|
+
before do
|
39
|
+
end
|
40
|
+
|
41
|
+
it "pushes it to the remote machine" do
|
42
|
+
expect(target_host).to receive(:upload_file).with(local_policy_path, remote_archive)
|
43
|
+
expect(subject.create_remote_policy(local_policy_path, remote_folder)).to eq(remote_archive)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "raises an error if the upload fails" do
|
47
|
+
expect(target_host).to receive(:upload_file).with(local_policy_path, remote_archive).and_raise("foo")
|
48
|
+
err = ChefApply::Action::ConvergeTarget::PolicyUploadFailed
|
49
|
+
expect { subject.create_remote_policy(local_policy_path, remote_folder) }.to raise_error(err)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#create_remote_config" do
|
54
|
+
|
55
|
+
@closed = false # tempfile close indicator
|
56
|
+
let(:remote_folder) { "/tmp/foo" }
|
57
|
+
let(:remote_config) { "#{remote_folder}/workstation.rb" }
|
58
|
+
# TODO - mock this, I think we're leaving things behind in /tmp in test runs.
|
59
|
+
let!(:local_tempfile) { Tempfile.new }
|
60
|
+
|
61
|
+
it "pushes it to the remote machine" do
|
62
|
+
expect(Tempfile).to receive(:new).and_return(local_tempfile)
|
63
|
+
expect(target_host).to receive(:upload_file).with(local_tempfile.path, remote_config)
|
64
|
+
expect(subject.create_remote_config(remote_folder)).to eq(remote_config)
|
65
|
+
# ensure the tempfile is deleted locally
|
66
|
+
expect(local_tempfile.closed?).to eq(true)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "raises an error if the upload fails" do
|
70
|
+
expect(Tempfile).to receive(:new).and_return(local_tempfile)
|
71
|
+
expect(target_host).to receive(:upload_file).with(local_tempfile.path, remote_config).and_raise("foo")
|
72
|
+
err = ChefApply::Action::ConvergeTarget::ConfigUploadFailed
|
73
|
+
expect { subject.create_remote_config(remote_folder) }.to raise_error(err)
|
74
|
+
# ensure the tempfile is deleted locally
|
75
|
+
expect(local_tempfile.closed?).to eq(true)
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "when data_collector is set in config" do
|
79
|
+
before do
|
80
|
+
ChefApply::Config.data_collector.url = "dc.url"
|
81
|
+
ChefApply::Config.data_collector.token = "dc.token"
|
82
|
+
end
|
83
|
+
|
84
|
+
after do
|
85
|
+
ChefApply::Config.reset
|
86
|
+
end
|
87
|
+
|
88
|
+
it "creates a config file with data collector config values" do
|
89
|
+
expect(Tempfile).to receive(:new).and_return(local_tempfile)
|
90
|
+
expect(local_tempfile).to receive(:write).with(<<~EOM
|
91
|
+
local_mode true
|
92
|
+
color false
|
93
|
+
cache_path "\#{ENV['APPDATA']}/chef-workstation"
|
94
|
+
chef_repo_path "\#{ENV['APPDATA']}/chef-workstation"
|
95
|
+
require_relative "reporter"
|
96
|
+
reporter = ChefApply::Reporter.new
|
97
|
+
report_handlers << reporter
|
98
|
+
exception_handlers << reporter
|
99
|
+
data_collector.server_url "dc.url"
|
100
|
+
data_collector.token "dc.token"
|
101
|
+
data_collector.mode :solo
|
102
|
+
data_collector.organization "Chef Workstation"
|
103
|
+
EOM
|
104
|
+
)
|
105
|
+
expect(target_host).to receive(:upload_file).with(local_tempfile.path, remote_config)
|
106
|
+
expect(subject.create_remote_config(remote_folder)).to eq(remote_config)
|
107
|
+
# ensure the tempfile is deleted locally
|
108
|
+
expect(local_tempfile.closed?).to eq(true)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "when data_collector is not set" do
|
113
|
+
before do
|
114
|
+
ChefApply::Config.data_collector.url = nil
|
115
|
+
ChefApply::Config.data_collector.token = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
it "creates a config file without data collector config values" do
|
119
|
+
expect(Tempfile).to receive(:new).and_return(local_tempfile)
|
120
|
+
expect(local_tempfile).to receive(:write).with(<<~EOM
|
121
|
+
local_mode true
|
122
|
+
color false
|
123
|
+
cache_path "\#{ENV['APPDATA']}/chef-workstation"
|
124
|
+
chef_repo_path "\#{ENV['APPDATA']}/chef-workstation"
|
125
|
+
require_relative "reporter"
|
126
|
+
reporter = ChefApply::Reporter.new
|
127
|
+
report_handlers << reporter
|
128
|
+
exception_handlers << reporter
|
129
|
+
EOM
|
130
|
+
)
|
131
|
+
expect(target_host).to receive(:upload_file).with(local_tempfile.path, remote_config)
|
132
|
+
expect(subject.create_remote_config(remote_folder)).to eq(remote_config)
|
133
|
+
# ensure the tempfile is deleted locally
|
134
|
+
expect(local_tempfile.closed?).to eq(true)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "#create_remote_handler" do
|
140
|
+
let(:remote_folder) { "/tmp/foo" }
|
141
|
+
let(:remote_reporter) { "#{remote_folder}/reporter.rb" }
|
142
|
+
let!(:local_tempfile) { Tempfile.new }
|
143
|
+
|
144
|
+
it "pushes it to the remote machine" do
|
145
|
+
expect(Tempfile).to receive(:new).and_return(local_tempfile)
|
146
|
+
expect(target_host).to receive(:upload_file).with(local_tempfile.path, remote_reporter)
|
147
|
+
expect(subject.create_remote_handler(remote_folder)).to eq(remote_reporter)
|
148
|
+
# ensure the tempfile is deleted locally
|
149
|
+
expect(local_tempfile.closed?).to eq(true)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "raises an error if the upload fails" do
|
153
|
+
expect(Tempfile).to receive(:new).and_return(local_tempfile)
|
154
|
+
expect(target_host).to receive(:upload_file).with(local_tempfile.path, remote_reporter).and_raise("foo")
|
155
|
+
err = ChefApply::Action::ConvergeTarget::HandlerUploadFailed
|
156
|
+
expect { subject.create_remote_handler(remote_folder) }.to raise_error(err)
|
157
|
+
# ensure the tempfile is deleted locally
|
158
|
+
expect(local_tempfile.closed?).to eq(true)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "#upload_trusted_certs" do
|
163
|
+
let(:remote_folder) { "/tmp/foo" }
|
164
|
+
let(:remote_tcd) { File.join(remote_folder, "trusted_certs") }
|
165
|
+
let(:tmpdir) { Dir.mktmpdir }
|
166
|
+
let(:certs_dir) { File.join(tmpdir, "weird/glob/chars[/") }
|
167
|
+
|
168
|
+
before do
|
169
|
+
ChefApply::Config.chef.trusted_certs_dir = certs_dir
|
170
|
+
FileUtils.mkdir_p(certs_dir)
|
171
|
+
end
|
172
|
+
|
173
|
+
after do
|
174
|
+
ChefApply::Config.reset
|
175
|
+
FileUtils.remove_entry tmpdir
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when there are local certificates" do
|
179
|
+
let!(:cert1) { FileUtils.touch(File.join(certs_dir, "1.crt"))[0] }
|
180
|
+
let!(:cert2) { FileUtils.touch(File.join(certs_dir, "2.pem"))[0] }
|
181
|
+
|
182
|
+
it "uploads the local certs" do
|
183
|
+
expect(target_host).to receive(:run_command).with("#{subject.mkdir} #{remote_tcd}", true)
|
184
|
+
expect(target_host).to receive(:upload_file).with(cert1, File.join(remote_tcd, File.basename(cert1)))
|
185
|
+
expect(target_host).to receive(:upload_file).with(cert2, File.join(remote_tcd, File.basename(cert2)))
|
186
|
+
subject.upload_trusted_certs(remote_folder)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "when there are no local certificates" do
|
191
|
+
it "does not upload any certs" do
|
192
|
+
expect(target_host).to_not receive(:run_command)
|
193
|
+
expect(target_host).to_not receive(:upload_file)
|
194
|
+
subject.upload_trusted_certs(remote_folder)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "#perform_action" do
|
201
|
+
let(:remote_folder) { "/tmp/foo" }
|
202
|
+
let(:remote_archive) { File.join(remote_folder, File.basename(archive)) }
|
203
|
+
let(:remote_config) { "#{remote_folder}/workstation.rb" }
|
204
|
+
let(:remote_handler) { "#{remote_folder}/reporter.rb" }
|
205
|
+
let(:tmpdir) { double("tmpdir", exit_status: 0, stdout: remote_folder) }
|
206
|
+
before do
|
207
|
+
expect(target_host).to receive(:run_command!).with(subject.mktemp, true).and_return(tmpdir)
|
208
|
+
end
|
209
|
+
let(:result) { double("command result", exit_status: 0, stdout: "") }
|
210
|
+
|
211
|
+
it "runs the converge and reports back success" do
|
212
|
+
expect(action).to receive(:create_remote_policy).with(local_policy_path, remote_folder).and_return(remote_archive)
|
213
|
+
expect(action).to receive(:create_remote_config).with(remote_folder).and_return(remote_config)
|
214
|
+
expect(action).to receive(:create_remote_handler).with(remote_folder).and_return(remote_handler)
|
215
|
+
expect(action).to receive(:upload_trusted_certs).with(remote_folder)
|
216
|
+
expect(target_host).to receive(:run_command).with(/chef-client.+#{archive}/).and_return(result)
|
217
|
+
expect(target_host).to receive(:run_command!)
|
218
|
+
.with("#{subject.delete_folder} #{remote_folder}")
|
219
|
+
.and_return(result)
|
220
|
+
[:running_chef, :success].each do |n|
|
221
|
+
expect(action).to receive(:notify).with(n)
|
222
|
+
end
|
223
|
+
subject.perform_action
|
224
|
+
end
|
225
|
+
|
226
|
+
context "when chef schedules restart" do
|
227
|
+
let(:result) { double("command result", exit_status: 35) }
|
228
|
+
|
229
|
+
it "runs the converge and reports back reboot" do
|
230
|
+
expect(action).to receive(:create_remote_policy).with(local_policy_path, remote_folder).and_return(remote_archive)
|
231
|
+
expect(action).to receive(:create_remote_config).with(remote_folder).and_return(remote_config)
|
232
|
+
expect(action).to receive(:create_remote_handler).with(remote_folder).and_return(remote_handler)
|
233
|
+
expect(action).to receive(:upload_trusted_certs).with(remote_folder)
|
234
|
+
expect(target_host).to receive(:run_command).with(/chef-client.+#{archive}/).and_return(result)
|
235
|
+
expect(target_host).to receive(:run_command!)
|
236
|
+
.with("#{subject.delete_folder} #{remote_folder}")
|
237
|
+
.and_return(result)
|
238
|
+
[:running_chef, :reboot].each do |n|
|
239
|
+
expect(action).to receive(:notify).with(n)
|
240
|
+
end
|
241
|
+
subject.perform_action
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context "when command fails" do
|
246
|
+
let(:result) { double("command result", exit_status: 1, stdout: "", stderr: "") }
|
247
|
+
let(:report_result) { double("report result", exit_status: 0, stdout: '{ "exception": "thing" }') }
|
248
|
+
let(:exception_mapper) { double("mapper") }
|
249
|
+
before do
|
250
|
+
expect(ChefApply::Errors::CCRFailureMapper).to receive(:new).
|
251
|
+
and_return exception_mapper
|
252
|
+
end
|
253
|
+
|
254
|
+
it "reports back failure and reads the remote report" do
|
255
|
+
expect(action).to receive(:create_remote_policy).with(local_policy_path, remote_folder).and_return(remote_archive)
|
256
|
+
expect(action).to receive(:create_remote_config).with(remote_folder).and_return(remote_config)
|
257
|
+
expect(action).to receive(:create_remote_handler).with(remote_folder).and_return(remote_handler)
|
258
|
+
expect(action).to receive(:upload_trusted_certs).with(remote_folder)
|
259
|
+
expect(target_host).to receive(:run_command).with(/chef-client.+#{archive}/).and_return(result)
|
260
|
+
expect(target_host).to receive(:run_command!)
|
261
|
+
.with("#{subject.delete_folder} #{remote_folder}")
|
262
|
+
[:running_chef, :converge_error].each do |n|
|
263
|
+
expect(action).to receive(:notify).with(n)
|
264
|
+
end
|
265
|
+
expect(target_host).to receive(:run_command).with(subject.read_chef_report).and_return(report_result)
|
266
|
+
expect(target_host).to receive(:run_command!).with(subject.delete_chef_report)
|
267
|
+
expect(exception_mapper).to receive(:raise_mapped_exception!)
|
268
|
+
subject.perform_action
|
269
|
+
end
|
270
|
+
|
271
|
+
context "when remote report cannot be read" do
|
272
|
+
let(:report_result) { double("report result", exit_status: 1, stdout: "", stderr: "") }
|
273
|
+
it "reports back failure" do
|
274
|
+
expect(action).to receive(:create_remote_policy).with(local_policy_path, remote_folder).and_return(remote_archive)
|
275
|
+
expect(action).to receive(:create_remote_config).with(remote_folder).and_return(remote_config)
|
276
|
+
expect(action).to receive(:create_remote_handler).with(remote_folder).and_return(remote_handler)
|
277
|
+
expect(action).to receive(:upload_trusted_certs).with(remote_folder)
|
278
|
+
expect(target_host).to receive(:run_command).with(/chef-client.+#{archive}/).and_return(result)
|
279
|
+
expect(target_host).to receive(:run_command!)
|
280
|
+
.with("#{subject.delete_folder} #{remote_folder}")
|
281
|
+
[:running_chef, :converge_error].each do |n|
|
282
|
+
expect(action).to receive(:notify).with(n)
|
283
|
+
end
|
284
|
+
expect(target_host).to receive(:run_command).with(subject.read_chef_report).and_return(report_result)
|
285
|
+
expect(exception_mapper).to receive(:raise_mapped_exception!)
|
286
|
+
subject.perform_action
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|