chef 12.4.3 → 12.5.0.alpha.1

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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -2
  3. data/lib/chef.rb +1 -1
  4. data/lib/chef/application/solo.rb +1 -1
  5. data/lib/chef/application/windows_service_manager.rb +17 -12
  6. data/lib/chef/chef_class.rb +7 -0
  7. data/lib/chef/chef_fs/config.rb +22 -24
  8. data/lib/chef/chef_fs/file_pattern.rb +4 -15
  9. data/lib/chef/chef_fs/file_system/cookbook_dir.rb +1 -0
  10. data/lib/chef/chef_fs/knife.rb +35 -7
  11. data/lib/chef/chef_fs/path_utils.rb +65 -34
  12. data/lib/chef/constants.rb +27 -0
  13. data/lib/chef/delayed_evaluator.rb +21 -0
  14. data/lib/chef/dsl/recipe.rb +20 -2
  15. data/lib/chef/event_dispatch/base.rb +40 -16
  16. data/lib/chef/event_dispatch/dsl.rb +64 -0
  17. data/lib/chef/exceptions.rb +6 -1
  18. data/lib/chef/formatters/doc.rb +3 -1
  19. data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +3 -1
  20. data/lib/chef/http/http_request.rb +1 -1
  21. data/lib/chef/knife/bootstrap/templates/chef-full.erb +1 -1
  22. data/lib/chef/knife/ssl_check.rb +3 -2
  23. data/lib/chef/knife/user_edit.rb +1 -2
  24. data/lib/chef/mixin/params_validate.rb +362 -135
  25. data/lib/chef/node.rb +19 -0
  26. data/lib/chef/platform/handler_map.rb +0 -5
  27. data/lib/chef/platform/rebooter.rb +1 -1
  28. data/lib/chef/property.rb +539 -0
  29. data/lib/chef/provider.rb +129 -12
  30. data/lib/chef/provider/deploy.rb +3 -5
  31. data/lib/chef/provider/lwrp_base.rb +1 -75
  32. data/lib/chef/provider/package.rb +1 -1
  33. data/lib/chef/provider/powershell_script.rb +32 -19
  34. data/lib/chef/provider/registry_key.rb +5 -5
  35. data/lib/chef/provider/service/macosx.rb +5 -1
  36. data/lib/chef/recipe.rb +1 -8
  37. data/lib/chef/resource.rb +499 -84
  38. data/lib/chef/resource/file/verification.rb +7 -1
  39. data/lib/chef/resource/lwrp_base.rb +1 -7
  40. data/lib/chef/run_context.rb +404 -83
  41. data/lib/chef/version.rb +1 -1
  42. data/lib/chef/win32/registry.rb +10 -2
  43. data/lib/chef/workstation_config_loader.rb +3 -158
  44. data/spec/data/run_context/cookbooks/include/recipes/default.rb +24 -0
  45. data/spec/data/run_context/cookbooks/include/recipes/includee.rb +3 -0
  46. data/spec/functional/rebooter_spec.rb +1 -1
  47. data/spec/functional/resource/{powershell_spec.rb → powershell_script_spec.rb} +3 -3
  48. data/spec/functional/win32/registry_helper_spec.rb +12 -0
  49. data/spec/functional/win32/service_manager_spec.rb +2 -2
  50. data/spec/integration/knife/chef_repo_path_spec.rb +13 -11
  51. data/spec/integration/recipes/recipe_dsl_spec.rb +0 -15
  52. data/spec/integration/recipes/resource_action_spec.rb +343 -0
  53. data/spec/spec_helper.rb +1 -0
  54. data/spec/support/shared/functional/win32_service.rb +2 -1
  55. data/spec/unit/application/solo_spec.rb +4 -3
  56. data/spec/unit/chef_class_spec.rb +23 -0
  57. data/spec/unit/chef_fs/path_util_spec.rb +108 -0
  58. data/spec/unit/event_dispatch/dsl_spec.rb +87 -0
  59. data/spec/unit/json_compat_spec.rb +4 -3
  60. data/spec/unit/knife/ssl_check_spec.rb +4 -0
  61. data/spec/unit/mixin/params_validate_spec.rb +4 -2
  62. data/spec/unit/node_spec.rb +7 -0
  63. data/spec/unit/property/state_spec.rb +506 -0
  64. data/spec/unit/property/validation_spec.rb +658 -0
  65. data/spec/unit/property_spec.rb +968 -0
  66. data/spec/unit/provider/{powershell_spec.rb → powershell_script_spec.rb} +0 -0
  67. data/spec/unit/provider/registry_key_spec.rb +12 -0
  68. data/spec/unit/provider/service/macosx_spec.rb +4 -4
  69. data/spec/unit/provider_spec.rb +1 -3
  70. data/spec/unit/recipe_spec.rb +0 -4
  71. data/spec/unit/registry_helper_spec.rb +15 -1
  72. data/spec/unit/resource/file/verification_spec.rb +33 -5
  73. data/spec/unit/resource/{powershell_spec.rb → powershell_script_spec.rb} +0 -0
  74. data/spec/unit/resource_spec.rb +2 -2
  75. data/spec/unit/run_context/child_run_context_spec.rb +133 -0
  76. data/spec/unit/run_context_spec.rb +7 -0
  77. metadata +25 -25
  78. 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: 1fb36207d513df46412aaabb2664021593d3237d
4
- data.tar.gz: c5a2775437c4a52ec7d4807bff6f0fc082550fe6
3
+ metadata.gz: b878f5f1ff6e11e17f28431bf13983c1eb79b7a8
4
+ data.tar.gz: 1a4961de80b9f4f9c76b0c76747a14af12818519
5
5
  SHA512:
6
- metadata.gz: 6a1ce60ed9ab3d13f87e56b427be169e026d22b90abba9a5d5db461af333f839e03d5135273a4c8dd0be2848dd1b737f69834a453cf7c1474f9ec98e50d669d6
7
- data.tar.gz: 29f2c9e3ef7671bed65750c32fa700fa422c92928a12ebebb37233839a453f7a24156c7c9ea40520be9b9c4fc7d0bbbc5bc1f49506c1f470f920848117f484d5
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 opscode --tags")
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
-
@@ -31,5 +31,5 @@ require 'chef/daemon'
31
31
  require 'chef/run_status'
32
32
  require 'chef/handler'
33
33
  require 'chef/handler/json_file'
34
-
34
+ require 'chef/event_dispatch/dsl'
35
35
  require 'chef/chef_class'
@@ -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
- Chef::Mixin::Command.run_command(:command => "tar zxvf #{tarball_path} -C #{recipes_path}")
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, :service_name, :service_description, :service_file_path]
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
- :service_name => @service_name,
117
- :display_name => @service_display_name,
118
- :description => @service_description,
119
- # Prior to 0.8.5, win32-service creates interactive services by default,
120
- # and we don't want that, so we need to override the service type.
121
- :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
122
- :start_type => ::Win32::Service::SERVICE_AUTO_START,
123
- :binary_path_name => cmd,
124
- :service_start_name => @service_start_name,
125
- :password => @password,
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'
@@ -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
@@ -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 paths (cookbooks, roles, data bags, etc. can all
170
- # have separate paths), and cwd+the path reaches into one of them, we will
171
- # return a path relative to that. Otherwise we will return a path to
172
- # chef_repo.
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
- pwd = File.expand_path(Dir.pwd)
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
- realest_path = Chef::ChefFS::PathUtils.realest_path(path)
186
- if PathUtils.descendant_of?(absolute_pwd, realest_path)
187
- relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path)
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
- realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path)
196
- if absolute_pwd == realest_chef_repo_path
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 ||= begin
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 == "/" && server_path[0,1] == "/"
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 = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
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 !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
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 = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
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 = !!(@pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
187
+ @is_absolute = Chef::ChefFS::PathUtils::is_absolute?(@pattern)
199
188
 
200
189
  full_regexp_parts = []
201
190
  normalized_parts = []
@@ -16,6 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
+ require 'chef/chef_fs/command_line'
19
20
  require 'chef/chef_fs/file_system/rest_list_dir'
20
21
  require 'chef/chef_fs/file_system/cookbook_subdir'
21
22
  require 'chef/chef_fs/file_system/cookbook_file'
@@ -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
- %w(acl client cookbook container data_bag environment group node role user).each do |variable_name|
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
- # TODO support absolute file paths and not just patterns? Too much?
102
- # Could be super useful in a world with multiple repo paths
103
- if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg)
104
- # Check if chef repo path is specified to give a better error message
105
- ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path")
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.relative_to(@chef_fs_config.base_path, arg)
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
- # If you are in 'source', this is what you would have to type to reach 'dest'
27
- # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
28
- # relative_to('/a/b', '/a/b') == '.'
29
- def self.relative_to(dest, source)
30
- # Skip past the common parts
31
- source_parts = Chef::ChefFS::PathUtils.split(source)
32
- dest_parts = Chef::ChefFS::PathUtils.split(dest)
33
- i = 0
34
- until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i]
35
- i+=1
36
- end
37
- # dot-dot up from 'source' to the common ancestor, then
38
- # descend to 'dest' from the common ancestor
39
- result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i]))
40
- result == '' ? '.' : result
41
- end
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 != "" }.join("/")
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
- def self.realest_path(path)
72
- path = Pathname.new(path)
73
- begin
74
- path.realpath.to_s
75
- rescue Errno::ENOENT
76
- dirname = path.dirname
77
- if dirname
78
- PathUtils.join(realest_path(dirname), path.basename.to_s)
79
- else
80
- path.to_s
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
- def self.descendant_of?(path, ancestor)
86
- path[0,ancestor.length] == ancestor &&
87
- (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/)
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
- def self.is_absolute?(path)
91
- path =~ /^#{regexp_path_separator}/
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