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.
@@ -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.mktemp()
30
- remote_dir_path = escape_windows_path(remote_tmp)
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
- cmd_str = run_chef(remote_dir_path,
40
- File.basename(remote_config_path),
41
- File.basename(remote_policy_path))
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.run_command!("#{delete_folder} #{remote_dir_path}")
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 "#{cache_path}"
77
- chef_repo_path "#{cache_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(dir)
119
- remote_handler_path = File.join(dir, "reporter.rb")
125
+ def create_remote_handler(remote_dir)
126
+ remote_handler_path = File.join(remote_dir, "reporter.rb")
120
127
  begin
121
- handler_file = Tempfile.new
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.mkdir(remote_tcd)
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
- c = target_host.run_command(read_chef_report)
149
- if c.exit_status == 0
150
- report = JSON.parse(c.stdout)
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.run_command!(delete_chef_report)
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/install_chef/base"
19
- require "chef_apply/action/install_chef/windows"
20
- require "chef_apply/action/install_chef/linux"
21
-
22
- module ChefApply::Action::InstallChef
23
- def self.instance_for_target(target_host, opts = { check_only: false })
24
- opts[:target_host] = target_host
25
- case target_host.base_os
26
- when :windows then Windows.new(opts)
27
- when :linux then Linux.new(opts)
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
@@ -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.instance_for_target(target_host, check_only: !parsed_options[:install])
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
@@ -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
 
@@ -28,6 +28,7 @@ module ChefApply
28
28
  end
29
29
  end
30
30
 
31
+ # These helpers are obsolete
31
32
  class ErrorNoLogs < Error
32
33
  def initialize(id, *params)
33
34
  super
@@ -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[:stdout], params[:stderr])
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(stdout, stderr); super("CHEFCCR001", stdout, stderr); end
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
- def self.instance_for_url(target, opts = {})
30
- opts = { target: @url }
31
- target_host = new(target, opts)
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.family == "windows"
166
+ if platform.windows?
116
167
  :windows
117
168
  elsif platform.linux?
118
169
  :linux
119
170
  else
120
- # TODO - this seems like it shouldn't happen here, when
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 = get_chef_version_manifest()
154
- raise ChefNotInstalled.new if manifest == :not_found
155
- # We'll split the version here because unstable builds (where we currently
156
- # install from) are in the form "Major.Minor.Build+HASH" which is not a valid
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
- MANIFEST_PATHS = {
162
- # TODO - use a proper method to query the win installation path -
163
- # currently we're assuming the default, but this can be customized
164
- # at install time.
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
- # create a dir. set owner to the connecting user if host isn't windows
180
- # so that scp -- which uses the connecting user -- can upload into it.
181
- def mkdir(path)
182
- if base_os == :windows
183
- run_command!("New-Item -ItemType Directory -Force -Path #{path}")
184
- else
185
- # This will also set ownership to the connecting user instead of default of
186
- # root when sudo'd, so that the dir can be used to upload files using scp -
187
- # which is done as the connecting user.
188
- run_command!("mkdir -p #{path}")
189
- chown(path, user)
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
- # TODO make these platform-specific classes instead of conditionals
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
- # Simplified chown - just sets user , defaults to connection user. Does not touch
197
- # group. Only has effect on non-windows targets
198
- def chown(path, owner = nil)
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
- MKTMP_WIN_CMD = "$parent = [System.IO.Path]::GetTempPath();" +
205
- "[string] $name = [System.Guid]::NewGuid();" +
206
- "$tmp = New-Item -ItemType Directory -Path " +
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
- MKTMP_LINUX_CMD = "d=$(mktemp -d -p${TMPDIR:-/tmp} chef_XXXXXX); echo $d".freeze
259
+ # Platform-specific installation of packages
260
+ def install_package(target_package_path); raise NotImplementedError; end
211
261
 
212
- # Create temporary dir and return the path.
213
- # This will also set ownership to the connecting user instead of default of
214
- # root when sudo'd, so that the dir can be used to upload files using scp -
215
- # which is done as the connecting user.
216
- def mktemp
217
- if base_os == :windows
218
- res = run_command!(MKTMP_WIN_CMD)
219
- res.stdout.chomp.strip
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