1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
|
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'chef/provider/directory'
require 'chef/resource/file'
require 'chef/resource/directory'
require 'chef/resource/cookbook_file'
require 'chef/mixin/file_class'
require 'chef/platform/query_helpers'
require 'chef/util/path_helper'
require 'chef/deprecation/warnings'
require 'chef/deprecation/provider/remote_directory'
require 'forwardable'
class Chef
class Provider
class RemoteDirectory < Chef::Provider::Directory
extend Forwardable
include Chef::Mixin::FileClass
provides :remote_directory
def_delegators :@new_resource, :purge, :path, :source, :cookbook, :cookbook_name
def_delegators :@new_resource, :files_rights, :files_mode, :files_group, :files_owner, :files_backup
def_delegators :@new_resource, :rights, :mode, :group, :owner
# The overwrite property on the resource. Delegates to new_resource but can be mutated.
#
# @return [Boolean] if we are overwriting
#
def overwrite?
@overwrite = new_resource.overwrite if @overwrite.nil?
!!@overwrite
end
attr_accessor :managed_files
# Hash containing keys of the paths for all the files that we sync, plus all their
# parent directories.
#
# @return [Set] Ruby Set of the files that we manage
#
def managed_files
@managed_files ||= Set.new
end
# Handle action :create.
#
def action_create
super
# Transfer files
files_to_transfer.each do |cookbook_file_relative_path|
create_cookbook_file(cookbook_file_relative_path)
# parent directories and file being transferred need to not be removed in the purge
add_managed_file(cookbook_file_relative_path)
end
purge_unmanaged_files
end
# Handle action :create_if_missing.
#
def action_create_if_missing
# if this action is called, ignore the existing overwrite flag
@overwrite = false
action_create
end
private
# Add a file and its parent directories to the managed_files Hash.
#
# @param [String] cookbook_file_relative_path relative path to the file
# @api private
#
def add_managed_file(cookbook_file_relative_path)
if purge
Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(path, cookbook_file_relative_path))).descend do |d|
managed_files.add(d.to_s)
end
end
end
# Remove all files not in the managed_files Set.
#
# @api private
#
def purge_unmanaged_files
if purge
Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'), ::File::FNM_DOTMATCH).sort!.reverse!.each do |file|
# skip '.' and '..'
next if ['.','..'].include?(Pathname.new(file).basename().to_s)
# Clean the path. This is required because of the ::File.join
file = Chef::Util::PathHelper.cleanpath(file)
# Skip files that we've sync'd and their parent dirs
next if managed_files.include?(file)
if ::File.directory?(file)
if !Chef::Platform.windows? && file_class.symlink?(file.dup)
# Unix treats dir symlinks as files
purge_file(file)
else
# Unix dirs are dirs, Windows dirs and dir symlinks are dirs
purge_directory(file)
end
else
purge_file(file)
end
end
end
end
# Use a Chef directory sub-resource to remove a directory.
#
# @param [String] dir The path of the directory to remove
# @api private
#
def purge_directory(dir)
res = Chef::Resource::Directory.new(dir, run_context)
res.run_action(:delete)
new_resource.updated_by_last_action(true) if res.updated?
end
# Use a Chef file sub-resource to remove a file.
#
# @param [String] file The path of the file to remove
# @api private
#
def purge_file(file)
res = Chef::Resource::File.new(file, run_context)
res.run_action(:delete)
new_resource.updated_by_last_action(true) if res.updated?
end
# Get the files to tranfer. This returns files in lexicographical sort order.
#
# FIXME: it should do breadth-first, see CHEF-5080 (please use a performant sort)
#
# @return Array<String> The list of files to transfer
# @api private
#
def files_to_transfer
cookbook = run_context.cookbook_collection[resource_cookbook]
files = cookbook.relative_filenames_in_preferred_directory(node, :files, source)
files.sort_by! { |x| x.count(::File::SEPARATOR) }
end
# Either the explicit cookbook that the user sets on the resource, or the implicit
# cookbook_name that the resource was declared in.
#
# @return [String] Cookbook to get file from.
# @api private
#
def resource_cookbook
cookbook || cookbook_name
end
# If we are overwriting, then cookbook_file sub-resources should all be action :create,
# otherwise they should be :create_if_missing
#
# @return [Symbol] Action to take on cookbook_file sub-resources
# @api private
#
def action_for_cookbook_file
overwrite? ? :create : :create_if_missing
end
# This creates and uses a cookbook_file resource to sync a single file from the cookbook.
#
# @param [String] cookbook_file_relative_path The relative path to the cookbook file
# @api private
#
def create_cookbook_file(cookbook_file_relative_path)
full_path = ::File.join(path, cookbook_file_relative_path)
ensure_directory_exists(::File.dirname(full_path))
res = cookbook_file_resource(full_path, cookbook_file_relative_path)
res.run_action(action_for_cookbook_file)
new_resource.updated_by_last_action(true) if res.updated?
end
# This creates the cookbook_file resource for use by create_cookbook_file.
#
# @param [String] target_path Path on the system to create
# @param [String] relative_source_path Relative path in the cookbook to the base source
# @return [Chef::Resource::CookbookFile] The built cookbook_file resource
# @api private
#
def cookbook_file_resource(target_path, relative_source_path)
res = Chef::Resource::CookbookFile.new(target_path, run_context)
res.cookbook_name = resource_cookbook
res.source(::File.join(source, relative_source_path))
if Chef::Platform.windows? && files_rights
files_rights.each_pair do |permission, *args|
res.rights(permission, *args)
end
end
res.mode(files_mode) if files_mode
res.group(files_group) if files_group
res.owner(files_owner) if files_owner
res.backup(files_backup) if files_backup
res
end
# This creates and uses a directory resource to create a directory if it is needed.
#
# @param [String] dir The path to the directory to create.
# @api private
#
def ensure_directory_exists(dir)
# doing the check here and skipping the resource should be more performant
unless ::File.directory?(dir)
res = directory_resource(dir)
res.run_action(:create)
new_resource.updated_by_last_action(true) if res.updated?
end
end
# This creates the directory resource for ensure_directory_exists.
#
# @param [String] dir Directory path on the system
# @return [Chef::Resource::Directory] The built directory resource
# @api private
#
def directory_resource(dir)
res = Chef::Resource::Directory.new(dir, run_context)
res.cookbook_name = resource_cookbook
if Chef::Platform.windows? && rights
# rights are only meant to be applied to the toppest-level directory;
# Windows will handle inheritance.
if dir == path
rights.each do |r|
r = r.dup # do not update the new_resource
permissions = r.delete(:permissions)
principals = r.delete(:principals)
res.rights(permissions, principals, r)
end
end
end
res.mode(mode) if mode
res.group(group) if group
res.owner(owner) if owner
res.recursive(true)
res
end
#
# Add back deprecated methods and aliases that are internally unused and should be removed in Chef-13
#
extend Chef::Deprecation::Warnings
include Chef::Deprecation::Provider::RemoteDirectory
add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteDirectory.instance_methods)
alias_method :resource_for_directory, :directory_resource
add_deprecation_warnings_for([:resource_for_directory])
end
end
end
|