chef-apply 0.2.8 → 0.2.13
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.lock +84 -63
- data/README.md +6 -0
- data/chef-apply.gemspec +1 -0
- data/i18n/errors/en.yml +11 -10
- data/lib/chef_apply/action/base.rb +0 -84
- data/lib/chef_apply/action/converge_target.rb +60 -24
- data/lib/chef_apply/action/install_chef.rb +99 -10
- data/lib/chef_apply/cli.rb +19 -1
- data/lib/chef_apply/config.rb +1 -0
- data/lib/chef_apply/error.rb +1 -0
- data/lib/chef_apply/errors/ccr_failure_mapper.rb +2 -2
- data/lib/chef_apply/target_host.rb +113 -73
- data/lib/chef_apply/target_host/linux.rb +63 -0
- data/lib/chef_apply/target_host/windows.rb +62 -0
- data/lib/chef_apply/version.rb +1 -1
- data/spec/integration/fixtures/chef_help.out +1 -0
- data/spec/unit/action/base_spec.rb +0 -30
- data/spec/unit/action/converge_target_spec.rb +130 -73
- data/spec/unit/action/install_chef_spec.rb +118 -23
- data/spec/unit/cli_spec.rb +32 -4
- data/spec/unit/target_host/linux_spec.rb +57 -0
- data/spec/unit/target_host/windows_spec.rb +43 -0
- data/spec/unit/target_host_spec.rb +130 -135
- data/spec/unit/ui/error_printer_spec.rb +1 -1
- metadata +27 -8
- data/lib/chef_apply/action/install_chef/base.rb +0 -117
- data/lib/chef_apply/action/install_chef/linux.rb +0 -37
- data/lib/chef_apply/action/install_chef/windows.rb +0 -54
- data/spec/unit/action/install_chef/base_spec.rb +0 -168
@@ -16,9 +16,9 @@
|
|
16
16
|
#
|
17
17
|
|
18
18
|
require "chef_apply/action/base"
|
19
|
-
require "chef_apply/text"
|
20
19
|
require "pathname"
|
21
20
|
require "tempfile"
|
21
|
+
# FLAG: require "chef/util/path_helper"
|
22
22
|
require "chef/util/path_helper"
|
23
23
|
|
24
24
|
module ChefApply::Action
|
@@ -26,8 +26,8 @@ module ChefApply::Action
|
|
26
26
|
|
27
27
|
def perform_action
|
28
28
|
local_policy_path = config.delete :local_policy_path
|
29
|
-
remote_tmp = target_host.
|
30
|
-
remote_dir_path =
|
29
|
+
remote_tmp = target_host.temp_dir()
|
30
|
+
remote_dir_path = target_host.normalize_path(remote_tmp)
|
31
31
|
# Ensure the directory is owned by the connecting user,
|
32
32
|
# otherwise we won't be able to put things into it over scp as that user.
|
33
33
|
remote_policy_path = create_remote_policy(local_policy_path, remote_dir_path)
|
@@ -36,11 +36,12 @@ module ChefApply::Action
|
|
36
36
|
upload_trusted_certs(remote_dir_path)
|
37
37
|
|
38
38
|
notify(:running_chef)
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
# TODO - just teach target_host how to run_chef?
|
40
|
+
cmd_str = run_chef_cmd(remote_dir_path,
|
41
|
+
File.basename(remote_config_path),
|
42
|
+
File.basename(remote_policy_path))
|
42
43
|
c = target_host.run_command(cmd_str)
|
43
|
-
target_host.
|
44
|
+
target_host.del_dir(remote_dir_path)
|
44
45
|
if c.exit_status == 0
|
45
46
|
ChefApply::Log.info(c.stdout)
|
46
47
|
notify(:success)
|
@@ -73,14 +74,20 @@ module ChefApply::Action
|
|
73
74
|
workstation_rb = <<~EOM
|
74
75
|
local_mode true
|
75
76
|
color false
|
76
|
-
cache_path "#{
|
77
|
-
chef_repo_path "#{
|
77
|
+
cache_path "#{target_host.ws_cache_path}"
|
78
|
+
chef_repo_path "#{target_host.ws_cache_path}"
|
78
79
|
require_relative "reporter"
|
79
80
|
reporter = ChefApply::Reporter.new
|
80
81
|
report_handlers << reporter
|
81
82
|
exception_handlers << reporter
|
82
83
|
EOM
|
83
84
|
|
85
|
+
unless ChefApply::Config.chef.chef_license.nil?
|
86
|
+
workstation_rb << <<~EOM
|
87
|
+
chef_license "#{ChefApply::Config.chef.chef_license}"
|
88
|
+
EOM
|
89
|
+
end
|
90
|
+
|
84
91
|
# add the target host's log level value
|
85
92
|
# (we don't set a location because we want output to
|
86
93
|
# go in stdout for reporting back to chef-apply)
|
@@ -115,13 +122,16 @@ module ChefApply::Action
|
|
115
122
|
remote_config_path
|
116
123
|
end
|
117
124
|
|
118
|
-
def create_remote_handler(
|
119
|
-
remote_handler_path = File.join(
|
125
|
+
def create_remote_handler(remote_dir)
|
126
|
+
remote_handler_path = File.join(remote_dir, "reporter.rb")
|
120
127
|
begin
|
121
|
-
|
128
|
+
# TODO - why don't we upload the original remote_handler_path instead of making a temp copy?
|
129
|
+
handler_file = Tempfile.new()
|
130
|
+
# TODO - ideally this is a resource in the gem, and not placed in with source files.
|
122
131
|
handler_file.write(File.read(File.join(__dir__, "reporter.rb")))
|
123
132
|
handler_file.close
|
124
133
|
target_host.upload_file(handler_file.path, remote_handler_path)
|
134
|
+
# TODO - should we be more specific in our error catch?
|
125
135
|
rescue RuntimeError
|
126
136
|
raise HandlerUploadFailed.new()
|
127
137
|
ensure
|
@@ -131,41 +141,67 @@ module ChefApply::Action
|
|
131
141
|
end
|
132
142
|
|
133
143
|
def upload_trusted_certs(dir)
|
144
|
+
# TODO BOOTSTRAP - trusted certs dir and other config to be received as argument to constructor
|
134
145
|
local_tcd = Chef::Util::PathHelper.escape_glob_dir(ChefApply::Config.chef.trusted_certs_dir)
|
135
146
|
certs = Dir.glob(File.join(local_tcd, "*.{crt,pem}"))
|
136
147
|
return if certs.empty?
|
148
|
+
|
137
149
|
notify(:uploading_trusted_certs)
|
138
150
|
remote_tcd = "#{dir}/trusted_certs"
|
139
|
-
target_host.
|
151
|
+
target_host.make_directory(remote_tcd)
|
140
152
|
certs.each do |cert_file|
|
141
153
|
target_host.upload_file(cert_file, "#{remote_tcd}/#{File.basename(cert_file)}")
|
142
154
|
end
|
143
155
|
end
|
144
156
|
|
157
|
+
def chef_report_path
|
158
|
+
@chef_report_path ||= target_host.normalize_path(File.join(target_host.ws_cache_path, "cache", "run-report.json"))
|
159
|
+
end
|
160
|
+
|
145
161
|
def handle_ccr_error
|
146
162
|
require "chef_apply/errors/ccr_failure_mapper"
|
147
163
|
mapper_opts = {}
|
148
|
-
|
149
|
-
if
|
150
|
-
report =
|
164
|
+
content = target_host.fetch_file_contents(chef_report_path)
|
165
|
+
if content.nil?
|
166
|
+
report = {}
|
167
|
+
mapper_opts[:failed_report_path] = chef_report_path
|
168
|
+
ChefApply::Log.error("Could not read remote report at #{chef_report_path}")
|
169
|
+
else
|
151
170
|
# We need to delete the stacktrace after copying it over. Otherwise if we get a
|
152
171
|
# remote failure that does not write a chef stacktrace its possible to get an old
|
153
172
|
# stale stacktrace.
|
154
|
-
target_host.
|
173
|
+
target_host.del_file(chef_report_path)
|
174
|
+
report = JSON.parse(content)
|
155
175
|
ChefApply::Log.error("Remote chef-client error follows:")
|
156
176
|
ChefApply::Log.error(report["exception"])
|
157
|
-
else
|
158
|
-
report = {}
|
159
|
-
ChefApply::Log.error("Could not read remote report:")
|
160
|
-
ChefApply::Log.error("stdout: #{c.stdout}")
|
161
|
-
ChefApply::Log.error("stderr: #{c.stderr}")
|
162
|
-
mapper_opts[:stdout] = c.stdout
|
163
|
-
mapper_opts[:stderr] = c.stderr
|
164
177
|
end
|
165
178
|
mapper = ChefApply::Errors::CCRFailureMapper.new(report["exception"], mapper_opts)
|
166
179
|
mapper.raise_mapped_exception!
|
167
180
|
end
|
168
181
|
|
182
|
+
# Chef will try 'downloading' the policy from the internet unless we pass it a valid, local file
|
183
|
+
# in the working directory. By pointing it at a local file it will just copy it instead of trying
|
184
|
+
# to download it.
|
185
|
+
#
|
186
|
+
# Chef 13 on Linux requires full path specifiers for --config and --recipe-url while on Chef 13 and 14 on
|
187
|
+
# Windows must use relative specifiers to prevent URI from causing an error
|
188
|
+
# (https://github.com/chef/chef/pull/7223/files).
|
189
|
+
def run_chef_cmd(working_dir, config_file, policy)
|
190
|
+
case target_host.base_os
|
191
|
+
when :windows
|
192
|
+
"Set-Location -Path #{working_dir}; " +
|
193
|
+
# We must 'wait' for chef-client to finish before changing directories and Out-Null does that
|
194
|
+
"chef-client -z --config #{File.join(working_dir, config_file)} --recipe-url #{File.join(working_dir, policy)} | Out-Null; " +
|
195
|
+
# We have to leave working dir so we don't hold a lock on it, which allows us to delete this tempdir later
|
196
|
+
"Set-Location C:/; " +
|
197
|
+
"exit $LASTEXITCODE"
|
198
|
+
else
|
199
|
+
# cd is shell a builtin, so we'll invoke bash. This also means all commands are executed
|
200
|
+
# with sudo (as long as we are hardcoding our sudo use)
|
201
|
+
"bash -c 'cd #{working_dir}; chef-client -z --config #{File.join(working_dir, config_file)} --recipe-url #{File.join(working_dir, policy)}'"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
169
205
|
class ConfigUploadFailed < ChefApply::Error
|
170
206
|
def initialize(); super("CHEFUPL003"); end
|
171
207
|
end
|
@@ -15,16 +15,105 @@
|
|
15
15
|
# limitations under the License.
|
16
16
|
#
|
17
17
|
|
18
|
-
require "chef_apply/action/
|
19
|
-
require "chef_apply/
|
20
|
-
require "
|
21
|
-
|
22
|
-
module ChefApply
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
require "chef_apply/action/base"
|
19
|
+
require "chef_apply/minimum_chef_version"
|
20
|
+
require "fileutils"
|
21
|
+
|
22
|
+
module ChefApply
|
23
|
+
module Action
|
24
|
+
class InstallChef < ChefApply::Action::Base
|
25
|
+
|
26
|
+
def initialize(opts = { check_only: false })
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def perform_action
|
31
|
+
if ChefApply::MinimumChefVersion.check!(target_host, config[:check_only]) == :minimum_version_met
|
32
|
+
notify(:already_installed)
|
33
|
+
else
|
34
|
+
perform_local_install
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def upgrading?
|
39
|
+
@upgrading
|
40
|
+
end
|
41
|
+
|
42
|
+
def perform_local_install
|
43
|
+
package = lookup_artifact()
|
44
|
+
notify(:downloading)
|
45
|
+
local_path = download_to_workstation(package.url)
|
46
|
+
notify(:uploading)
|
47
|
+
remote_path = upload_to_target(local_path)
|
48
|
+
notify(:installing)
|
49
|
+
target_host.install_package(remote_path)
|
50
|
+
notify(:install_complete)
|
51
|
+
end
|
52
|
+
|
53
|
+
def perform_remote_install
|
54
|
+
# TODO BOOTSTRAP - we'll need to implement this for both platforms
|
55
|
+
# require "mixlib/install"
|
56
|
+
# installer = Mixlib::Install.new({
|
57
|
+
# platform: "windows",
|
58
|
+
# product_name: "chef",
|
59
|
+
# channel: :stable,
|
60
|
+
# shell_type: :ps1,
|
61
|
+
# version: "13",
|
62
|
+
# })
|
63
|
+
target_host.run_command! installer.install_command
|
64
|
+
raise NotImplementedError
|
65
|
+
end
|
66
|
+
|
67
|
+
def lookup_artifact
|
68
|
+
return @artifact_info if @artifact_info
|
69
|
+
require "mixlib/install"
|
70
|
+
c = train_to_mixlib(target_host.platform)
|
71
|
+
Mixlib::Install.new(c).artifact_info
|
72
|
+
end
|
73
|
+
|
74
|
+
def version_to_install
|
75
|
+
lookup_artifact.version
|
76
|
+
end
|
77
|
+
|
78
|
+
def train_to_mixlib(platform)
|
79
|
+
opts = {
|
80
|
+
platform_version: platform.release,
|
81
|
+
platform: platform.name,
|
82
|
+
architecture: platform.arch,
|
83
|
+
product_name: "chef",
|
84
|
+
product_version: :latest,
|
85
|
+
channel: :stable,
|
86
|
+
platform_version_compatibility_mode: true,
|
87
|
+
}
|
88
|
+
case platform.name
|
89
|
+
when /windows/
|
90
|
+
opts[:platform] = "windows"
|
91
|
+
when "redhat", "centos"
|
92
|
+
opts[:platform] = "el"
|
93
|
+
when "suse"
|
94
|
+
opts[:platform] = "sles"
|
95
|
+
when "amazon"
|
96
|
+
opts[:platform] = "el"
|
97
|
+
if platform.release.to_i > 2010 # legacy Amazon version 1
|
98
|
+
opts[:platform_version] = "6"
|
99
|
+
else
|
100
|
+
opts[:platform_version] = "7"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
opts
|
104
|
+
end
|
105
|
+
|
106
|
+
def download_to_workstation(url_path)
|
107
|
+
require "chef_apply/file_fetcher"
|
108
|
+
ChefApply::FileFetcher.fetch(url_path)
|
109
|
+
end
|
110
|
+
|
111
|
+
def upload_to_target(local_path)
|
112
|
+
installer_dir = target_host.temp_dir()
|
113
|
+
remote_path = File.join(installer_dir, File.basename(local_path))
|
114
|
+
target_host.upload_file(local_path, remote_path)
|
115
|
+
remote_path
|
116
|
+
end
|
28
117
|
end
|
29
118
|
end
|
30
119
|
end
|
data/lib/chef_apply/cli.rb
CHANGED
@@ -36,6 +36,8 @@ require "chef_apply/telemeter"
|
|
36
36
|
require "chef_apply/ui/error_printer"
|
37
37
|
require "chef_apply/ui/terminal"
|
38
38
|
require "chef_apply/ui/terminal/job"
|
39
|
+
require "license_acceptance/cli_flags/mixlib_cli"
|
40
|
+
require "license_acceptance/acceptor"
|
39
41
|
|
40
42
|
module ChefApply
|
41
43
|
class CLI
|
@@ -48,6 +50,7 @@ module ChefApply
|
|
48
50
|
include ChefApply::CLI::Validation
|
49
51
|
# Help and version formatting
|
50
52
|
include ChefApply::CLI::Help
|
53
|
+
include LicenseAcceptance::CLIFlags::MixlibCLI
|
51
54
|
|
52
55
|
RC_OK = 0
|
53
56
|
RC_COMMAND_FAILED = 1
|
@@ -106,6 +109,7 @@ module ChefApply
|
|
106
109
|
elsif parsed_options[:version]
|
107
110
|
show_version
|
108
111
|
else
|
112
|
+
check_license_acceptance
|
109
113
|
validate_params(cli_arguments)
|
110
114
|
target_hosts = resolve_targets(cli_arguments.shift, parsed_options)
|
111
115
|
render_cookbook_setup(cli_arguments)
|
@@ -124,6 +128,16 @@ module ChefApply
|
|
124
128
|
temp_cookbook.delete unless temp_cookbook.nil?
|
125
129
|
end
|
126
130
|
|
131
|
+
def check_license_acceptance
|
132
|
+
acceptor = LicenseAcceptance::Acceptor.new(provided: ChefApply::Config.chef.chef_license)
|
133
|
+
begin
|
134
|
+
acceptor.check_and_persist("infra-client", "latest")
|
135
|
+
rescue LicenseAcceptance::LicenseNotAcceptedError
|
136
|
+
raise LicenseCheckFailed.new
|
137
|
+
end
|
138
|
+
ChefApply::Config.chef.chef_license ||= acceptor.acceptance_value
|
139
|
+
end
|
140
|
+
|
127
141
|
def resolve_targets(host_spec, opts)
|
128
142
|
@target_hosts = TargetResolver.new(host_spec,
|
129
143
|
opts.delete(:protocol),
|
@@ -170,7 +184,7 @@ module ChefApply
|
|
170
184
|
def install(target_host, reporter)
|
171
185
|
context = TS.install_chef
|
172
186
|
reporter.update(context.verifying)
|
173
|
-
installer = Action::InstallChef.
|
187
|
+
installer = Action::InstallChef.new(target_host: target_host, check_only: !parsed_options[:install])
|
174
188
|
installer.run do |event, data|
|
175
189
|
case event
|
176
190
|
when :installing
|
@@ -325,4 +339,8 @@ module ChefApply
|
|
325
339
|
end
|
326
340
|
|
327
341
|
end
|
342
|
+
|
343
|
+
class LicenseCheckFailed < ChefApply::Error
|
344
|
+
def initialize(); super("CHEFLIC001"); end
|
345
|
+
end
|
328
346
|
end
|
data/lib/chef_apply/config.rb
CHANGED
@@ -145,6 +145,7 @@ module ChefApply
|
|
145
145
|
ChefConfig::WorkstationConfigLoader.new(nil, ChefApply::Log).load
|
146
146
|
default(:cookbook_repo_paths, [ChefConfig::Config[:cookbook_path]].flatten)
|
147
147
|
default(:trusted_certs_dir, ChefConfig::Config[:trusted_certs_dir])
|
148
|
+
default(:chef_license, ChefConfig::Config[:chef_license])
|
148
149
|
ChefConfig::Config.reset
|
149
150
|
end
|
150
151
|
|
data/lib/chef_apply/error.rb
CHANGED
@@ -28,7 +28,7 @@ module ChefApply::Errors
|
|
28
28
|
|
29
29
|
def raise_mapped_exception!
|
30
30
|
if @cause_line.nil?
|
31
|
-
raise RemoteChefRunFailedToResolveError.new(params[:
|
31
|
+
raise RemoteChefRunFailedToResolveError.new(params[:failed_report_path])
|
32
32
|
else
|
33
33
|
errid, *args = exception_args_from_cause()
|
34
34
|
if errid.nil?
|
@@ -85,7 +85,7 @@ module ChefApply::Errors
|
|
85
85
|
end
|
86
86
|
|
87
87
|
class RemoteChefRunFailedToResolveError < ChefApply::ErrorNoStack
|
88
|
-
def initialize(
|
88
|
+
def initialize(path); super("CHEFCCR001", path); end
|
89
89
|
end
|
90
90
|
|
91
91
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright:: Copyright (c) 2017 Chef Software Inc.
|
2
|
+
# Copyright:: Copyright (c) 2017-2019 Chef Software Inc.
|
3
3
|
# License:: Apache License, Version 2.0
|
4
4
|
#
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -18,6 +18,7 @@
|
|
18
18
|
require "chef_apply/log"
|
19
19
|
require "chef_apply/error"
|
20
20
|
require "train"
|
21
|
+
|
21
22
|
module ChefApply
|
22
23
|
class TargetHost
|
23
24
|
attr_reader :config, :reporter, :backend, :transport_type
|
@@ -26,10 +27,39 @@ module ChefApply
|
|
26
27
|
# See #apply_ssh_config
|
27
28
|
SSH_CONFIG_OVERRIDE_KEYS = [:user, :port, :proxy].freeze
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
# We're borrowing a page from train here - because setting up a
|
31
|
+
# reliable connection for testing is a multi-step process,
|
32
|
+
# we'll provide this method which instantiates a TargetHost connected
|
33
|
+
# to a train mock backend. If the family/name provided resolves to a suported
|
34
|
+
# OS, this instance will mix-in the supporting methods for the given platform;
|
35
|
+
# otherwise those methods will raise NotImplementedError.
|
36
|
+
def self.mock_instance(url, family: "unknown", name: "unknown",
|
37
|
+
release: "unknown", arch: "x86_64")
|
38
|
+
# Specifying sudo: false ensures that attempted operations
|
39
|
+
# don't fail because the mock platform doesn't support sudo
|
40
|
+
target_host = TargetHost.new(url, { sudo: false })
|
41
|
+
|
42
|
+
# Don't pull in the platform-specific mixins automatically during connect
|
43
|
+
# Otherwise, it will raise since it can't resolve the OS without the mock.
|
44
|
+
target_host.instance_variable_set(:@mocked_connection, true)
|
32
45
|
target_host.connect!
|
46
|
+
|
47
|
+
# We need to provide this mock before invoking mix_in_target_platform,
|
48
|
+
# otherwise it will fail with an unknown OS (since we don't have a real connection).
|
49
|
+
target_host.backend.mock_os(
|
50
|
+
family: family,
|
51
|
+
name: name,
|
52
|
+
release: release,
|
53
|
+
arch: arch
|
54
|
+
)
|
55
|
+
|
56
|
+
# Only mix-in if we can identify the platform. This
|
57
|
+
# prevents mix_in_target_platform! from raising on unknown platform during
|
58
|
+
# tests that validate unsupported platform behaviors.
|
59
|
+
if target_host.base_os != :other
|
60
|
+
target_host.mix_in_target_platform!
|
61
|
+
end
|
62
|
+
|
33
63
|
target_host
|
34
64
|
end
|
35
65
|
|
@@ -79,10 +109,19 @@ module ChefApply
|
|
79
109
|
end
|
80
110
|
end
|
81
111
|
|
112
|
+
# Establish connection to configured target.
|
113
|
+
#
|
82
114
|
def connect!
|
115
|
+
# Keep existing connections
|
83
116
|
return unless @backend.nil?
|
84
117
|
@backend = train_connection.connection
|
85
118
|
@backend.wait_until_ready
|
119
|
+
|
120
|
+
# When the testing function `mock_instance` is used, it will set
|
121
|
+
# this instance variable to false and handle this function call
|
122
|
+
# after the platform data is mocked; this will allow binding
|
123
|
+
# of mixin functions based on the mocked platform.
|
124
|
+
mix_in_target_platform! unless @mocked_connection
|
86
125
|
rescue Train::UserError => e
|
87
126
|
raise ConnectionFailure.new(e, config)
|
88
127
|
rescue Train::Error => e
|
@@ -91,8 +130,20 @@ module ChefApply
|
|
91
130
|
raise ConnectionFailure.new(e.cause || e, config)
|
92
131
|
end
|
93
132
|
|
133
|
+
def mix_in_target_platform!
|
134
|
+
case base_os
|
135
|
+
when :linux
|
136
|
+
require "chef_apply/target_host/linux"
|
137
|
+
class << self; include ChefApply::TargetHost::Linux; end
|
138
|
+
when :windows
|
139
|
+
require "chef_apply/target_host/windows"
|
140
|
+
class << self; include ChefApply::TargetHost::Windows; end
|
141
|
+
when :other
|
142
|
+
raise ChefApply::TargetHost::UnsupportedTargetOS.new(platform.name)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
94
146
|
# Returns the user being used to connect. Defaults to train's default user if not specified
|
95
|
-
# defaulted in .ssh/config (for ssh connections), as set up in '#apply_ssh_config'.
|
96
147
|
def user
|
97
148
|
return config[:user] unless config[:user].nil?
|
98
149
|
require "train/transports/ssh"
|
@@ -112,17 +163,16 @@ module ChefApply
|
|
112
163
|
end
|
113
164
|
|
114
165
|
def base_os
|
115
|
-
if platform.
|
166
|
+
if platform.windows?
|
116
167
|
:windows
|
117
168
|
elsif platform.linux?
|
118
169
|
:linux
|
119
170
|
else
|
120
|
-
|
121
|
-
# all the caller is doing is asking about the OS
|
122
|
-
raise ChefApply::TargetHost::UnsupportedTargetOS.new(platform.name)
|
171
|
+
:other
|
123
172
|
end
|
124
173
|
end
|
125
174
|
|
175
|
+
# TODO 2019-01-29 not expose this, it's internal implemenation. Same with #backend.
|
126
176
|
def platform
|
127
177
|
backend.platform
|
128
178
|
end
|
@@ -143,6 +193,17 @@ module ChefApply
|
|
143
193
|
backend.upload(local_path, remote_path)
|
144
194
|
end
|
145
195
|
|
196
|
+
# Retrieve the contents of a remote file. Returns nil
|
197
|
+
# if the file didn't exist or couldn't be read.
|
198
|
+
def fetch_file_contents(remote_path)
|
199
|
+
result = backend.file(remote_path)
|
200
|
+
if result.exist? && result.file?
|
201
|
+
result.content
|
202
|
+
else
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
146
207
|
# Returns the installed chef version as a Gem::Version,
|
147
208
|
# or raised ChefNotInstalled if chef client version manifest can't
|
148
209
|
# be found.
|
@@ -150,81 +211,62 @@ module ChefApply
|
|
150
211
|
return @installed_chef_version if @installed_chef_version
|
151
212
|
# Note: In the case of a very old version of chef (that has no manifest - pre 12.0?)
|
152
213
|
# this will report as not installed.
|
153
|
-
manifest =
|
154
|
-
|
155
|
-
# We
|
156
|
-
#
|
214
|
+
manifest = read_chef_version_manifest()
|
215
|
+
|
216
|
+
# We split the version here because unstable builds install from)
|
217
|
+
# are in the form "Major.Minor.Build+HASH" which is not a valid
|
157
218
|
# version string.
|
158
219
|
@installed_chef_version = Gem::Version.new(manifest["build_version"].split("+")[0])
|
159
220
|
end
|
160
221
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
# A working approach is below - but it runs very slowly in testing
|
166
|
-
# on a virtualbox windows vm:
|
167
|
-
# (over winrm) Get-WmiObject Win32_Product | Where {$_.Name -match 'Chef Client'}
|
168
|
-
windows: "c:\\opscode\\chef\\version-manifest.json",
|
169
|
-
linux: "/opt/chef/version-manifest.json",
|
170
|
-
}.freeze
|
171
|
-
|
172
|
-
def get_chef_version_manifest
|
173
|
-
path = MANIFEST_PATHS[base_os()]
|
174
|
-
manifest = backend.file(path)
|
175
|
-
return :not_found unless manifest.file?
|
176
|
-
JSON.parse(manifest.content)
|
222
|
+
def read_chef_version_manifest
|
223
|
+
manifest = fetch_file_contents(omnibus_manifest_path)
|
224
|
+
raise ChefNotInstalled.new if manifest.nil?
|
225
|
+
JSON.parse(manifest)
|
177
226
|
end
|
178
227
|
|
179
|
-
#
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
end
|
191
|
-
nil
|
228
|
+
# Creates and caches location of temporary directory on the remote host
|
229
|
+
# using platform-specific implementations of make_temp_dir
|
230
|
+
# This will also set ownership to the connecting user instead of default of
|
231
|
+
# root when sudo'd, so that the dir can be used to upload files using scp
|
232
|
+
# as the connecting user.
|
233
|
+
#
|
234
|
+
# The base temp dir is cached and will only be created once per connection lifetime.
|
235
|
+
def temp_dir
|
236
|
+
dir = make_temp_dir()
|
237
|
+
chown(dir, user)
|
238
|
+
dir
|
192
239
|
end
|
193
240
|
|
194
|
-
#
|
241
|
+
# create a directory. because we run all commands as root, this will also set group:owner
|
242
|
+
# to the connecting user if host isn't windows so that scp -- which uses the connecting user --
|
243
|
+
# will have permissions to upload into it.
|
244
|
+
def make_directory(path)
|
245
|
+
mkdir(path)
|
246
|
+
chown(path, user)
|
247
|
+
path
|
248
|
+
end
|
195
249
|
|
196
|
-
#
|
197
|
-
|
198
|
-
|
199
|
-
return if base_os == :windows
|
200
|
-
owner ||= user
|
201
|
-
run_command!("chown #{owner} '#{path}'")
|
250
|
+
# normalizes path across OS's
|
251
|
+
def normalize_path(p) # NOTE BOOTSTRAP: was action::base::escape_windows_path
|
252
|
+
p.tr("\\", "/")
|
202
253
|
end
|
203
254
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
"(Join-Path $parent $name);" +
|
208
|
-
"$tmp.FullName"
|
255
|
+
# Simplified chown - just sets user, defaults to connection user. Does not touch
|
256
|
+
# group. Only has effect on non-windows targets
|
257
|
+
def chown(path, owner); raise NotImplementedError; end
|
209
258
|
|
210
|
-
|
259
|
+
# Platform-specific installation of packages
|
260
|
+
def install_package(target_package_path); raise NotImplementedError; end
|
211
261
|
|
212
|
-
|
213
|
-
|
214
|
-
#
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
else
|
221
|
-
# # TODO should we keep chmod 777?
|
222
|
-
res = run_command!("bash -c '#{MKTMP_LINUX_CMD}'")
|
223
|
-
path = res.stdout.chomp.strip
|
224
|
-
chown(path)
|
225
|
-
path
|
226
|
-
end
|
227
|
-
end
|
262
|
+
def ws_cache_path; raise NotImplementedError; end
|
263
|
+
|
264
|
+
# Recursively delete directory
|
265
|
+
def del_dir(path); raise NotImplementedError; end
|
266
|
+
|
267
|
+
def del_file(path); raise NotImplementedError; end
|
268
|
+
|
269
|
+
def omnibus_manifest_path(); raise NotImplementedError; end
|
228
270
|
|
229
271
|
private
|
230
272
|
|
@@ -276,9 +318,7 @@ module ChefApply
|
|
276
318
|
super(*(Array(init_params).flatten))
|
277
319
|
end
|
278
320
|
end
|
279
|
-
|
280
321
|
class ChefNotInstalled < StandardError; end
|
281
|
-
|
282
322
|
class UnsupportedTargetOS < ChefApply::ErrorNoLogs
|
283
323
|
def initialize(os_name); super("CHEFTARG001", os_name); end
|
284
324
|
end
|