knife-essentials 0.8.6 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/chef/knife/diff_essentials.rb +6 -1
- data/lib/chef_fs/command_line.rb +68 -75
- data/lib/chef_fs/data_handler/client_data_handler.rb +28 -0
- data/lib/chef_fs/data_handler/cookbook_data_handler.rb +36 -0
- data/lib/chef_fs/data_handler/data_bag_item_data_handler.rb +32 -0
- data/lib/chef_fs/data_handler/data_handler_base.rb +110 -0
- data/lib/chef_fs/data_handler/environment_data_handler.rb +38 -0
- data/lib/chef_fs/data_handler/node_data_handler.rb +34 -0
- data/lib/chef_fs/data_handler/role_data_handler.rb +38 -0
- data/lib/chef_fs/data_handler/user_data_handler.rb +20 -0
- data/lib/chef_fs/file_system.rb +4 -0
- data/lib/chef_fs/file_system/chef_repository_file_system_entry.rb +5 -7
- data/lib/chef_fs/file_system/chef_repository_file_system_root_dir.rb +14 -13
- data/lib/chef_fs/file_system/chef_server_root_dir.rb +6 -3
- data/lib/chef_fs/file_system/cookbook_file.rb +1 -5
- data/lib/chef_fs/file_system/data_bag_dir.rb +4 -9
- data/lib/chef_fs/file_system/data_bag_item.rb +6 -0
- data/lib/chef_fs/file_system/environments_dir.rb +2 -1
- data/lib/chef_fs/file_system/file_system_error.rb +3 -1
- data/lib/chef_fs/file_system/must_delete_recursively_error.rb +1 -4
- data/lib/chef_fs/file_system/nodes_dir.rb +2 -1
- data/lib/chef_fs/file_system/not_found_error.rb +1 -4
- data/lib/chef_fs/file_system/operation_failed_error.rb +32 -0
- data/lib/chef_fs/file_system/operation_not_allowed_error.rb +1 -2
- data/lib/chef_fs/file_system/rest_list_dir.rb +33 -6
- data/lib/chef_fs/file_system/rest_list_entry.rb +53 -15
- data/lib/chef_fs/version.rb +1 -1
- data/spec/chef_fs/file_system/chef_server_root_dir_spec.rb +1 -0
- data/spec/chef_fs/file_system/data_bags_dir_spec.rb +1 -0
- data/spec/integration/diff_spec.rb +75 -0
- data/spec/integration/download_spec.rb +27 -0
- data/spec/integration/raw_spec.rb +2 -24
- data/spec/integration/show_spec.rb +40 -23
- data/spec/integration/upload_spec.rb +61 -0
- metadata +11 -2
@@ -26,6 +26,11 @@ class Chef
|
|
26
26
|
:boolean => true,
|
27
27
|
:description => "Only show names and statuses of modified files: Added, Deleted, Modified, and Type Changed."
|
28
28
|
|
29
|
+
option :diff_filter,
|
30
|
+
:long => '--diff-filter=[(A|D|M|T)...[*]]',
|
31
|
+
:description => "Select only files that are Added (A), Deleted (D), Modified (M), or have their type (i.e. regular file, directory) changed (T). Any combination of the filter characters (including none) can be used. When * (All-or-none) is added to the combination, all paths are selected if
|
32
|
+
there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected."
|
33
|
+
|
29
34
|
def run
|
30
35
|
if config[:name_only]
|
31
36
|
output_mode = :name_only
|
@@ -38,7 +43,7 @@ class Chef
|
|
38
43
|
# Get the matches (recursively)
|
39
44
|
error = false
|
40
45
|
patterns.each do |pattern|
|
41
|
-
found_match = ChefFS::CommandLine.diff_print(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, output_mode, proc { |entry| format_path(entry) } ) do |diff|
|
46
|
+
found_match = ChefFS::CommandLine.diff_print(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, output_mode, proc { |entry| format_path(entry) }, config[:diff_filter] ) do |diff|
|
42
47
|
stdout.print diff
|
43
48
|
end
|
44
49
|
if !found_match
|
data/lib/chef_fs/command_line.rb
CHANGED
@@ -21,7 +21,7 @@ require 'chef_fs/file_system'
|
|
21
21
|
module ChefFS
|
22
22
|
module CommandLine
|
23
23
|
|
24
|
-
def self.diff_print(pattern, a_root, b_root, recurse_depth, output_mode, format_path = nil)
|
24
|
+
def self.diff_print(pattern, a_root, b_root, recurse_depth, output_mode, format_path = nil, diff_filter = nil)
|
25
25
|
if format_path.nil?
|
26
26
|
format_path = proc { |entry| entry.path_for_printing }
|
27
27
|
end
|
@@ -33,71 +33,77 @@ module ChefFS
|
|
33
33
|
old_path = format_path.call(old_entry)
|
34
34
|
new_path = format_path.call(new_entry)
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
result << "new file\n"
|
41
|
-
old_path = "/dev/null"
|
42
|
-
old_value = ''
|
36
|
+
case type
|
37
|
+
when :common_subdirectories
|
38
|
+
if output_mode != :name_only && output_mode != :name_status
|
39
|
+
yield "Common subdirectories: #{new_path}\n"
|
43
40
|
end
|
44
|
-
|
41
|
+
|
42
|
+
when :directory_to_file
|
43
|
+
next if diff_filter && diff_filter !~ /T/
|
44
|
+
if output_mode == :name_only
|
45
|
+
yield "#{new_path}\n"
|
46
|
+
elsif output_mode == :name_status
|
47
|
+
yield "T\t#{new_path}\n"
|
48
|
+
else
|
49
|
+
yield "File #{old_path} is a directory while file #{new_path} is a regular file\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
when :file_to_directory
|
53
|
+
next if diff_filter && diff_filter !~ /T/
|
54
|
+
if output_mode == :name_only
|
55
|
+
yield "#{new_path}\n"
|
56
|
+
elsif output_mode == :name_status
|
57
|
+
yield "T\t#{new_path}\n"
|
58
|
+
else
|
59
|
+
yield "File #{old_path} is a regular file while file #{new_path} is a directory\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
when :deleted
|
63
|
+
next if diff_filter && diff_filter !~ /D/
|
64
|
+
if output_mode == :name_only
|
65
|
+
yield "#{new_path}\n"
|
66
|
+
elsif output_mode == :name_status
|
67
|
+
yield "D\t#{new_path}\n"
|
68
|
+
elsif old_value
|
69
|
+
result = "diff --knife #{old_path} #{new_path}\n"
|
45
70
|
result << "deleted file\n"
|
46
|
-
|
47
|
-
|
71
|
+
result << diff_text(old_path, '/dev/null', old_value, '')
|
72
|
+
yield result
|
73
|
+
else
|
74
|
+
yield "Only in #{format_path.call(old_entry.parent)}: #{old_entry.name}\n"
|
48
75
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
yield "File #{old_path} is a directory while file #{new_path} is a regular file\n"
|
64
|
-
end
|
65
|
-
when :file_to_directory
|
66
|
-
if output_mode == :name_only
|
67
|
-
yield "#{new_path}\n"
|
68
|
-
elsif output_mode == :name_status
|
69
|
-
yield "T\t#{new_path}\n"
|
70
|
-
else
|
71
|
-
yield "File #{old_path} is a regular file while file #{new_path} is a directory\n"
|
72
|
-
end
|
73
|
-
when :deleted
|
74
|
-
if output_mode == :name_only
|
75
|
-
yield "#{new_path}\n"
|
76
|
-
elsif output_mode == :name_status
|
77
|
-
yield "D\t#{new_path}\n"
|
78
|
-
else
|
79
|
-
yield "Only in #{format_path.call(old_entry.parent)}: #{old_entry.name}\n"
|
80
|
-
end
|
81
|
-
when :added
|
82
|
-
if output_mode == :name_only
|
83
|
-
yield "#{new_path}\n"
|
84
|
-
elsif output_mode == :name_status
|
85
|
-
yield "A\t#{new_path}\n"
|
86
|
-
else
|
87
|
-
yield "Only in #{format_path.call(new_entry.parent)}: #{new_entry.name}\n"
|
88
|
-
end
|
89
|
-
when :modified
|
90
|
-
if output_mode == :name_only
|
91
|
-
yield "#{new_path}\n"
|
92
|
-
elsif output_mode == :name_status
|
93
|
-
yield "M\t#{new_path}\n"
|
94
|
-
end
|
95
|
-
when :both_nonexistent
|
96
|
-
when :added_cannot_upload
|
97
|
-
when :deleted_cannot_download
|
98
|
-
when :same
|
99
|
-
# Skip these silently
|
76
|
+
|
77
|
+
when :added
|
78
|
+
next if diff_filter && diff_filter !~ /A/
|
79
|
+
if output_mode == :name_only
|
80
|
+
yield "#{new_path}\n"
|
81
|
+
elsif output_mode == :name_status
|
82
|
+
yield "A\t#{new_path}\n"
|
83
|
+
elsif new_value
|
84
|
+
result = "diff --knife #{old_path} #{new_path}\n"
|
85
|
+
result << "new file\n"
|
86
|
+
result << diff_text('/dev/null', new_path, '', new_value)
|
87
|
+
yield result
|
88
|
+
else
|
89
|
+
yield "Only in #{format_path.call(new_entry.parent)}: #{new_entry.name}\n"
|
100
90
|
end
|
91
|
+
when :modified
|
92
|
+
next if diff_filter && diff_filter !~ /M/
|
93
|
+
if output_mode == :name_only
|
94
|
+
yield "#{new_path}\n"
|
95
|
+
elsif output_mode == :name_status
|
96
|
+
yield "M\t#{new_path}\n"
|
97
|
+
else
|
98
|
+
result = "diff --knife #{old_path} #{new_path}\n"
|
99
|
+
result << diff_text(old_path, new_path, old_value, new_value)
|
100
|
+
yield result
|
101
|
+
end
|
102
|
+
when :both_nonexistent
|
103
|
+
when :added_cannot_upload
|
104
|
+
when :deleted_cannot_download
|
105
|
+
when :same
|
106
|
+
# Skip these silently
|
101
107
|
end
|
102
108
|
end
|
103
109
|
found_match
|
@@ -229,19 +235,6 @@ module ChefFS
|
|
229
235
|
end
|
230
236
|
|
231
237
|
def self.diff_text(old_path, new_path, old_value, new_value)
|
232
|
-
# Reformat JSON for a nicer diff.
|
233
|
-
if old_path =~ /\.json$/
|
234
|
-
begin
|
235
|
-
reformatted_old_value = canonicalize_json(old_value)
|
236
|
-
reformatted_new_value = canonicalize_json(new_value)
|
237
|
-
old_value = reformatted_old_value
|
238
|
-
new_value = reformatted_new_value
|
239
|
-
rescue
|
240
|
-
# If JSON parsing fails, we just won't change any values and fall back
|
241
|
-
# to normal diff.
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
238
|
# Copy to tempfiles before diffing
|
246
239
|
# TODO don't copy things that are already in files! Or find an in-memory diff algorithm
|
247
240
|
begin
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'chef_fs/data_handler/data_handler_base'
|
2
|
+
require 'chef/api_client'
|
3
|
+
|
4
|
+
module ChefFS
|
5
|
+
module DataHandler
|
6
|
+
class ClientDataHandler < DataHandlerBase
|
7
|
+
def normalize(client, entry)
|
8
|
+
super(client, {
|
9
|
+
'name' => remove_dot_json(entry.name),
|
10
|
+
'admin' => false,
|
11
|
+
'validator' => false,
|
12
|
+
'json_class' => 'Chef::ApiClient',
|
13
|
+
'chef_type' => 'client'
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
def preserve_key(key)
|
18
|
+
return key == 'name'
|
19
|
+
end
|
20
|
+
|
21
|
+
def chef_class
|
22
|
+
Chef::ApiClient
|
23
|
+
end
|
24
|
+
|
25
|
+
# There is no Ruby API for Chef::ApiClient
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'chef_fs/data_handler/data_handler_base'
|
2
|
+
require 'chef/cookbook/metadata'
|
3
|
+
|
4
|
+
module ChefFS
|
5
|
+
module DataHandler
|
6
|
+
class CookbookDataHandler < DataHandlerBase
|
7
|
+
def normalize(cookbook, entry)
|
8
|
+
version = entry.name
|
9
|
+
name = entry.parent.name
|
10
|
+
result = super(cookbook, {
|
11
|
+
'name' => "#{name}-#{version}",
|
12
|
+
'version' => version,
|
13
|
+
'cookbook_name' => name,
|
14
|
+
'json_class' => 'Chef::CookbookVersion',
|
15
|
+
'chef_type' => 'cookbook_version',
|
16
|
+
'frozen?' => false,
|
17
|
+
'metadata' => {}
|
18
|
+
})
|
19
|
+
result['metadata'] = super(result['metadata'], {
|
20
|
+
'version' => version,
|
21
|
+
'name' => name
|
22
|
+
})
|
23
|
+
end
|
24
|
+
|
25
|
+
def preserve_key(key)
|
26
|
+
return key == 'cookbook_name' || key == 'version'
|
27
|
+
end
|
28
|
+
|
29
|
+
def chef_class
|
30
|
+
Chef::Cookbook::Metadata
|
31
|
+
end
|
32
|
+
|
33
|
+
# Not using this yet, so not sure if to_ruby will be useful.
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'chef_fs/data_handler/data_handler_base'
|
2
|
+
require 'chef/data_bag_item'
|
3
|
+
|
4
|
+
module ChefFS
|
5
|
+
module DataHandler
|
6
|
+
class DataBagItemDataHandler < DataHandlerBase
|
7
|
+
def normalize(data_bag_item, entry)
|
8
|
+
# If it's wrapped with raw_data, unwrap it.
|
9
|
+
if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
|
10
|
+
data_bag_item = data_bag_item['raw_data']
|
11
|
+
end
|
12
|
+
# chef_type and data_bag only come back from PUT and POST, but we'll
|
13
|
+
# normalize them in in case someone is comparing with those results.
|
14
|
+
super(data_bag_item, {
|
15
|
+
'chef_type' => 'data_bag_item',
|
16
|
+
'data_bag' => entry.parent.name,
|
17
|
+
'id' => remove_dot_json(entry.name)
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
def preserve_key(key)
|
22
|
+
return key == 'id'
|
23
|
+
end
|
24
|
+
|
25
|
+
def chef_class
|
26
|
+
Chef::DataBagItem
|
27
|
+
end
|
28
|
+
|
29
|
+
# Data bags do not support .rb files (or if they do, it's undocumented)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module ChefFS
|
2
|
+
module DataHandler
|
3
|
+
class DataHandlerBase
|
4
|
+
def minimize(object, entry)
|
5
|
+
default_object = default(entry)
|
6
|
+
object.each_pair do |key, value|
|
7
|
+
if default_object[key] == value && !preserve_key(key)
|
8
|
+
object.delete(key)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
object
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove_dot_json(name)
|
15
|
+
if name.length < 5 || name[-5,5] != ".json"
|
16
|
+
raise "Invalid name #{path}: must end in .json"
|
17
|
+
end
|
18
|
+
name[0,name.length-5]
|
19
|
+
end
|
20
|
+
|
21
|
+
def preserve_key(key)
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def default(entry)
|
26
|
+
normalize({}, entry)
|
27
|
+
end
|
28
|
+
|
29
|
+
def normalize(object, defaults)
|
30
|
+
# Make a normalized result in the specified order for diffing
|
31
|
+
result = {}
|
32
|
+
defaults.each_pair do |key, default|
|
33
|
+
result[key] = object.has_key?(key) ? object[key] : default
|
34
|
+
end
|
35
|
+
object.each_pair do |key, value|
|
36
|
+
result[key] = value if !result.has_key?(key)
|
37
|
+
end
|
38
|
+
result
|
39
|
+
end
|
40
|
+
|
41
|
+
def normalize_run_list(run_list)
|
42
|
+
run_list.map{|item|
|
43
|
+
case item.to_s
|
44
|
+
when /^recipe\[.*\]$/
|
45
|
+
item # explicit recipe
|
46
|
+
when /^role\[.*\]$/
|
47
|
+
item # explicit role
|
48
|
+
else
|
49
|
+
"recipe[#{item}]"
|
50
|
+
end
|
51
|
+
}.uniq
|
52
|
+
end
|
53
|
+
|
54
|
+
def from_ruby(ruby)
|
55
|
+
chef_class.from_file(ruby).to_hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def chef_object(object)
|
59
|
+
chef_class.json_create(object)
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_ruby(object)
|
63
|
+
raise NotImplementedError
|
64
|
+
end
|
65
|
+
|
66
|
+
def chef_class
|
67
|
+
raise NotImplementedError
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_ruby_keys(object, keys)
|
71
|
+
result = ''
|
72
|
+
keys.each do |key|
|
73
|
+
if object[key]
|
74
|
+
if object[key].is_a?(Hash)
|
75
|
+
if object[key].size > 0
|
76
|
+
result << key
|
77
|
+
first = true
|
78
|
+
object[key].each_pair do |k,v|
|
79
|
+
if first
|
80
|
+
first = false
|
81
|
+
else
|
82
|
+
result << ' '*key.length
|
83
|
+
end
|
84
|
+
result << " #{k.inspect} => #{v.inspect}\n"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
elsif object[key].is_a?(Array)
|
88
|
+
if object[key].size > 0
|
89
|
+
result << key
|
90
|
+
first = true
|
91
|
+
object[key].each do |value|
|
92
|
+
if first
|
93
|
+
first = false
|
94
|
+
else
|
95
|
+
result << ", "
|
96
|
+
end
|
97
|
+
result << value.inspect
|
98
|
+
end
|
99
|
+
result << "\n"
|
100
|
+
end
|
101
|
+
elsif !object[key].nil?
|
102
|
+
result << "#{key} #{object[key].inspect}\n"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
result
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'chef_fs/data_handler/data_handler_base'
|
2
|
+
require 'chef/environment'
|
3
|
+
|
4
|
+
module ChefFS
|
5
|
+
module DataHandler
|
6
|
+
class EnvironmentDataHandler < DataHandlerBase
|
7
|
+
def normalize(environment, entry)
|
8
|
+
super(environment, {
|
9
|
+
'name' => remove_dot_json(entry.name),
|
10
|
+
'description' => '',
|
11
|
+
'cookbook_versions' => {},
|
12
|
+
'default_attributes' => {},
|
13
|
+
'override_attributes' => {},
|
14
|
+
'json_class' => 'Chef::Environment',
|
15
|
+
'chef_type' => 'environment'
|
16
|
+
})
|
17
|
+
end
|
18
|
+
|
19
|
+
def preserve_key(key)
|
20
|
+
return key == 'name'
|
21
|
+
end
|
22
|
+
|
23
|
+
def chef_class
|
24
|
+
Chef::Environment
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_ruby(object)
|
28
|
+
result = to_ruby_keys(object, %w(name description default_attributes override_attributes))
|
29
|
+
if object['cookbook_versions']
|
30
|
+
object['cookbook_versions'].each_pair do |name, version|
|
31
|
+
result << "cookbook #{name.inspect}, #{version.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'chef_fs/data_handler/data_handler_base'
|
2
|
+
require 'chef/node'
|
3
|
+
|
4
|
+
module ChefFS
|
5
|
+
module DataHandler
|
6
|
+
class NodeDataHandler < DataHandlerBase
|
7
|
+
def normalize(node, entry)
|
8
|
+
result = super(node, {
|
9
|
+
'name' => remove_dot_json(entry.name),
|
10
|
+
'json_class' => 'Chef::Node',
|
11
|
+
'chef_type' => 'node',
|
12
|
+
'chef_environment' => '_default',
|
13
|
+
'override' => {},
|
14
|
+
'normal' => {},
|
15
|
+
'default' => {},
|
16
|
+
'automatic' => {},
|
17
|
+
'run_list' => []
|
18
|
+
})
|
19
|
+
result['run_list'] = normalize_run_list(result['run_list'])
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def preserve_key(key)
|
24
|
+
return key == 'name'
|
25
|
+
end
|
26
|
+
|
27
|
+
def chef_class
|
28
|
+
Chef::Node
|
29
|
+
end
|
30
|
+
|
31
|
+
# Nodes do not support .rb files
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|