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,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
|