knife-essentials 1.2 → 1.2.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.
@@ -4,12 +4,12 @@ class Chef
4
4
  class Knife
5
5
  remove_const(:Raw) if const_defined?(:Raw) && Raw.name == 'Chef::Knife::Raw' # override Chef's version
6
6
  class Raw < Chef::Knife
7
- ChefFS = ::ChefFS
8
7
  banner "knife raw REQUEST_PATH"
9
8
 
10
9
  deps do
11
10
  require 'json'
12
- require 'chef_fs/data_handler/data_handler_base'
11
+ require 'chef/rest'
12
+ require 'chef/config'
13
13
  require 'chef_fs/raw_request'
14
14
  end
15
15
 
@@ -48,7 +48,7 @@ class Chef
48
48
  end
49
49
  chef_rest = Chef::REST.new(Chef::Config[:chef_server_url])
50
50
  begin
51
- output ChefFS::RawRequest.api_request(chef_rest, config[:method].to_sym, chef_rest.create_url(name_args[0]), {}, data)
51
+ output ::ChefFS::RawRequest.api_request(chef_rest, config[:method].to_sym, chef_rest.create_url(name_args[0]), {}, data)
52
52
  rescue Timeout::Error => e
53
53
  ui.error "Server timeout"
54
54
  exit 1
@@ -19,13 +19,7 @@ ERROR: chef-zero must be installed to run "knife serve"! To install:
19
19
  EOM
20
20
  exit(1)
21
21
  end
22
- require 'chef_zero/data_store/memory_store'
23
- require 'chef_fs/file_pattern'
24
- require 'chef_fs/file_system'
25
- require 'chef_fs/file_system/not_found_error'
26
- require 'chef_fs/file_system/memory_root'
27
- require 'chef_zero/data_store/data_already_exists_error'
28
- require 'chef_zero/data_store/data_not_found_error'
22
+ require 'chef_fs/chef_fs_data_store'
29
23
  end
30
24
 
31
25
  option :remote,
@@ -50,7 +44,7 @@ EOM
50
44
 
51
45
  def run
52
46
  server_options = {}
53
- server_options[:data_store] = ChefFSDataStore.new(proc { config[:remote] ? create_chef_fs : create_local_fs })
47
+ server_options[:data_store] = ChefFS::ChefFSDataStore.new(proc { config[:remote] ? create_chef_fs : create_local_fs })
54
48
  server_options[:log_level] = Chef::Log.level
55
49
  server_options[:host] = config[:host] if config[:host]
56
50
  server_options[:port] = config[:port] ? config[:port].to_i : 4000
@@ -58,349 +52,6 @@ EOM
58
52
 
59
53
  ChefZero::Server.new(server_options).start(:publish => true)
60
54
  end
61
-
62
- class ChefFSDataStore
63
- def initialize(chef_fs)
64
- @chef_fs = chef_fs
65
- @memory_store = ChefZero::DataStore::MemoryStore.new
66
- end
67
-
68
- def chef_fs
69
- @chef_fs.call
70
- end
71
-
72
- MEMORY_PATHS = %w(sandboxes file_store)
73
-
74
- def create_dir(path, name, *options)
75
- if use_memory_store?(path)
76
- @memory_store.create_dir(path, name, *options)
77
- else
78
- with_dir(path) do |parent|
79
- parent.create_child(chef_fs_filename(path + [name]), nil)
80
- end
81
- end
82
- end
83
-
84
- def create(path, name, data, *options)
85
- if use_memory_store?(path)
86
- @memory_store.create(path, name, data, *options)
87
-
88
- elsif path[0] == 'cookbooks' && path.length == 2
89
- # Do nothing. The entry gets created when the cookbook is created.
90
-
91
- else
92
- if !data.is_a?(String)
93
- raise "set only works with strings"
94
- end
95
-
96
- with_dir(path) do |parent|
97
- parent.create_child(chef_fs_filename(path + [name]), data)
98
- end
99
- end
100
- end
101
-
102
- def get(path, request=nil)
103
- if use_memory_store?(path)
104
- @memory_store.get(path)
105
-
106
- elsif path[0] == 'file_store' && path[1] == 'repo'
107
- entry = ChefFS::FileSystem.resolve_path(chef_fs, path[2..-1].join('/'))
108
- begin
109
- entry.read
110
- rescue ChefFS::FileSystem::NotFoundError => e
111
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
112
- end
113
-
114
- else
115
- with_entry(path) do |entry|
116
- if path[0] == 'cookbooks' && path.length == 3
117
- # get /cookbooks/NAME/version
118
- result = entry.chef_object.to_hash
119
- result.each_pair do |key, value|
120
- if value.is_a?(Array)
121
- value.each do |file|
122
- if file.is_a?(Hash) && file.has_key?('checksum')
123
- relative = ['file_store', 'repo', 'cookbooks']
124
- if Chef::Config.versioned_cookbooks
125
- relative << "#{path[1]}-#{path[2]}"
126
- else
127
- relative << path[1]
128
- end
129
- relative = relative + file[:path].split('/')
130
- file['url'] = ChefZero::RestBase::build_uri(request.base_uri, relative)
131
- end
132
- end
133
- end
134
- end
135
- JSON.pretty_generate(result)
136
-
137
- else
138
- entry.read
139
- end
140
- end
141
- end
142
- end
143
-
144
- def set(path, data, *options)
145
- if use_memory_store?(path)
146
- @memory_store.set(path, data, *options)
147
- else
148
- if !data.is_a?(String)
149
- raise "set only works with strings: #{path} = #{data.inspect}"
150
- end
151
-
152
- # Write out the files!
153
- if path[0] == 'cookbooks' && path.length == 3
154
- write_cookbook(path, data, *options)
155
- else
156
- with_dir(path[0..-2]) do |parent|
157
- parent.create_child(chef_fs_filename(path), data)
158
- end
159
- end
160
- end
161
- end
162
-
163
- def delete(path)
164
- if use_memory_store?(path)
165
- @memory_store.delete(path)
166
- else
167
- with_entry(path) do |entry|
168
- if path[0] == 'cookbooks' && path.length >= 3
169
- entry.delete(true)
170
- else
171
- entry.delete
172
- end
173
- end
174
- end
175
- end
176
-
177
- def delete_dir(path, *options)
178
- if use_memory_store?(path)
179
- @memory_store.delete_dir(path, *options)
180
- else
181
- with_entry(path) do |entry|
182
- entry.delete(options.include?(:recursive))
183
- end
184
- end
185
- end
186
-
187
- def list(path)
188
- if use_memory_store?(path)
189
- @memory_store.list(path)
190
-
191
- elsif path[0] == 'cookbooks' && path.length == 1
192
- with_entry(path) do |entry|
193
- begin
194
- if Chef::Config.versioned_cookbooks
195
- # /cookbooks/name-version -> /cookbooks/name
196
- entry.children.map { |child| split_name_version(child.name)[0] }.uniq
197
- else
198
- entry.children.map { |child| child.name }
199
- end
200
- rescue ChefFS::FileSystem::NotFoundError
201
- # If the cookbooks dir doesn't exist, we have no cookbooks (not 404)
202
- []
203
- end
204
- end
205
-
206
- elsif path[0] == 'cookbooks' && path.length == 2
207
- if Chef::Config.versioned_cookbooks
208
- # list /cookbooks/name = filter /cookbooks/name-version down to name
209
- entry.children.map { |child| split_name_version(child.name) }.
210
- select { |name, version| name == path[1] }.
211
- map { |name, version| version }.to_a
212
- else
213
- # list /cookbooks/name = <single version>
214
- version = get_single_cookbook_version(path)
215
- [version]
216
- end
217
-
218
- else
219
- with_entry(path) do |entry|
220
- begin
221
- entry.children.map { |c| zero_filename(c) }.sort
222
- rescue ChefFS::FileSystem::NotFoundError
223
- # /cookbooks, /data, etc. never return 404
224
- if path_always_exists?(path)
225
- []
226
- else
227
- raise
228
- end
229
- end
230
- end
231
- end
232
- end
233
-
234
- def exists?(path)
235
- if use_memory_store?(path)
236
- @memory_store.exists?(path)
237
- else
238
- path_always_exists?(path) || ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
239
- end
240
- end
241
-
242
- def exists_dir?(path)
243
- if use_memory_store?(path)
244
- @memory_store.exists_dir?(path)
245
- elsif path[0] == 'cookbooks' && path.length == 2
246
- list([ path[0] ]).include?(path[1])
247
- else
248
- ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
249
- end
250
- end
251
-
252
- private
253
-
254
- def use_memory_store?(path)
255
- return path[0] == 'sandboxes' || path[0] == 'file_store' && path[1] == 'checksums' || path == [ 'environments', '_default' ]
256
- end
257
-
258
- def write_cookbook(path, data, *options)
259
- # Create a little ChefFS memory filesystem with the data
260
- if Chef::Config.versioned_cookbooks
261
- cookbook_path = "cookbooks/#{path[1]}-#{path[2]}"
262
- else
263
- cookbook_path = "cookbooks/#{path[1]}"
264
- end
265
- puts "Write cookbook #{cookbook_path}"
266
- cookbook_fs = ChefFS::FileSystem::MemoryRoot.new('uploading')
267
- cookbook = JSON.parse(data, :create_additions => false)
268
- cookbook.each_pair do |key, value|
269
- if value.is_a?(Array)
270
- value.each do |file|
271
- if file.is_a?(Hash) && file.has_key?('checksum')
272
- file_data = @memory_store.get(['file_store', 'checksums', file['checksum']])
273
- cookbook_fs.add_file("#{cookbook_path}/#{file['path']}", file_data)
274
- end
275
- end
276
- end
277
- end
278
-
279
- # Use the copy/diff algorithm to copy it down so we don't destroy
280
- # chefignored data. This is terribly un-thread-safe.
281
- ChefFS::FileSystem.copy_to(ChefFS::FilePattern.new("/#{cookbook_path}"), cookbook_fs, chef_fs, nil, {:purge => true})
282
- end
283
-
284
- def split_name_version(entry_name)
285
- name_version = entry_name.split('-')
286
- name = name_version[0..-2].join('-')
287
- version = name_version[-1]
288
- [name,version]
289
- end
290
-
291
- def to_chef_fs_path(path)
292
- _to_chef_fs_path(path).join('/')
293
- end
294
-
295
- def chef_fs_filename(path)
296
- _to_chef_fs_path(path)[-1]
297
- end
298
-
299
- def _to_chef_fs_path(path)
300
- if path[0] == 'data'
301
- path = path.dup
302
- path[0] = 'data_bags'
303
- if path.length >= 3
304
- path[2] = "#{path[2]}.json"
305
- end
306
- elsif path[0] == 'cookbooks'
307
- if path.length == 2
308
- raise ChefZero::DataStore::DataNotFoundError.new(path)
309
- elsif Chef::Config.versioned_cookbooks
310
- if path.length >= 3
311
- # cookbooks/name/version -> cookbooks/name-version
312
- path = [ path[0], "#{path[1]}-#{path[2]}" ] + path[3..-1]
313
- end
314
- else
315
- if path.length >= 3
316
- # cookbooks/name/version/... -> /cookbooks/name/... iff metadata says so
317
- version = get_single_cookbook_version(path)
318
- if path[2] == version
319
- path = path[0..1] + path[3..-1]
320
- else
321
- raise ChefZero::DataStore::DataNotFoundError.new(path)
322
- end
323
- end
324
- end
325
- elsif path.length == 2
326
- path = path.dup
327
- path[1] = "#{path[1]}.json"
328
- end
329
- path
330
- end
331
-
332
- def to_zero_path(entry)
333
- path = entry.path.split('/')[1..-1]
334
- if path[0] == 'data_bags'
335
- path = path.dup
336
- path[0] = 'data'
337
- if path.length >= 3
338
- path[2] = path[2][0..-6]
339
- end
340
-
341
- elsif path[0] == 'cookbooks'
342
- if Chef::Config.versioned_cookbooks
343
- # cookbooks/name-version/... -> cookbooks/name/version/...
344
- if path.length >= 2
345
- name, version = split_name_version(path[1])
346
- path = [ path[0], name, version ] + path[2..-1]
347
- end
348
- else
349
- if path.length >= 2
350
- # cookbooks/name/... -> cookbooks/name/version/...
351
- version = get_single_cookbook_version(path)
352
- path = path[0..1] + [version] + path[2..-1]
353
- end
354
- end
355
-
356
- elsif path.length == 2 && path[0] != 'cookbooks'
357
- path = path.dup
358
- path[1] = path[1][0..-6]
359
- end
360
- path
361
- end
362
-
363
- def zero_filename(entry)
364
- to_zero_path(entry)[-1]
365
- end
366
-
367
- def path_always_exists?(path)
368
- return path.length == 1 && %w(clients cookbooks data environments nodes roles users).include?(path[0])
369
- end
370
-
371
- def with_entry(path)
372
- begin
373
- yield ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path))
374
- rescue ChefFS::FileSystem::NotFoundError => e
375
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
376
- end
377
- end
378
-
379
- def with_dir(path)
380
- begin
381
- yield get_dir(_to_chef_fs_path(path), true)
382
- rescue ChefFS::FileSystem::NotFoundError => e
383
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
384
- end
385
- end
386
-
387
- def get_dir(path, create=false)
388
- result = ChefFS::FileSystem.resolve_path(chef_fs, path.join('/'))
389
- if result.exists?
390
- result
391
- elsif create
392
- get_dir(path[0..-2], create).create_child(result.name, nil)
393
- else
394
- raise ChefZero::DataStore::DataNotFoundError.new(path)
395
- end
396
- end
397
-
398
- def get_single_cookbook_version(path)
399
- dir = ChefFS::FileSystem.resolve_path(chef_fs, path[0..1].join('/'))
400
- metadata = ChefZero::CookbookData.metadata_from(dir, path[1], nil, [])
401
- metadata[:version] || '0.0.0'
402
- end
403
- end
404
55
  end
405
56
  end
406
57
  end
@@ -0,0 +1,373 @@
1
+ #
2
+ # Author:: John Keiser (<jkeiser@opscode.com>)
3
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef_zero/data_store/memory_store'
20
+ require 'chef_zero/data_store/data_already_exists_error'
21
+ require 'chef_zero/data_store/data_not_found_error'
22
+ require 'chef_fs/file_pattern'
23
+ require 'chef_fs/file_system'
24
+ require 'chef_fs/file_system/not_found_error'
25
+ require 'chef_fs/file_system/memory_root'
26
+
27
+ module ChefFS
28
+ class ChefFSDataStore
29
+ def initialize(chef_fs)
30
+ @chef_fs = chef_fs
31
+ @memory_store = ChefZero::DataStore::MemoryStore.new
32
+ end
33
+
34
+ def publish_description
35
+ "Reading and writing data to #{chef_fs.fs_description}"
36
+ end
37
+
38
+ def chef_fs
39
+ @chef_fs.call
40
+ end
41
+
42
+ MEMORY_PATHS = %w(sandboxes file_store)
43
+
44
+ def create_dir(path, name, *options)
45
+ if use_memory_store?(path)
46
+ @memory_store.create_dir(path, name, *options)
47
+ else
48
+ with_dir(path) do |parent|
49
+ parent.create_child(chef_fs_filename(path + [name]), nil)
50
+ end
51
+ end
52
+ end
53
+
54
+ def create(path, name, data, *options)
55
+ if use_memory_store?(path)
56
+ @memory_store.create(path, name, data, *options)
57
+
58
+ elsif path[0] == 'cookbooks' && path.length == 2
59
+ # Do nothing. The entry gets created when the cookbook is created.
60
+
61
+ else
62
+ if !data.is_a?(String)
63
+ raise "set only works with strings"
64
+ end
65
+
66
+ with_dir(path) do |parent|
67
+ parent.create_child(chef_fs_filename(path + [name]), data)
68
+ end
69
+ end
70
+ end
71
+
72
+ def get(path, request=nil)
73
+ if use_memory_store?(path)
74
+ @memory_store.get(path)
75
+
76
+ elsif path[0] == 'file_store' && path[1] == 'repo'
77
+ entry = ChefFS::FileSystem.resolve_path(chef_fs, path[2..-1].join('/'))
78
+ begin
79
+ entry.read
80
+ rescue ChefFS::FileSystem::NotFoundError => e
81
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
82
+ end
83
+
84
+ else
85
+ with_entry(path) do |entry|
86
+ if path[0] == 'cookbooks' && path.length == 3
87
+ # get /cookbooks/NAME/version
88
+ result = entry.chef_object.to_hash
89
+ result.each_pair do |key, value|
90
+ if value.is_a?(Array)
91
+ value.each do |file|
92
+ if file.is_a?(Hash) && file.has_key?('checksum')
93
+ relative = ['file_store', 'repo', 'cookbooks']
94
+ if Chef::Config.versioned_cookbooks
95
+ relative << "#{path[1]}-#{path[2]}"
96
+ else
97
+ relative << path[1]
98
+ end
99
+ relative = relative + file[:path].split('/')
100
+ file['url'] = ChefZero::RestBase::build_uri(request.base_uri, relative)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ JSON.pretty_generate(result)
106
+
107
+ else
108
+ entry.read
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ def set(path, data, *options)
115
+ if use_memory_store?(path)
116
+ @memory_store.set(path, data, *options)
117
+ else
118
+ if !data.is_a?(String)
119
+ raise "set only works with strings: #{path} = #{data.inspect}"
120
+ end
121
+
122
+ # Write out the files!
123
+ if path[0] == 'cookbooks' && path.length == 3
124
+ write_cookbook(path, data, *options)
125
+ else
126
+ with_dir(path[0..-2]) do |parent|
127
+ parent.create_child(chef_fs_filename(path), data)
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ def delete(path)
134
+ if use_memory_store?(path)
135
+ @memory_store.delete(path)
136
+ else
137
+ with_entry(path) do |entry|
138
+ if path[0] == 'cookbooks' && path.length >= 3
139
+ entry.delete(true)
140
+ else
141
+ entry.delete
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ def delete_dir(path, *options)
148
+ if use_memory_store?(path)
149
+ @memory_store.delete_dir(path, *options)
150
+ else
151
+ with_entry(path) do |entry|
152
+ entry.delete(options.include?(:recursive))
153
+ end
154
+ end
155
+ end
156
+
157
+ def list(path)
158
+ if use_memory_store?(path)
159
+ @memory_store.list(path)
160
+
161
+ elsif path[0] == 'cookbooks' && path.length == 1
162
+ with_entry(path) do |entry|
163
+ begin
164
+ if Chef::Config.versioned_cookbooks
165
+ # /cookbooks/name-version -> /cookbooks/name
166
+ entry.children.map { |child| split_name_version(child.name)[0] }.uniq
167
+ else
168
+ entry.children.map { |child| child.name }
169
+ end
170
+ rescue ChefFS::FileSystem::NotFoundError
171
+ # If the cookbooks dir doesn't exist, we have no cookbooks (not 404)
172
+ []
173
+ end
174
+ end
175
+
176
+ elsif path[0] == 'cookbooks' && path.length == 2
177
+ if Chef::Config.versioned_cookbooks
178
+ # list /cookbooks/name = filter /cookbooks/name-version down to name
179
+ entry.children.map { |child| split_name_version(child.name) }.
180
+ select { |name, version| name == path[1] }.
181
+ map { |name, version| version }.to_a
182
+ else
183
+ # list /cookbooks/name = <single version>
184
+ version = get_single_cookbook_version(path)
185
+ [version]
186
+ end
187
+
188
+ else
189
+ with_entry(path) do |entry|
190
+ begin
191
+ entry.children.map { |c| zero_filename(c) }.sort
192
+ rescue ChefFS::FileSystem::NotFoundError
193
+ # /cookbooks, /data, etc. never return 404
194
+ if path_always_exists?(path)
195
+ []
196
+ else
197
+ raise
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ def exists?(path)
205
+ if use_memory_store?(path)
206
+ @memory_store.exists?(path)
207
+ else
208
+ path_always_exists?(path) || ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
209
+ end
210
+ end
211
+
212
+ def exists_dir?(path)
213
+ if use_memory_store?(path)
214
+ @memory_store.exists_dir?(path)
215
+ elsif path[0] == 'cookbooks' && path.length == 2
216
+ list([ path[0] ]).include?(path[1])
217
+ else
218
+ ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
219
+ end
220
+ end
221
+
222
+ private
223
+
224
+ def use_memory_store?(path)
225
+ return path[0] == 'sandboxes' || path[0] == 'file_store' && path[1] == 'checksums' || path == [ 'environments', '_default' ]
226
+ end
227
+
228
+ def write_cookbook(path, data, *options)
229
+ # Create a little ChefFS memory filesystem with the data
230
+ if Chef::Config.versioned_cookbooks
231
+ cookbook_path = "cookbooks/#{path[1]}-#{path[2]}"
232
+ else
233
+ cookbook_path = "cookbooks/#{path[1]}"
234
+ end
235
+ cookbook_fs = ChefFS::FileSystem::MemoryRoot.new('uploading')
236
+ cookbook = JSON.parse(data, :create_additions => false)
237
+ cookbook.each_pair do |key, value|
238
+ if value.is_a?(Array)
239
+ value.each do |file|
240
+ if file.is_a?(Hash) && file.has_key?('checksum')
241
+ file_data = @memory_store.get(['file_store', 'checksums', file['checksum']])
242
+ cookbook_fs.add_file("#{cookbook_path}/#{file['path']}", file_data)
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ # Use the copy/diff algorithm to copy it down so we don't destroy
249
+ # chefignored data. This is terribly un-thread-safe.
250
+ ChefFS::FileSystem.copy_to(ChefFS::FilePattern.new("/#{cookbook_path}"), cookbook_fs, chef_fs, nil, {:purge => true})
251
+ end
252
+
253
+ def split_name_version(entry_name)
254
+ name_version = entry_name.split('-')
255
+ name = name_version[0..-2].join('-')
256
+ version = name_version[-1]
257
+ [name,version]
258
+ end
259
+
260
+ def to_chef_fs_path(path)
261
+ _to_chef_fs_path(path).join('/')
262
+ end
263
+
264
+ def chef_fs_filename(path)
265
+ _to_chef_fs_path(path)[-1]
266
+ end
267
+
268
+ def _to_chef_fs_path(path)
269
+ if path[0] == 'data'
270
+ path = path.dup
271
+ path[0] = 'data_bags'
272
+ if path.length >= 3
273
+ path[2] = "#{path[2]}.json"
274
+ end
275
+ elsif path[0] == 'cookbooks'
276
+ if path.length == 2
277
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
278
+ elsif Chef::Config.versioned_cookbooks
279
+ if path.length >= 3
280
+ # cookbooks/name/version -> cookbooks/name-version
281
+ path = [ path[0], "#{path[1]}-#{path[2]}" ] + path[3..-1]
282
+ end
283
+ else
284
+ if path.length >= 3
285
+ # cookbooks/name/version/... -> /cookbooks/name/... iff metadata says so
286
+ version = get_single_cookbook_version(path)
287
+ if path[2] == version
288
+ path = path[0..1] + path[3..-1]
289
+ else
290
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
291
+ end
292
+ end
293
+ end
294
+ elsif path.length == 2
295
+ path = path.dup
296
+ path[1] = "#{path[1]}.json"
297
+ end
298
+ path
299
+ end
300
+
301
+ def to_zero_path(entry)
302
+ path = entry.path.split('/')[1..-1]
303
+ if path[0] == 'data_bags'
304
+ path = path.dup
305
+ path[0] = 'data'
306
+ if path.length >= 3
307
+ path[2] = path[2][0..-6]
308
+ end
309
+
310
+ elsif path[0] == 'cookbooks'
311
+ if Chef::Config.versioned_cookbooks
312
+ # cookbooks/name-version/... -> cookbooks/name/version/...
313
+ if path.length >= 2
314
+ name, version = split_name_version(path[1])
315
+ path = [ path[0], name, version ] + path[2..-1]
316
+ end
317
+ else
318
+ if path.length >= 2
319
+ # cookbooks/name/... -> cookbooks/name/version/...
320
+ version = get_single_cookbook_version(path)
321
+ path = path[0..1] + [version] + path[2..-1]
322
+ end
323
+ end
324
+
325
+ elsif path.length == 2 && path[0] != 'cookbooks'
326
+ path = path.dup
327
+ path[1] = path[1][0..-6]
328
+ end
329
+ path
330
+ end
331
+
332
+ def zero_filename(entry)
333
+ to_zero_path(entry)[-1]
334
+ end
335
+
336
+ def path_always_exists?(path)
337
+ return path.length == 1 && %w(clients cookbooks data environments nodes roles users).include?(path[0])
338
+ end
339
+
340
+ def with_entry(path)
341
+ begin
342
+ yield ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path))
343
+ rescue ChefFS::FileSystem::NotFoundError => e
344
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
345
+ end
346
+ end
347
+
348
+ def with_dir(path)
349
+ begin
350
+ yield get_dir(_to_chef_fs_path(path), true)
351
+ rescue ChefFS::FileSystem::NotFoundError => e
352
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
353
+ end
354
+ end
355
+
356
+ def get_dir(path, create=false)
357
+ result = ChefFS::FileSystem.resolve_path(chef_fs, path.join('/'))
358
+ if result.exists?
359
+ result
360
+ elsif create
361
+ get_dir(path[0..-2], create).create_child(result.name, nil)
362
+ else
363
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
364
+ end
365
+ end
366
+
367
+ def get_single_cookbook_version(path)
368
+ dir = ChefFS::FileSystem.resolve_path(chef_fs, path[0..1].join('/'))
369
+ metadata = ChefZero::CookbookData.metadata_from(dir, path[1], nil, [])
370
+ metadata[:version] || '0.0.0'
371
+ end
372
+ end
373
+ end
@@ -0,0 +1,193 @@
1
+ #
2
+ # Author:: John Keiser (<jkeiser@opscode.com>)
3
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/log'
20
+ require 'chef_fs/path_utils'
21
+
22
+ module ChefFS
23
+ #
24
+ # Helpers to take Chef::Config and create chef_fs and local_fs from it
25
+ #
26
+ class Config
27
+ def initialize(chef_config = Chef::Config, cwd = Dir.pwd)
28
+ @chef_config = chef_config
29
+ @cwd = cwd
30
+ configure_repo_paths
31
+ end
32
+
33
+ PATH_VARIABLES = %w(acl_path client_path cookbook_path container_path data_bag_path environment_path group_path node_path role_path user_path)
34
+
35
+ def chef_fs
36
+ @chef_fs ||= create_chef_fs
37
+ end
38
+
39
+ def create_chef_fs
40
+ require 'chef_fs/file_system/chef_server_root_dir'
41
+ ChefFS::FileSystem::ChefServerRootDir.new("remote", @chef_config)
42
+ end
43
+
44
+ def local_fs
45
+ @local_fs ||= create_local_fs
46
+ end
47
+
48
+ def create_local_fs
49
+ require 'chef_fs/file_system/chef_repository_file_system_root_dir'
50
+ ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths)
51
+ end
52
+
53
+ # Returns the given real path's location relative to the server root.
54
+ #
55
+ # If chef_repo is /home/jkeiser/chef_repo,
56
+ # and pwd is /home/jkeiser/chef_repo/cookbooks,
57
+ # server_path('blah') == '/cookbooks/blah'
58
+ # server_path('../roles/blah.json') == '/roles/blah'
59
+ # server_path('../../readme.txt') == nil
60
+ # server_path('*/*ab*') == '/cookbooks/*/*ab*'
61
+ # server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
62
+ # server_path('/home/*/chef_repo/cookbooks/blah') == nil
63
+ #
64
+ # If there are multiple paths (cookbooks, roles, data bags, etc. can all
65
+ # have separate paths), and cwd+the path reaches into one of them, we will
66
+ # return a path relative to that. Otherwise we will return a path to
67
+ # chef_repo.
68
+ #
69
+ # Globs are allowed as well, but globs outside server paths are NOT
70
+ # (presently) supported. See above examples. TODO support that.
71
+ #
72
+ # If the path does not reach into ANY specified directory, nil is returned.
73
+ def server_path(file_path)
74
+ pwd = File.expand_path(Dir.pwd)
75
+ absolute_path = ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
76
+
77
+ # Check all object paths (cookbooks_dir, data_bags_dir, etc.)
78
+ object_paths.each_pair do |name, paths|
79
+ paths.each do |path|
80
+ realest_path = ChefFS::PathUtils.realest_path(path)
81
+ if absolute_path[0,realest_path.length] == realest_path &&
82
+ (absolute_path.length == realest_path.length ||
83
+ absolute_path[realest_path.length,1] =~ /#{PathUtils.regexp_path_separator}/)
84
+ relative_path = ChefFS::PathUtils::relative_to(absolute_path, realest_path)
85
+ return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
86
+ end
87
+ end
88
+ end
89
+
90
+ # Check chef_repo_path
91
+ Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path|
92
+ realest_chef_repo_path = ChefFS::PathUtils.realest_path(chef_repo_path)
93
+ if absolute_path == realest_chef_repo_path
94
+ return '/'
95
+ end
96
+ end
97
+
98
+ nil
99
+ end
100
+
101
+ # The current directory, relative to server root
102
+ def base_path
103
+ @base_path ||= server_path(File.expand_path(@cwd))
104
+ end
105
+
106
+ # Print the given server path, relative to the current directory
107
+ def format_path(entry)
108
+ server_path = entry.path
109
+ if base_path && server_path[0,base_path.length] == base_path
110
+ if server_path == base_path
111
+ return "."
112
+ elsif server_path[base_path.length,1] == "/"
113
+ return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
114
+ elsif base_path == "/" && server_path[0,1] == "/"
115
+ return server_path[1, server_path.length - 1]
116
+ end
117
+ end
118
+ server_path
119
+ end
120
+
121
+ private
122
+
123
+ def object_paths
124
+ @object_paths ||= begin
125
+ if !@chef_config[:chef_repo_path]
126
+ Chef::Log.error("Must specify either chef_repo_path or cookbook_path in Chef config file")
127
+ exit(1)
128
+ end
129
+
130
+ result = {}
131
+ case @chef_config[:repo_mode]
132
+ when 'static'
133
+ object_names = %w(cookbooks data_bags environments roles)
134
+ when 'hosted_everything'
135
+ object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles)
136
+ else
137
+ object_names = %w(clients cookbooks data_bags environments nodes roles users)
138
+ end
139
+ object_names.each do |object_name|
140
+ variable_name = "#{object_name[0..-2]}_path" # cookbooks -> cookbook_path
141
+ paths = Array(@chef_config[variable_name]).flatten
142
+ result[object_name] = paths.map { |path| File.expand_path(path) }
143
+ end
144
+ result
145
+ end
146
+ end
147
+
148
+ def configure_repo_paths
149
+ # Smooth out some (for now) inappropriate defaults set by Chef
150
+ if @chef_config[:cookbook_path] == [ @chef_config.platform_specific_path("/var/chef/cookbooks"),
151
+ @chef_config.platform_specific_path("/var/chef/site-cookbooks") ]
152
+ @chef_config[:cookbook_path] = nil
153
+ end
154
+ if @chef_config[:data_bag_path] == @chef_config.platform_specific_path('/var/chef/data_bags')
155
+ @chef_config[:data_bag_path] = nil
156
+ end
157
+ if @chef_config[:node_path] == '/var/chef/node'
158
+ @chef_config[:node_path] = nil
159
+ end
160
+ if @chef_config[:role_path] == @chef_config.platform_specific_path('/var/chef/roles')
161
+ @chef_config[:role_path] = nil
162
+ end
163
+
164
+ # Infer chef_repo_path from cookbook_path if not speciifed
165
+ if !@chef_config[:chef_repo_path]
166
+ if @chef_config[:cookbook_path]
167
+ @chef_config[:chef_repo_path] = Array(@chef_config[:cookbook_path]).flatten.map { |path| File.expand_path('..', path) }
168
+ end
169
+ end
170
+
171
+ # Default to getting *everything* from the server.
172
+ if !@chef_config[:repo_mode]
173
+ if @chef_config[:chef_server_url] =~ /\/+organizations\/.+/
174
+ @chef_config[:repo_mode] = 'hosted_everything'
175
+ else
176
+ @chef_config[:repo_mode] = 'everything'
177
+ end
178
+ end
179
+
180
+ # Infer any *_path variables that are not specified
181
+ if @chef_config[:chef_repo_path]
182
+ PATH_VARIABLES.each do |variable_name|
183
+ chef_repo_paths = Array(@chef_config[:chef_repo_path]).flatten
184
+ variable = variable_name.to_sym
185
+ if !@chef_config[variable]
186
+ # cookbook_path -> cookbooks
187
+ @chef_config[variable] = chef_repo_paths.map { |path| File.join(path, "#{variable_name[0..-6]}s") }
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -67,6 +67,23 @@ module ChefFS
67
67
  nil
68
68
  end
69
69
 
70
+ # Used to print out the filesystem
71
+ def fs_description
72
+ repo_path = File.dirname(child_paths['cookbooks'][0])
73
+ result = "repository at #{repo_path}\n"
74
+ if Chef::Config[:versioned_cookbooks]
75
+ result << " Multiple versions per cookbook\n"
76
+ else
77
+ result << " One version per cookbook\n"
78
+ end
79
+ child_paths.each_pair do |name, paths|
80
+ if paths.any? { |path| File.dirname(path) != repo_path }
81
+ result << " #{name} at #{paths.join(', ')}\n"
82
+ end
83
+ end
84
+ result
85
+ end
86
+
70
87
  private
71
88
 
72
89
  def make_child_entry(name)
@@ -49,6 +49,10 @@ module ChefFS
49
49
  attr_reader :environment
50
50
  attr_reader :repo_mode
51
51
 
52
+ def fs_description
53
+ "Chef server at #{chef_server_url} (user #{chef_username}), repo_mode = #{repo_mode}"
54
+ end
55
+
52
56
  def rest
53
57
  Chef::REST.new(chef_server_url, chef_username, chef_private_key)
54
58
  end
@@ -75,6 +75,8 @@ module ChefFS
75
75
  else
76
76
  raise ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}"
77
77
  end
78
+ rescue Chef::Exceptions::CookbookFrozen => e
79
+ raise ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen"
78
80
  end
79
81
 
80
82
  # Knife currently does not understand versioned cookbooks
@@ -23,14 +23,20 @@ module ChefFS
23
23
  # Workaround for CHEF-3932
24
24
  def self.deps
25
25
  super do
26
- require 'chef_fs/file_system/chef_server_root_dir'
27
- require 'chef_fs/file_system/chef_repository_file_system_root_dir'
26
+ require 'chef/config'
27
+ require 'chef_fs/parallelizer'
28
+ require 'chef_fs/config'
28
29
  require 'chef_fs/file_pattern'
29
30
  require 'chef_fs/path_utils'
30
- require 'chef_fs/parallelizer'
31
- require 'chef/config'
32
31
  yield
33
32
  end
33
+ end
34
+
35
+ def self.inherited(c)
36
+ super
37
+ # Ensure we always get to do our includes, whether subclass calls deps or not
38
+ c.deps do
39
+ end
34
40
 
35
41
  option :repo_mode,
36
42
  :long => '--repo-mode MODE',
@@ -45,176 +51,38 @@ module ChefFS
45
51
  :description => 'Maximum number of simultaneous requests to send (default: 10)'
46
52
  end
47
53
 
48
- def self.inherited(c)
49
- super
50
- # Ensure we always get to do our includes, whether subclass calls deps or not
51
- c.deps do
52
- end
53
- end
54
-
55
54
  def configure_chef
56
55
  super
57
56
  Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode]
58
57
  Chef::Config[:concurrency] = config[:concurrency].to_i if config[:concurrency]
59
58
 
60
59
  # --chef-repo-path overrides all other paths
61
- path_variables = %w(acl_path client_path cookbook_path container_path data_bag_path environment_path group_path node_path role_path user_path)
62
60
  if config[:chef_repo_path]
63
61
  Chef::Config[:chef_repo_path] = config[:chef_repo_path]
64
- path_variables.each do |variable_name|
65
- Chef::Config[variable_name.to_sym] = nil
66
- end
67
- end
68
-
69
- # Infer chef_repo_path from cookbook_path if not speciifed
70
- if !Chef::Config[:chef_repo_path]
71
- if Chef::Config[:cookbook_path]
72
- Chef::Config[:chef_repo_path] = Array(Chef::Config[:cookbook_path]).flatten.map { |path| File.expand_path('..', path) }
73
- end
74
- end
75
-
76
- # Smooth out some (for now) inappropriate defaults set by Chef
77
- if Chef::Config[:data_bag_path] == Chef::Config.platform_specific_path('/var/chef/data_bags')
78
- Chef::Config[:data_bag_path] = nil
79
- end
80
- if Chef::Config[:node_path] == '/var/chef/node'
81
- Chef::Config[:node_path] = nil
82
- end
83
- if Chef::Config[:role_path] == Chef::Config.platform_specific_path('/var/chef/roles')
84
- Chef::Config[:role_path] = nil
85
- end
86
-
87
- # Default to getting *everything* from the server.
88
- if !Chef::Config[:repo_mode]
89
- if Chef::Config[:chef_server_url] =~ /\/+organizations\/.+/
90
- Chef::Config[:repo_mode] = 'hosted_everything'
91
- else
92
- Chef::Config[:repo_mode] = 'everything'
62
+ ChefFS::Config::PATH_VARIABLES.each do |variable_name|
63
+ Chef::Config[variable_name.to_sym] = chef_repo_paths.map { |path| File.join(path, "#{variable_name[0..-6]}s") }
93
64
  end
94
65
  end
95
66
 
96
- # Infer any *_path variables that are not specified
97
- if Chef::Config[:chef_repo_path]
98
- path_variables.each do |variable_name|
99
- chef_repo_paths = Array(Chef::Config[:chef_repo_path]).flatten
100
- variable = variable_name.to_sym
101
- if !Chef::Config[variable]
102
- # cookbook_path -> cookbooks
103
- Chef::Config[variable] = chef_repo_paths.map { |path| File.join(path, "#{variable_name[0..-6]}s") }
104
- end
105
- end
106
- end
67
+ @chef_fs_config = ChefFS::Config.new(Chef::Config)
107
68
 
108
69
  ChefFS::Parallelizer.threads = (Chef::Config[:concurrency] || 10) - 1
109
70
  end
110
71
 
111
72
  def chef_fs
112
- @chef_fs ||= create_chef_fs
73
+ @chef_fs_config.chef_fs
113
74
  end
114
75
 
115
76
  def create_chef_fs
116
- ChefFS::FileSystem::ChefServerRootDir.new("remote", Chef::Config)
117
- end
118
-
119
- def object_paths
120
- @object_paths ||= begin
121
- if !Chef::Config[:chef_repo_path]
122
- Chef::Log.error("Must specify either chef_repo_path or cookbook_path in Chef config file")
123
- exit(1)
124
- end
125
-
126
- result = {}
127
- case Chef::Config[:repo_mode]
128
- when 'static'
129
- object_names = %w(cookbooks data_bags environments roles)
130
- when 'hosted_everything'
131
- object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles)
132
- else
133
- object_names = %w(clients cookbooks data_bags environments nodes roles users)
134
- end
135
- object_names.each do |object_name|
136
- variable_name = "#{object_name[0..-2]}_path" # cookbooks -> cookbook_path
137
- paths = Array(Chef::Config[variable_name]).flatten
138
- result[object_name] = paths.map { |path| File.expand_path(path) }
139
- end
140
- result
141
- end
142
- end
143
-
144
- # Returns the given real path's location relative to the server root.
145
- #
146
- # If chef_repo is /home/jkeiser/chef_repo,
147
- # and pwd is /home/jkeiser/chef_repo/cookbooks,
148
- # server_path('blah') == '/cookbooks/blah'
149
- # server_path('../roles/blah.json') == '/roles/blah'
150
- # server_path('../../readme.txt') == nil
151
- # server_path('*/*ab*') == '/cookbooks/*/*ab*'
152
- # server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
153
- # server_path('/home/*/chef_repo/cookbooks/blah') == nil
154
- #
155
- # If there are multiple paths (cookbooks, roles, data bags, etc. can all
156
- # have separate paths), and cwd+the path reaches into one of them, we will
157
- # return a path relative to that. Otherwise we will return a path to
158
- # chef_repo.
159
- #
160
- # Globs are allowed as well, but globs outside server paths are NOT
161
- # (presently) supported. See above examples. TODO support that.
162
- #
163
- # If the path does not reach into ANY specified directory, nil is returned.
164
- def server_path(file_path)
165
- pwd = File.expand_path(Dir.pwd)
166
- absolute_path = ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
167
-
168
- # Check all object paths (cookbooks_dir, data_bags_dir, etc.)
169
- object_paths.each_pair do |name, paths|
170
- paths.each do |path|
171
- realest_path = ChefFS::PathUtils.realest_path(path)
172
- if absolute_path[0,realest_path.length] == realest_path &&
173
- (absolute_path.length == realest_path.length ||
174
- absolute_path[realest_path.length,1] =~ /#{PathUtils.regexp_path_separator}/)
175
- relative_path = ChefFS::PathUtils::relative_to(absolute_path, realest_path)
176
- return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
177
- end
178
- end
179
- end
180
-
181
- # Check chef_repo_path
182
- Array(Chef::Config[:chef_repo_path]).flatten.each do |chef_repo_path|
183
- realest_chef_repo_path = ChefFS::PathUtils.realest_path(chef_repo_path)
184
- if absolute_path == realest_chef_repo_path
185
- return '/'
186
- end
187
- end
188
-
189
- nil
190
- end
191
-
192
- # The current directory, relative to server root
193
- def base_path
194
- @base_path ||= server_path(File.expand_path(Dir.pwd))
195
- end
196
-
197
- # Print the given server path, relative to the current directory
198
- def format_path(entry)
199
- server_path = entry.path
200
- if base_path && server_path[0,base_path.length] == base_path
201
- if server_path == base_path
202
- return "."
203
- elsif server_path[base_path.length,1] == "/"
204
- return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
205
- elsif base_path == "/" && server_path[0,1] == "/"
206
- return server_path[1, server_path.length - 1]
207
- end
208
- end
209
- server_path
77
+ @chef_fs_config.create_chef_fs
210
78
  end
211
79
 
212
80
  def local_fs
213
- @local_fs ||= create_local_fs
81
+ @chef_fs_config.local_fs
214
82
  end
215
83
 
216
84
  def create_local_fs
217
- ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths)
85
+ @chef_fs_config.create_local_fs
218
86
  end
219
87
 
220
88
  def pattern_args
@@ -225,14 +93,18 @@ module ChefFS
225
93
  # TODO support absolute file paths and not just patterns? Too much?
226
94
  # Could be super useful in a world with multiple repo paths
227
95
  args.map do |arg|
228
- if !base_path && !PathUtils.is_absolute?(arg)
96
+ if !@chef_fs_config.base_path && !ChefFS::PathUtils.is_absolute?(arg)
229
97
  ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path")
230
98
  exit(1)
231
99
  end
232
- ChefFS::FilePattern::relative_to(base_path, arg)
100
+ ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg)
233
101
  end
234
102
  end
235
103
 
104
+ def format_path(entry)
105
+ @chef_fs_config.format_path(entry)
106
+ end
107
+
236
108
  def parallelize(inputs, options = {}, &block)
237
109
  ChefFS::Parallelizer.parallelize(inputs, options, &block)
238
110
  end
@@ -1,3 +1,3 @@
1
1
  module ChefFS
2
- VERSION = "1.2"
2
+ VERSION = "1.2.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-essentials
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.2'
4
+ version: 1.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-03 00:00:00.000000000 Z
12
+ date: 2013-06-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef
@@ -129,7 +129,9 @@ files:
129
129
  - lib/chef/knife/show_essentials.rb
130
130
  - lib/chef/knife/upload_essentials.rb
131
131
  - lib/chef/knife/xargs_essentials.rb
132
+ - lib/chef_fs/chef_fs_data_store.rb
132
133
  - lib/chef_fs/command_line.rb
134
+ - lib/chef_fs/config.rb
133
135
  - lib/chef_fs/data_handler/acl_data_handler.rb
134
136
  - lib/chef_fs/data_handler/client_data_handler.rb
135
137
  - lib/chef_fs/data_handler/container_data_handler.rb