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