knife-essentials 0.8.6 → 0.9.0

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.
Files changed (35) hide show
  1. data/lib/chef/knife/diff_essentials.rb +6 -1
  2. data/lib/chef_fs/command_line.rb +68 -75
  3. data/lib/chef_fs/data_handler/client_data_handler.rb +28 -0
  4. data/lib/chef_fs/data_handler/cookbook_data_handler.rb +36 -0
  5. data/lib/chef_fs/data_handler/data_bag_item_data_handler.rb +32 -0
  6. data/lib/chef_fs/data_handler/data_handler_base.rb +110 -0
  7. data/lib/chef_fs/data_handler/environment_data_handler.rb +38 -0
  8. data/lib/chef_fs/data_handler/node_data_handler.rb +34 -0
  9. data/lib/chef_fs/data_handler/role_data_handler.rb +38 -0
  10. data/lib/chef_fs/data_handler/user_data_handler.rb +20 -0
  11. data/lib/chef_fs/file_system.rb +4 -0
  12. data/lib/chef_fs/file_system/chef_repository_file_system_entry.rb +5 -7
  13. data/lib/chef_fs/file_system/chef_repository_file_system_root_dir.rb +14 -13
  14. data/lib/chef_fs/file_system/chef_server_root_dir.rb +6 -3
  15. data/lib/chef_fs/file_system/cookbook_file.rb +1 -5
  16. data/lib/chef_fs/file_system/data_bag_dir.rb +4 -9
  17. data/lib/chef_fs/file_system/data_bag_item.rb +6 -0
  18. data/lib/chef_fs/file_system/environments_dir.rb +2 -1
  19. data/lib/chef_fs/file_system/file_system_error.rb +3 -1
  20. data/lib/chef_fs/file_system/must_delete_recursively_error.rb +1 -4
  21. data/lib/chef_fs/file_system/nodes_dir.rb +2 -1
  22. data/lib/chef_fs/file_system/not_found_error.rb +1 -4
  23. data/lib/chef_fs/file_system/operation_failed_error.rb +32 -0
  24. data/lib/chef_fs/file_system/operation_not_allowed_error.rb +1 -2
  25. data/lib/chef_fs/file_system/rest_list_dir.rb +33 -6
  26. data/lib/chef_fs/file_system/rest_list_entry.rb +53 -15
  27. data/lib/chef_fs/version.rb +1 -1
  28. data/spec/chef_fs/file_system/chef_server_root_dir_spec.rb +1 -0
  29. data/spec/chef_fs/file_system/data_bags_dir_spec.rb +1 -0
  30. data/spec/integration/diff_spec.rb +75 -0
  31. data/spec/integration/download_spec.rb +27 -0
  32. data/spec/integration/raw_spec.rb +2 -24
  33. data/spec/integration/show_spec.rb +40 -23
  34. data/spec/integration/upload_spec.rb +61 -0
  35. metadata +11 -2
@@ -0,0 +1,38 @@
1
+ require 'chef_fs/data_handler/data_handler_base'
2
+ require 'chef/role'
3
+
4
+ module ChefFS
5
+ module DataHandler
6
+ class RoleDataHandler < DataHandlerBase
7
+ def normalize(role, entry)
8
+ result = super(role, {
9
+ 'name' => remove_dot_json(entry.name),
10
+ 'description' => '',
11
+ 'json_class' => 'Chef::Role',
12
+ 'chef_type' => 'role',
13
+ 'default_attributes' => {},
14
+ 'override_attributes' => {},
15
+ 'run_list' => [],
16
+ 'env_run_lists' => {}
17
+ })
18
+ result['run_list'] = normalize_run_list(result['run_list'])
19
+ result['env_run_lists'].each_pair do |env, run_list|
20
+ result['env_run_lists'][env] = normalize_run_list(run_list)
21
+ end
22
+ result
23
+ end
24
+
25
+ def preserve_key(key)
26
+ return key == 'name'
27
+ end
28
+
29
+ def chef_class
30
+ Chef::Role
31
+ end
32
+
33
+ def to_ruby(object)
34
+ to_ruby_keys(object, %w(name description default_attributes override_attributes run_list env_run_lists))
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ require 'chef_fs/data_handler/data_handler_base'
2
+
3
+ module ChefFS
4
+ module DataHandler
5
+ class UserDataHandler < DataHandlerBase
6
+ def normalize(user, entry)
7
+ super(user, {
8
+ 'name' => remove_dot_json(entry.name),
9
+ 'admin' => false
10
+ })
11
+ end
12
+
13
+ def preserve_key(key)
14
+ return key == 'name'
15
+ end
16
+
17
+ # There is no chef_class for users, nor does to_ruby work.
18
+ end
19
+ end
20
+ end
@@ -18,6 +18,7 @@
18
18
 
19
19
  require 'chef_fs/path_utils'
20
20
  require 'chef_fs/file_system/default_environment_cannot_be_modified_error'
21
+ require 'chef_fs/file_system/operation_failed_error'
21
22
  require 'chef_fs/file_system/operation_not_allowed_error'
22
23
 
23
24
  module ChefFS
@@ -350,6 +351,9 @@ module ChefFS
350
351
  end
351
352
  rescue DefaultEnvironmentCannotBeModifiedError => e
352
353
  ui.warn "#{format_path.call(e.entry)} #{e.reason}."
354
+ rescue OperationFailedError => e
355
+ ui.error "#{format_path.call(e.entry)} failed to #{e.operation}: #{e.message}"
356
+ error = true
353
357
  rescue OperationNotAllowedError => e
354
358
  ui.error "#{format_path.call(e.entry)} #{e.reason}."
355
359
  error = true
@@ -24,9 +24,9 @@ module ChefFS
24
24
  # ChefRepositoryFileSystemEntry works just like FileSystemEntry,
25
25
  # except can inflate Chef objects
26
26
  class ChefRepositoryFileSystemEntry < FileSystemEntry
27
- def initialize(name, parent, file_path = nil, json_class = nil)
27
+ def initialize(name, parent, file_path = nil, data_handler = nil)
28
28
  super(name, parent, file_path)
29
- @json_class = json_class
29
+ @data_handler = data_handler
30
30
  end
31
31
 
32
32
  def chefignore
@@ -37,8 +37,8 @@ module ChefFS
37
37
  parent.ignore_empty_directories?
38
38
  end
39
39
 
40
- def json_class
41
- @json_class || parent.json_class
40
+ def data_handler
41
+ @data_handler || parent.data_handler
42
42
  end
43
43
 
44
44
  def chef_object
@@ -50,9 +50,7 @@ module ChefFS
50
50
  end
51
51
 
52
52
  # Otherwise, inflate the file using the chosen JSON class (if any)
53
- if json_class
54
- return json_class.json_create(JSON.parse(read, :create_additions => false))
55
- end
53
+ return data_handler.chef_object(JSON.parse(read, :create_additions => false))
56
54
  rescue
57
55
  Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
58
56
  end
@@ -20,11 +20,12 @@ require 'chef_fs/file_system/base_fs_dir'
20
20
  require 'chef_fs/file_system/chef_repository_file_system_entry'
21
21
  require 'chef_fs/file_system/chef_repository_file_system_cookbooks_dir'
22
22
  require 'chef_fs/file_system/multiplexed_dir'
23
- require 'chef/api_client'
24
- require 'chef/data_bag_item'
25
- require 'chef/environment'
26
- require 'chef/node'
27
- require 'chef/role'
23
+ require 'chef_fs/data_handler/client_data_handler'
24
+ require 'chef_fs/data_handler/data_bag_item_data_handler'
25
+ require 'chef_fs/data_handler/environment_data_handler'
26
+ require 'chef_fs/data_handler/node_data_handler'
27
+ require 'chef_fs/data_handler/role_data_handler'
28
+ require 'chef_fs/data_handler/user_data_handler'
28
29
 
29
30
  module ChefFS
30
31
  module FileSystem
@@ -75,23 +76,23 @@ module ChefFS
75
76
  if name == 'cookbooks'
76
77
  dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
77
78
  else
78
- json_class = case name
79
+ data_handler = case name
79
80
  when 'clients'
80
- Chef::ApiClient
81
+ ChefFS::DataHandler::ClientDataHandler.new
81
82
  when 'data_bags'
82
- Chef::DataBagItem
83
+ ChefFS::DataHandler::DataBagItemDataHandler.new
83
84
  when 'environments'
84
- Chef::Environment
85
+ ChefFS::DataHandler::EnvironmentDataHandler.new
85
86
  when 'nodes'
86
- Chef::Node
87
+ ChefFS::DataHandler::NodeDataHandler.new
87
88
  when 'roles'
88
- Chef::Role
89
+ ChefFS::DataHandler::RoleDataHandler.new
89
90
  when 'users'
90
- nil
91
+ ChefFS::DataHandler::UserDataHandler.new
91
92
  else
92
93
  raise "Unknown top level path #{name}"
93
94
  end
94
- dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path, json_class) }
95
+ dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path, data_handler) }
95
96
  end
96
97
  MultiplexedDir.new(dirs)
97
98
  end
@@ -23,6 +23,9 @@ require 'chef_fs/file_system/data_bags_dir'
23
23
  require 'chef_fs/file_system/nodes_dir'
24
24
  require 'chef_fs/file_system/environments_dir'
25
25
  require 'chef/rest'
26
+ require 'chef_fs/data_handler/client_data_handler'
27
+ require 'chef_fs/data_handler/role_data_handler'
28
+ require 'chef_fs/data_handler/user_data_handler'
26
29
 
27
30
  module ChefFS
28
31
  module FileSystem
@@ -65,13 +68,13 @@ module ChefFS
65
68
  CookbooksDir.new(self),
66
69
  DataBagsDir.new(self),
67
70
  EnvironmentsDir.new(self),
68
- RestListDir.new("roles", self)
71
+ RestListDir.new("roles", self, nil, ChefFS::DataHandler::RoleDataHandler.new)
69
72
  ]
70
73
  if repo_mode == 'everything'
71
74
  result += [
72
- RestListDir.new("clients", self),
75
+ RestListDir.new("clients", self, nil, ChefFS::DataHandler::ClientDataHandler.new),
73
76
  NodesDir.new(self),
74
- RestListDir.new("users", self)
77
+ RestListDir.new("users", self, nil, ChefFS::DataHandler::UserDataHandler.new)
75
78
  ]
76
79
  end
77
80
  result.sort_by { |child| child.name }
@@ -71,11 +71,7 @@ module ChefFS
71
71
  private
72
72
 
73
73
  def calc_checksum(value)
74
- begin
75
- Digest::MD5.hexdigest(value)
76
- rescue ChefFS::FileSystem::NotFoundError
77
- nil
78
- end
74
+ Digest::MD5.hexdigest(value)
79
75
  end
80
76
  end
81
77
  end
@@ -20,12 +20,13 @@ require 'chef_fs/file_system/rest_list_dir'
20
20
  require 'chef_fs/file_system/data_bag_item'
21
21
  require 'chef_fs/file_system/not_found_error'
22
22
  require 'chef_fs/file_system/must_delete_recursively_error'
23
+ require 'chef_fs/data_handler/data_bag_item_data_handler'
23
24
 
24
25
  module ChefFS
25
26
  module FileSystem
26
27
  class DataBagDir < RestListDir
27
28
  def initialize(name, parent, exists = nil)
28
- super(name, parent)
29
+ super(name, parent, nil, ChefFS::DataHandler::DataBagItemDataHandler.new)
29
30
  @exists = nil
30
31
  end
31
32
 
@@ -45,14 +46,8 @@ module ChefFS
45
46
  @exists
46
47
  end
47
48
 
48
- def create_child(name, file_contents)
49
- json = Chef::JSONCompat.from_json(file_contents).to_hash
50
- id = name[0,name.length-5]
51
- if json.include?('id') && json['id'] != id
52
- raise "ID in #{path_for_printing}/#{name} must be '#{id}' (is '#{json['id']}')"
53
- end
54
- rest.post_rest(api_path, json)
55
- _make_child_entry(name, true)
49
+ def identity_key
50
+ 'id'
56
51
  end
57
52
 
58
53
  def _make_child_entry(name, exists = nil)
@@ -52,6 +52,12 @@ module ChefFS
52
52
  end
53
53
  end
54
54
  end
55
+
56
+ def normalize_value(value)
57
+ data_handler.minimize(
58
+ data_handler.normalize(value, parent.name, api_child_name),
59
+ parent.name, api_child_name)
60
+ end
55
61
  end
56
62
  end
57
63
  end
@@ -20,12 +20,13 @@ require 'chef_fs/file_system/base_fs_dir'
20
20
  require 'chef_fs/file_system/rest_list_entry'
21
21
  require 'chef_fs/file_system/not_found_error'
22
22
  require 'chef_fs/file_system/default_environment_cannot_be_modified_error'
23
+ require 'chef_fs/data_handler/environment_data_handler'
23
24
 
24
25
  module ChefFS
25
26
  module FileSystem
26
27
  class EnvironmentsDir < RestListDir
27
28
  def initialize(parent)
28
- super("environments", parent)
29
+ super("environments", parent, nil, ChefFS::DataHandler::EnvironmentDataHandler.new)
29
30
  end
30
31
 
31
32
  def _make_child_entry(name, exists = nil)
@@ -19,10 +19,12 @@
19
19
  module ChefFS
20
20
  module FileSystem
21
21
  class FileSystemError < StandardError
22
- def initialize(cause = nil)
22
+ def initialize(entry, cause = nil)
23
+ @entry = entry
23
24
  @cause = cause
24
25
  end
25
26
 
27
+ attr_reader :entry
26
28
  attr_reader :cause
27
29
  end
28
30
  end
@@ -22,11 +22,8 @@ module ChefFS
22
22
  module FileSystem
23
23
  class MustDeleteRecursivelyError < FileSystemError
24
24
  def initialize(entry, cause = nil)
25
- super(cause)
26
- @entry = entry
25
+ super(entry, cause)
27
26
  end
28
-
29
- attr_reader :entry
30
27
  end
31
28
  end
32
29
  end
@@ -19,12 +19,13 @@
19
19
  require 'chef_fs/file_system/base_fs_dir'
20
20
  require 'chef_fs/file_system/rest_list_entry'
21
21
  require 'chef_fs/file_system/not_found_error'
22
+ require 'chef_fs/data_handler/node_data_handler'
22
23
 
23
24
  module ChefFS
24
25
  module FileSystem
25
26
  class NodesDir < RestListDir
26
27
  def initialize(parent)
27
- super("nodes", parent)
28
+ super("nodes", parent, nil, ChefFS::DataHandler::NodeDataHandler.new)
28
29
  end
29
30
 
30
31
  # Override children to respond to environment
@@ -22,11 +22,8 @@ module ChefFS
22
22
  module FileSystem
23
23
  class NotFoundError < FileSystemError
24
24
  def initialize(entry, cause = nil)
25
- super(cause)
26
- @entry = entry
25
+ super(entry, cause)
27
26
  end
28
-
29
- attr_reader :entry
30
27
  end
31
28
  end
32
29
  end
@@ -0,0 +1,32 @@
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_fs/file_system/file_system_error'
20
+
21
+ module ChefFS
22
+ module FileSystem
23
+ class OperationFailedError < FileSystemError
24
+ def initialize(operation, entry, cause = nil)
25
+ super(entry, cause)
26
+ @operation = operation
27
+ end
28
+
29
+ attr_reader :operation
30
+ end
31
+ end
32
+ end
@@ -22,9 +22,8 @@ module ChefFS
22
22
  module FileSystem
23
23
  class OperationNotAllowedError < FileSystemError
24
24
  def initialize(operation, entry, cause = nil)
25
- super(cause)
25
+ super(entry, cause)
26
26
  @operation = operation
27
- @entry = entry
28
27
  end
29
28
 
30
29
  attr_reader :operation
@@ -23,12 +23,14 @@ require 'chef_fs/file_system/not_found_error'
23
23
  module ChefFS
24
24
  module FileSystem
25
25
  class RestListDir < BaseFSDir
26
- def initialize(name, parent, api_path = nil)
26
+ def initialize(name, parent, api_path = nil, data_handler = nil)
27
27
  super(name, parent)
28
28
  @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}")
29
+ @data_handler = data_handler
29
30
  end
30
31
 
31
32
  attr_reader :api_path
33
+ attr_reader :data_handler
32
34
 
33
35
  def child(name)
34
36
  result = @children.select { |child| child.name == name }.first if @children
@@ -54,16 +56,41 @@ module ChefFS
54
56
  end
55
57
  end
56
58
 
59
+ def identity_key
60
+ 'name'
61
+ end
62
+
57
63
  # NOTE if you change this significantly, you will likely need to change
58
64
  # DataBagDir.create_child as well.
59
65
  def create_child(name, file_contents)
60
- json = Chef::JSONCompat.from_json(file_contents).to_hash
66
+ begin
67
+ object = Chef::JSONCompat.from_json(file_contents).to_hash
68
+ rescue JSON::ParserError => e
69
+ raise ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Parse error reading JSON creating child '#{name}': #{e}"
70
+ end
71
+
72
+ result = _make_child_entry(name, true)
73
+
74
+ if data_handler
75
+ object = data_handler.normalize(object, result)
76
+ end
77
+
61
78
  base_name = name[0,name.length-5]
62
- if json.include?('name') && json['name'] != base_name
63
- raise "Name in #{path_for_printing}/#{name} must be '#{base_name}' (is '#{json['name']}')"
79
+ if object[identity_key] != base_name
80
+ raise ChefFS::FileSystem::OperationFailedError.new(:create_child, self), "Name in #{path_for_printing}/#{name} must be '#{base_name}' (is '#{object[identity_key]}')"
64
81
  end
65
- rest.post_rest(api_path, json)
66
- _make_child_entry(name, true)
82
+
83
+ begin
84
+ rest.post_rest(api_path, object)
85
+ rescue Net::HTTPServerException => e
86
+ if e.response.code == "404"
87
+ raise ChefFS::FileSystem::NotFoundError.new(self, e)
88
+ else
89
+ raise ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Failure creating '#{name}': #{e.message}"
90
+ end
91
+ end
92
+
93
+ result
67
94
  end
68
95
 
69
96
  def environment
@@ -18,6 +18,7 @@
18
18
 
19
19
  require 'chef_fs/file_system/base_fs_object'
20
20
  require 'chef_fs/file_system/not_found_error'
21
+ require 'chef_fs/file_system/operation_failed_error'
21
22
  require 'chef/role'
22
23
  require 'chef/node'
23
24
 
@@ -29,11 +30,18 @@ module ChefFS
29
30
  @exists = exists
30
31
  end
31
32
 
32
- def api_path
33
+ def data_handler
34
+ parent.data_handler
35
+ end
36
+
37
+ def api_child_name
33
38
  if name.length < 5 || name[-5,5] != ".json"
34
39
  raise "Invalid name #{path}: must end in .json"
35
40
  end
36
- api_child_name = name[0,name.length-5]
41
+ name[0,name.length-5]
42
+ end
43
+
44
+ def api_path
37
45
  "#{parent.api_path}/#{api_child_name}"
38
46
  end
39
47
 
@@ -65,7 +73,8 @@ module ChefFS
65
73
  end
66
74
 
67
75
  def read
68
- Chef::JSONCompat.to_json_pretty(chef_object.to_hash)
76
+ # Minimize the value so the results don't look terrible
77
+ Chef::JSONCompat.to_json_pretty(minimize_value(chef_object.to_hash))
69
78
  end
70
79
 
71
80
  def chef_object
@@ -81,19 +90,38 @@ module ChefFS
81
90
  end
82
91
  end
83
92
 
93
+ def minimize_value(value)
94
+ data_handler.minimize(data_handler.normalize(value, self), self)
95
+ end
96
+
84
97
  def compare_to(other)
98
+ # Grab the other value
85
99
  begin
86
- other_value = other.read
100
+ other_value_json = other.read
87
101
  rescue ChefFS::FileSystem::NotFoundError
88
102
  return [ nil, nil, :none ]
89
103
  end
104
+
105
+ # Grab this value
90
106
  begin
91
107
  value = chef_object.to_hash
92
108
  rescue ChefFS::FileSystem::NotFoundError
93
- return [ false, :none, other_value ]
109
+ return [ false, :none, other_value_json ]
110
+ end
111
+
112
+ # Minimize (and normalize) both values for easy and beautiful diffs
113
+ value = minimize_value(value)
114
+ value_json = Chef::JSONCompat.to_json_pretty(value)
115
+ begin
116
+ other_value = Chef::JSONCompat.from_json(other_value_json, :create_additions => false)
117
+ rescue JSON::ParserError => e
118
+ Chef::Log.warn("Parse error reading #{other.path_for_printing} as JSON: #{e}")
119
+ return [ nil, value_json, other_value_json ]
94
120
  end
95
- are_same = (value == Chef::JSONCompat.from_json(other_value, :create_additions => false))
96
- [ are_same, Chef::JSONCompat.to_json_pretty(value), other_value ]
121
+ other_value = minimize_value(other_value)
122
+ other_value_json = Chef::JSONCompat.to_json_pretty(other_value)
123
+
124
+ [ value == other_value, value_json, other_value_json ]
97
125
  end
98
126
 
99
127
  def rest
@@ -101,18 +129,28 @@ module ChefFS
101
129
  end
102
130
 
103
131
  def write(file_contents)
104
- json = Chef::JSONCompat.from_json(file_contents).to_hash
132
+ begin
133
+ object = Chef::JSONCompat.from_json(file_contents).to_hash
134
+ rescue JSON::ParserError => e
135
+ raise ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Parse error reading JSON: #{e}"
136
+ end
137
+
138
+ if data_handler
139
+ object = data_handler.normalize(object, self)
140
+ end
141
+
105
142
  base_name = name[0,name.length-5]
106
- if json['name'] != base_name
107
- raise "Name in #{path_for_printing}/#{name} must be '#{base_name}' (is '#{json['name']}')"
143
+ if object['name'] != base_name
144
+ raise ChefFS::FileSystem::OperationFailedError.new(:write, self), "Name in #{path_for_printing}/#{name} must be '#{base_name}' (is '#{object['name']}')"
108
145
  end
146
+
109
147
  begin
110
- rest.put_rest(api_path, json)
111
- rescue Net::HTTPServerException
112
- if $!.response.code == "404"
113
- raise ChefFS::FileSystem::NotFoundError.new(self, $!)
148
+ rest.put_rest(api_path, object)
149
+ rescue Net::HTTPServerException => e
150
+ if e.response.code == "404"
151
+ raise ChefFS::FileSystem::NotFoundError.new(self, e)
114
152
  else
115
- raise
153
+ raise ChefFS::FileSystem::OperationFailedError.new(:write, self, e)
116
154
  end
117
155
  end
118
156
  end