chef 12.4.3 → 12.5.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -2
- data/lib/chef.rb +1 -1
- data/lib/chef/application/solo.rb +1 -1
- data/lib/chef/application/windows_service_manager.rb +17 -12
- data/lib/chef/chef_class.rb +7 -0
- data/lib/chef/chef_fs/config.rb +22 -24
- data/lib/chef/chef_fs/file_pattern.rb +4 -15
- data/lib/chef/chef_fs/file_system/cookbook_dir.rb +1 -0
- data/lib/chef/chef_fs/knife.rb +35 -7
- data/lib/chef/chef_fs/path_utils.rb +65 -34
- data/lib/chef/constants.rb +27 -0
- data/lib/chef/delayed_evaluator.rb +21 -0
- data/lib/chef/dsl/recipe.rb +20 -2
- data/lib/chef/event_dispatch/base.rb +40 -16
- data/lib/chef/event_dispatch/dsl.rb +64 -0
- data/lib/chef/exceptions.rb +6 -1
- data/lib/chef/formatters/doc.rb +3 -1
- data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +3 -1
- data/lib/chef/http/http_request.rb +1 -1
- data/lib/chef/knife/bootstrap/templates/chef-full.erb +1 -1
- data/lib/chef/knife/ssl_check.rb +3 -2
- data/lib/chef/knife/user_edit.rb +1 -2
- data/lib/chef/mixin/params_validate.rb +362 -135
- data/lib/chef/node.rb +19 -0
- data/lib/chef/platform/handler_map.rb +0 -5
- data/lib/chef/platform/rebooter.rb +1 -1
- data/lib/chef/property.rb +539 -0
- data/lib/chef/provider.rb +129 -12
- data/lib/chef/provider/deploy.rb +3 -5
- data/lib/chef/provider/lwrp_base.rb +1 -75
- data/lib/chef/provider/package.rb +1 -1
- data/lib/chef/provider/powershell_script.rb +32 -19
- data/lib/chef/provider/registry_key.rb +5 -5
- data/lib/chef/provider/service/macosx.rb +5 -1
- data/lib/chef/recipe.rb +1 -8
- data/lib/chef/resource.rb +499 -84
- data/lib/chef/resource/file/verification.rb +7 -1
- data/lib/chef/resource/lwrp_base.rb +1 -7
- data/lib/chef/run_context.rb +404 -83
- data/lib/chef/version.rb +1 -1
- data/lib/chef/win32/registry.rb +10 -2
- data/lib/chef/workstation_config_loader.rb +3 -158
- data/spec/data/run_context/cookbooks/include/recipes/default.rb +24 -0
- data/spec/data/run_context/cookbooks/include/recipes/includee.rb +3 -0
- data/spec/functional/rebooter_spec.rb +1 -1
- data/spec/functional/resource/{powershell_spec.rb → powershell_script_spec.rb} +3 -3
- data/spec/functional/win32/registry_helper_spec.rb +12 -0
- data/spec/functional/win32/service_manager_spec.rb +2 -2
- data/spec/integration/knife/chef_repo_path_spec.rb +13 -11
- data/spec/integration/recipes/recipe_dsl_spec.rb +0 -15
- data/spec/integration/recipes/resource_action_spec.rb +343 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/shared/functional/win32_service.rb +2 -1
- data/spec/unit/application/solo_spec.rb +4 -3
- data/spec/unit/chef_class_spec.rb +23 -0
- data/spec/unit/chef_fs/path_util_spec.rb +108 -0
- data/spec/unit/event_dispatch/dsl_spec.rb +87 -0
- data/spec/unit/json_compat_spec.rb +4 -3
- data/spec/unit/knife/ssl_check_spec.rb +4 -0
- data/spec/unit/mixin/params_validate_spec.rb +4 -2
- data/spec/unit/node_spec.rb +7 -0
- data/spec/unit/property/state_spec.rb +506 -0
- data/spec/unit/property/validation_spec.rb +658 -0
- data/spec/unit/property_spec.rb +968 -0
- data/spec/unit/provider/{powershell_spec.rb → powershell_script_spec.rb} +0 -0
- data/spec/unit/provider/registry_key_spec.rb +12 -0
- data/spec/unit/provider/service/macosx_spec.rb +4 -4
- data/spec/unit/provider_spec.rb +1 -3
- data/spec/unit/recipe_spec.rb +0 -4
- data/spec/unit/registry_helper_spec.rb +15 -1
- data/spec/unit/resource/file/verification_spec.rb +33 -5
- data/spec/unit/resource/{powershell_spec.rb → powershell_script_spec.rb} +0 -0
- data/spec/unit/resource_spec.rb +2 -2
- data/spec/unit/run_context/child_run_context_spec.rb +133 -0
- data/spec/unit/run_context_spec.rb +7 -0
- metadata +25 -25
- data/spec/unit/workstation_config_loader_spec.rb +0 -283
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b878f5f1ff6e11e17f28431bf13983c1eb79b7a8
|
4
|
+
data.tar.gz: 1a4961de80b9f4f9c76b0c76747a14af12818519
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8d3e8cb4a2f4cd826942d7bf6f7b250964a1197677b30f8a3f64711d568e84241a22ad0effa4e0fe721556a58bf51cf84f418a7ef2d986943a913020f9ee13f
|
7
|
+
data.tar.gz: 54ab263002c8de538b3167e25b17bbc37808d705c33817b9634d2ad7ba032c8ea9d0b02e84514d70d4178d11ab350f58a4f7a01c04c81e044e1d54ffb4f278ba
|
data/Rakefile
CHANGED
@@ -131,7 +131,7 @@ end
|
|
131
131
|
desc "Build it, tag it and ship it"
|
132
132
|
task :ship => [:clobber_package, :gem] do
|
133
133
|
sh("git tag #{VERSION}")
|
134
|
-
sh("git push
|
134
|
+
sh("git push chef --tags")
|
135
135
|
Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
|
136
136
|
sh("gem push #{built_gem}")
|
137
137
|
end
|
@@ -168,4 +168,3 @@ begin
|
|
168
168
|
rescue LoadError
|
169
169
|
puts "yard is not available. (sudo) gem install yard to generate yard documentation."
|
170
170
|
end
|
171
|
-
|
data/lib/chef.rb
CHANGED
@@ -214,7 +214,7 @@ class Chef::Application::Solo < Chef::Application
|
|
214
214
|
FileUtils.mkdir_p(recipes_path)
|
215
215
|
tarball_path = File.join(recipes_path, 'recipes.tgz')
|
216
216
|
fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path)
|
217
|
-
|
217
|
+
Mixlib::ShellOut.new("tar zxvf #{tarball_path} -C #{recipes_path}").run_command
|
218
218
|
end
|
219
219
|
|
220
220
|
# json_attribs shuld be fetched after recipe_url tarball is unpacked.
|
@@ -78,7 +78,7 @@ class Chef
|
|
78
78
|
|
79
79
|
raise ArgumentError, "Service definition is not provided" if service_options.nil?
|
80
80
|
|
81
|
-
required_options = [:service_name, :service_display_name, :
|
81
|
+
required_options = [:service_name, :service_display_name, :service_description, :service_file_path]
|
82
82
|
|
83
83
|
required_options.each do |req_option|
|
84
84
|
if !service_options.has_key?(req_option)
|
@@ -92,6 +92,7 @@ class Chef
|
|
92
92
|
@service_file_path = service_options[:service_file_path]
|
93
93
|
@service_start_name = service_options[:run_as_user]
|
94
94
|
@password = service_options[:run_as_password]
|
95
|
+
@delayed_start = service_options[:delayed_start]
|
95
96
|
end
|
96
97
|
|
97
98
|
def run(params = ARGV)
|
@@ -113,17 +114,21 @@ class Chef
|
|
113
114
|
cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR)
|
114
115
|
|
115
116
|
::Win32::Service.new(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
117
|
+
:service_name => @service_name,
|
118
|
+
:display_name => @service_display_name,
|
119
|
+
:description => @service_description,
|
120
|
+
# Prior to 0.8.5, win32-service creates interactive services by default,
|
121
|
+
# and we don't want that, so we need to override the service type.
|
122
|
+
:service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
|
123
|
+
:start_type => ::Win32::Service::SERVICE_AUTO_START,
|
124
|
+
:binary_path_name => cmd,
|
125
|
+
:service_start_name => @service_start_name,
|
126
|
+
:password => @password,
|
127
|
+
)
|
128
|
+
::Win32::Service.configure(
|
129
|
+
:service_name => @service_name,
|
130
|
+
:delayed_start => @delayed_start
|
131
|
+
) unless @delayed_start.nil?
|
127
132
|
puts "Service '#{@service_name}' has successfully been installed."
|
128
133
|
end
|
129
134
|
when 'status'
|
data/lib/chef/chef_class.rb
CHANGED
@@ -52,7 +52,14 @@ class Chef
|
|
52
52
|
#
|
53
53
|
attr_reader :run_context
|
54
54
|
|
55
|
+
# Register an event handler with user specified block
|
55
56
|
#
|
57
|
+
# @return[Chef::EventDispatch::Base] handler object
|
58
|
+
def event_handler(&block)
|
59
|
+
dsl = Chef::EventDispatch::DSL.new('Chef client DSL')
|
60
|
+
dsl.instance_eval(&block)
|
61
|
+
end
|
62
|
+
|
56
63
|
# Get the array of providers associated with a resource_name for the current node
|
57
64
|
#
|
58
65
|
# @param resource_name [Symbol] name of the resource as a symbol
|
data/lib/chef/chef_fs/config.rb
CHANGED
@@ -111,7 +111,7 @@ class Chef
|
|
111
111
|
#
|
112
112
|
def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil)
|
113
113
|
@chef_config = chef_config
|
114
|
-
@cwd = cwd
|
114
|
+
@cwd = File.expand_path(cwd)
|
115
115
|
@cookbook_version = options[:cookbook_version]
|
116
116
|
|
117
117
|
if @chef_config[:repo_mode] == 'everything' && is_hosted? && !ui.nil?
|
@@ -166,34 +166,37 @@ class Chef
|
|
166
166
|
# server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
|
167
167
|
# server_path('/home/*/chef_repo/cookbooks/blah') == nil
|
168
168
|
#
|
169
|
-
# If there are multiple
|
170
|
-
# have separate paths), and cwd+the
|
171
|
-
#
|
172
|
-
#
|
169
|
+
# If there are multiple different, manually specified paths to object locations
|
170
|
+
# (cookbooks, roles, data bags, etc. can all have separate paths), and cwd+the
|
171
|
+
# path reaches into one of them, we will return a path relative to the first
|
172
|
+
# one to match it. Otherwise we expect the path provided to be to the chef
|
173
|
+
# repo path itself. Paths that are not available on the server are not supported.
|
173
174
|
#
|
174
175
|
# Globs are allowed as well, but globs outside server paths are NOT
|
175
176
|
# (presently) supported. See above examples. TODO support that.
|
176
177
|
#
|
177
178
|
# If the path does not reach into ANY specified directory, nil is returned.
|
178
179
|
def server_path(file_path)
|
179
|
-
|
180
|
-
absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
|
180
|
+
target_path = Chef::ChefFS::PathUtils.realest_path(file_path, @cwd)
|
181
181
|
|
182
182
|
# Check all object paths (cookbooks_dir, data_bags_dir, etc.)
|
183
|
+
# These are either manually specified by the user or autogenerated relative
|
184
|
+
# to chef_repo_path.
|
183
185
|
object_paths.each_pair do |name, paths|
|
184
186
|
paths.each do |path|
|
185
|
-
|
186
|
-
if PathUtils.
|
187
|
-
|
188
|
-
return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
|
187
|
+
object_abs_path = Chef::ChefFS::PathUtils.realest_path(path, @cwd)
|
188
|
+
if relative_path = PathUtils.descendant_path(target_path, object_abs_path)
|
189
|
+
return Chef::ChefFS::PathUtils.join("/#{name}", relative_path)
|
189
190
|
end
|
190
191
|
end
|
191
192
|
end
|
192
193
|
|
193
194
|
# Check chef_repo_path
|
194
195
|
Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path|
|
195
|
-
|
196
|
-
if
|
196
|
+
# We're using realest_path here but we really don't need to - we can just expand the
|
197
|
+
# path and use realpath because a repo_path if provided *must* exist.
|
198
|
+
realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path, @cwd)
|
199
|
+
if Chef::ChefFS::PathUtils.os_path_eq?(target_path, realest_chef_repo_path)
|
197
200
|
return '/'
|
198
201
|
end
|
199
202
|
end
|
@@ -201,15 +204,10 @@ class Chef
|
|
201
204
|
nil
|
202
205
|
end
|
203
206
|
|
204
|
-
# The current directory, relative to server root
|
207
|
+
# The current directory, relative to server root. This is a case-sensitive server path.
|
208
|
+
# It only exists if the current directory is a child of one of the recognized object_paths below.
|
205
209
|
def base_path
|
206
|
-
@base_path ||=
|
207
|
-
if @chef_config[:chef_repo_path]
|
208
|
-
server_path(File.expand_path(@cwd))
|
209
|
-
else
|
210
|
-
nil
|
211
|
-
end
|
212
|
-
end
|
210
|
+
@base_path ||= server_path(@cwd)
|
213
211
|
end
|
214
212
|
|
215
213
|
# Print the given server path, relative to the current directory
|
@@ -217,10 +215,10 @@ class Chef
|
|
217
215
|
server_path = entry.path
|
218
216
|
if base_path && server_path[0,base_path.length] == base_path
|
219
217
|
if server_path == base_path
|
220
|
-
return
|
221
|
-
elsif server_path[base_path.length,1] ==
|
218
|
+
return '.'
|
219
|
+
elsif server_path[base_path.length,1] == '/'
|
222
220
|
return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
|
223
|
-
elsif base_path ==
|
221
|
+
elsif base_path == '/' && server_path[0,1] == '/'
|
224
222
|
return server_path[1, server_path.length - 1]
|
225
223
|
end
|
226
224
|
end
|
@@ -72,7 +72,7 @@ class Chef
|
|
72
72
|
def could_match_children?(path)
|
73
73
|
return false if path == '' # Empty string is not a path
|
74
74
|
|
75
|
-
argument_is_absolute =
|
75
|
+
argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
|
76
76
|
return false if is_absolute != argument_is_absolute
|
77
77
|
path = path[1,path.length-1] if argument_is_absolute
|
78
78
|
|
@@ -111,7 +111,7 @@ class Chef
|
|
111
111
|
#
|
112
112
|
# This method assumes +could_match_children?(path)+ is +true+.
|
113
113
|
def exact_child_name_under(path)
|
114
|
-
path = path[1,path.length-1] if
|
114
|
+
path = path[1,path.length-1] if Chef::ChefFS::PathUtils::is_absolute?(path)
|
115
115
|
dirs_in_path = Chef::ChefFS::PathUtils::split(path).length
|
116
116
|
return nil if exact_parts.length <= dirs_in_path
|
117
117
|
return exact_parts[dirs_in_path]
|
@@ -149,7 +149,7 @@ class Chef
|
|
149
149
|
# abc/*/def.match?('abc/foo/def') == true
|
150
150
|
# abc/*/def.match?('abc/foo') == false
|
151
151
|
def match?(path)
|
152
|
-
argument_is_absolute =
|
152
|
+
argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
|
153
153
|
return false if is_absolute != argument_is_absolute
|
154
154
|
path = path[1,path.length-1] if argument_is_absolute
|
155
155
|
!!regexp.match(path)
|
@@ -160,17 +160,6 @@ class Chef
|
|
160
160
|
pattern
|
161
161
|
end
|
162
162
|
|
163
|
-
# Given a relative file pattern and a directory, makes a new file pattern
|
164
|
-
# starting with the directory.
|
165
|
-
#
|
166
|
-
# FilePattern.relative_to('/usr/local', 'bin/*grok') == FilePattern.new('/usr/local/bin/*grok')
|
167
|
-
#
|
168
|
-
# BUG: this does not support patterns starting with <tt>..</tt>
|
169
|
-
def self.relative_to(dir, pattern)
|
170
|
-
return FilePattern.new(pattern) if pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/
|
171
|
-
FilePattern.new(Chef::ChefFS::PathUtils::join(dir, pattern))
|
172
|
-
end
|
173
|
-
|
174
163
|
private
|
175
164
|
|
176
165
|
def regexp
|
@@ -195,7 +184,7 @@ class Chef
|
|
195
184
|
|
196
185
|
def calculate
|
197
186
|
if !@regexp
|
198
|
-
@is_absolute =
|
187
|
+
@is_absolute = Chef::ChefFS::PathUtils::is_absolute?(@pattern)
|
199
188
|
|
200
189
|
full_regexp_parts = []
|
201
190
|
normalized_parts = []
|
data/lib/chef/chef_fs/knife.rb
CHANGED
@@ -17,6 +17,7 @@
|
|
17
17
|
#
|
18
18
|
|
19
19
|
require 'chef/knife'
|
20
|
+
require 'pathname'
|
20
21
|
|
21
22
|
class Chef
|
22
23
|
module ChefFS
|
@@ -63,7 +64,7 @@ class Chef
|
|
63
64
|
# --chef-repo-path forcibly overrides all other paths
|
64
65
|
if config[:chef_repo_path]
|
65
66
|
Chef::Config[:chef_repo_path] = config[:chef_repo_path]
|
66
|
-
|
67
|
+
Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name|
|
67
68
|
Chef::Config.delete("#{variable_name}_path".to_sym)
|
68
69
|
end
|
69
70
|
end
|
@@ -98,14 +99,41 @@ class Chef
|
|
98
99
|
end
|
99
100
|
|
100
101
|
def pattern_arg_from(arg)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
|
102
|
+
inferred_path = nil
|
103
|
+
if Chef::ChefFS::PathUtils.is_absolute?(arg)
|
104
|
+
# We should be able to use this as-is - but the user might have incorrectly provided
|
105
|
+
# us with a path that is based off of the OS root path instead of the Chef-FS root.
|
106
|
+
# Do a quick and dirty sanity check.
|
107
|
+
if possible_server_path = @chef_fs_config.server_path(arg)
|
108
|
+
ui.warn("The absolute path provided is suspicious: #{arg}")
|
109
|
+
ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.")
|
110
|
+
ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'")
|
111
|
+
end
|
112
|
+
# Use the original path because we can't be sure.
|
113
|
+
inferred_path = arg
|
114
|
+
elsif arg[0,1] == '~'
|
115
|
+
# Let's be nice and fix it if possible - but warn the user.
|
116
|
+
ui.warn("A path relative to a user home directory has been provided: #{arg}")
|
117
|
+
ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
|
118
|
+
inferred_path = @chef_fs_config.server_path(arg)
|
119
|
+
ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
|
120
|
+
elsif Pathname.new(arg).absolute?
|
121
|
+
# It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be
|
122
|
+
# interpreted as a Chef-FS absolute path. Again attempt to be nice but warn the user.
|
123
|
+
ui.warn("An absolute file system path that isn't a server path was provided: #{arg}")
|
124
|
+
ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
|
125
|
+
inferred_path = @chef_fs_config.server_path(arg)
|
126
|
+
ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
|
127
|
+
elsif @chef_fs_config.base_path.nil?
|
128
|
+
# These are all relative paths. We can't resolve and root paths unless we are in the
|
129
|
+
# chef repo.
|
130
|
+
ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.")
|
131
|
+
ui.error("Current working directory is '#{@chef_fs_config.cwd}'.")
|
106
132
|
exit(1)
|
133
|
+
else
|
134
|
+
inferred_path = Chef::ChefFS::PathUtils::join(@chef_fs_config.base_path, arg)
|
107
135
|
end
|
108
|
-
Chef::ChefFS::FilePattern.
|
136
|
+
Chef::ChefFS::FilePattern.new(inferred_path)
|
109
137
|
end
|
110
138
|
|
111
139
|
def format_path(entry)
|
@@ -23,31 +23,31 @@ class Chef
|
|
23
23
|
module ChefFS
|
24
24
|
class PathUtils
|
25
25
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
26
|
+
# A Chef-FS path is a path in a chef-repository that can be used to address
|
27
|
+
# both files on a local file-system as well as objects on a chef server.
|
28
|
+
# These paths are stricter than file-system paths allowed on various OSes.
|
29
|
+
# Absolute Chef-FS paths begin with "/" (on windows, "\" is acceptable as well).
|
30
|
+
# "/" is used as the path element separator (on windows, "\" is acceptable as well).
|
31
|
+
# No directory/path element may contain a literal "\" character. Any such characters
|
32
|
+
# encountered are either dealt with as separators (on windows) or as escape
|
33
|
+
# characters (on POSIX systems). Relative Chef-FS paths may use ".." or "." but
|
34
|
+
# may never use these to back-out of the root of a Chef-FS path. Any such extraneous
|
35
|
+
# ".."s are ignored.
|
36
|
+
# Chef-FS paths are case sensitive (since the paths on the server are).
|
37
|
+
# On OSes with case insensitive paths, you may be unable to locally deal with two
|
38
|
+
# objects whose server paths only differ by case. OTOH, the case of path segments
|
39
|
+
# that are outside the Chef-FS root (such as when looking at a file-system absolute
|
40
|
+
# path to discover the Chef-FS root path) are handled in accordance to the rules
|
41
|
+
# of the local file-system and OS.
|
42
42
|
|
43
43
|
def self.join(*parts)
|
44
44
|
return "" if parts.length == 0
|
45
45
|
# Determine if it started with a slash
|
46
46
|
absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/
|
47
47
|
# Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away)
|
48
|
-
parts = parts.map { |part| part.gsub(
|
48
|
+
parts = parts.map { |part| part.gsub(/^#{regexp_path_separator}+|#{regexp_path_separator}+$/, '') }
|
49
49
|
# Don't join empty bits
|
50
|
-
result = parts.select { |part| part !=
|
50
|
+
result = parts.select { |part| part != '' }.join('/')
|
51
51
|
# Put the / back on
|
52
52
|
absolute ? "/#{result}" : result
|
53
53
|
end
|
@@ -60,36 +60,67 @@ class Chef
|
|
60
60
|
Chef::ChefFS::windows? ? '[\/\\\\]' : '/'
|
61
61
|
end
|
62
62
|
|
63
|
+
# Given a server path, determines if it is absolute.
|
64
|
+
def self.is_absolute?(path)
|
65
|
+
!!(path =~ /^#{regexp_path_separator}/)
|
66
|
+
end
|
63
67
|
# Given a path which may only be partly real (i.e. /x/y/z when only /x exists,
|
64
68
|
# or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest
|
65
|
-
# part that actually exists.
|
69
|
+
# part that actually exists. The paths operated on here are not Chef-FS paths.
|
70
|
+
# These are OS paths that may contain symlinks but may not also fully exist.
|
66
71
|
#
|
67
72
|
# If /x is a symlink to /blarghle, and has no subdirectories, then:
|
68
73
|
# PathUtils.realest_path('/x/y/z') == '/blarghle/y/z'
|
69
74
|
# PathUtils.realest_path('/x/*/z') == '/blarghle/*/z'
|
70
75
|
# PathUtils.realest_path('/*/y/z') == '/*/y/z'
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
#
|
77
|
+
# TODO: Move this to wherever util/path_helper is these days.
|
78
|
+
def self.realest_path(path, cwd = Dir.pwd)
|
79
|
+
path = File.expand_path(path, cwd)
|
80
|
+
parent_path = File.dirname(path)
|
81
|
+
suffix = []
|
82
|
+
|
83
|
+
# File.dirname happens to return the path as its own dirname if you're
|
84
|
+
# at the root (such as at \\foo\bar, C:\ or /)
|
85
|
+
until parent_path == path do
|
86
|
+
# This can occur if a path such as "C:" is given. Ruby gives the parent as "C:."
|
87
|
+
# for reasons only it knows.
|
88
|
+
raise ArgumentError "Invalid path segment #{path}" if parent_path.length > path.length
|
89
|
+
begin
|
90
|
+
path = File.realpath(path)
|
91
|
+
break
|
92
|
+
rescue Errno::ENOENT
|
93
|
+
suffix << File.basename(path)
|
94
|
+
path = parent_path
|
95
|
+
parent_path = File.dirname(path)
|
81
96
|
end
|
82
97
|
end
|
98
|
+
File.join(path, *suffix.reverse)
|
83
99
|
end
|
84
100
|
|
85
|
-
|
86
|
-
|
87
|
-
|
101
|
+
# Compares two path fragments according to the case-sentitivity of the host platform.
|
102
|
+
def self.os_path_eq?(left, right)
|
103
|
+
Chef::ChefFS::windows? ? left.casecmp(right) == 0 : left == right
|
88
104
|
end
|
89
105
|
|
90
|
-
|
91
|
-
|
106
|
+
# Given two general OS-dependent file paths, determines the relative path of the
|
107
|
+
# child with respect to the ancestor. Both child and ancestor must exist and be
|
108
|
+
# fully resolved - this is strictly a lexical comparison. No trailing slashes
|
109
|
+
# and other shenanigans are allowed.
|
110
|
+
#
|
111
|
+
# TODO: Move this to util/path_helper.
|
112
|
+
def self.descendant_path(path, ancestor)
|
113
|
+
candidate_fragment = path[0, ancestor.length]
|
114
|
+
return nil unless PathUtils.os_path_eq?(candidate_fragment, ancestor)
|
115
|
+
if ancestor.length == path.length
|
116
|
+
''
|
117
|
+
elsif path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/
|
118
|
+
path[ancestor.length+1..-1]
|
119
|
+
else
|
120
|
+
nil
|
121
|
+
end
|
92
122
|
end
|
123
|
+
|
93
124
|
end
|
94
125
|
end
|
95
126
|
end
|