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.
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