knife-essentials 1.2 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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