knife-essentials 0.8.3 → 0.8.4

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 (30) hide show
  1. data/lib/chef/knife/diff_essentials.rb +2 -2
  2. data/lib/chef/knife/download_essentials.rb +5 -1
  3. data/lib/chef/knife/list_essentials.rb +2 -1
  4. data/lib/chef/knife/upload_essentials.rb +5 -1
  5. data/lib/chef_fs/command_line.rb +94 -63
  6. data/lib/chef_fs/file_system.rb +113 -93
  7. data/lib/chef_fs/file_system/base_fs_object.rb +91 -37
  8. data/lib/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb +14 -1
  9. data/lib/chef_fs/file_system/chef_server_root_dir.rb +2 -1
  10. data/lib/chef_fs/file_system/cookbook_file.rb +7 -1
  11. data/lib/chef_fs/file_system/cookbooks_dir.rb +13 -6
  12. data/lib/chef_fs/file_system/environments_dir.rb +59 -0
  13. data/lib/chef_fs/file_system/multiplexed_dir.rb +1 -1
  14. data/lib/chef_fs/file_system/nodes_dir.rb +1 -0
  15. data/lib/chef_fs/file_system/operation_not_allowed_error.rb +29 -0
  16. data/lib/chef_fs/file_system/operation_skipped_error.rb +29 -0
  17. data/lib/chef_fs/knife.rb +29 -21
  18. data/lib/chef_fs/version.rb +1 -1
  19. data/spec/chef_fs/diff_spec.rb +30 -30
  20. data/spec/chef_fs/file_system/cookbooks_dir_spec.rb +5 -1
  21. data/spec/integration/chef_repo_path_spec.rb +705 -0
  22. data/spec/integration/chef_repository_file_system_spec.rb +82 -713
  23. data/spec/integration/chefignore_spec.rb +258 -0
  24. data/spec/integration/diff_spec.rb +151 -0
  25. data/spec/integration/download_spec.rb +403 -0
  26. data/spec/integration/list_spec.rb +21 -21
  27. data/spec/integration/upload_spec.rb +407 -0
  28. data/spec/support/integration_helper.rb +9 -4
  29. data/spec/support/knife_support.rb +14 -2
  30. metadata +12 -3
@@ -17,6 +17,7 @@
17
17
  #
18
18
 
19
19
  require 'chef_fs/path_utils'
20
+ require 'chef_fs/file_system/operation_not_allowed_error'
20
21
 
21
22
  module ChefFS
22
23
  module FileSystem
@@ -38,42 +39,6 @@ module ChefFS
38
39
  attr_reader :parent
39
40
  attr_reader :path
40
41
 
41
- def root
42
- parent ? parent.root : self
43
- end
44
-
45
- def path_for_printing
46
- if parent
47
- parent_path = parent.path_for_printing
48
- if parent_path == '.'
49
- name
50
- else
51
- ChefFS::PathUtils::join(parent.path_for_printing, name)
52
- end
53
- else
54
- name
55
- end
56
- end
57
-
58
- def dir?
59
- false
60
- end
61
-
62
- def exists?
63
- true
64
- end
65
-
66
- def child(name)
67
- NonexistentFSObject.new(name, self)
68
- end
69
-
70
- # Override can_have_child? to report whether a given file *could* be added
71
- # to this directory. (Some directories can't have subdirs, some can only have .json
72
- # files, etc.)
73
- def can_have_child?(name, is_dir)
74
- false
75
- end
76
-
77
42
  # Override this if you have a special comparison algorithm that can tell
78
43
  # you whether this entry is the same as another--either a quicker or a
79
44
  # more reliable one. Callers will use this to decide whether to upload,
@@ -111,14 +76,103 @@ module ChefFS
111
76
  nil
112
77
  end
113
78
 
79
+ # Override can_have_child? to report whether a given file *could* be added
80
+ # to this directory. (Some directories can't have subdirs, some can only have .json
81
+ # files, etc.)
82
+ def can_have_child?(name, is_dir)
83
+ false
84
+ end
85
+
86
+ # Get a child of this entry with the given name. This MUST always
87
+ # return a child, even if it is NonexistentFSObject. Overriders should
88
+ # take caution not to do expensive network requests to get the list of
89
+ # children to fulfill this request, unless absolutely necessary here; it
90
+ # is intended as a quick way to traverse a hierarchy.
91
+ #
92
+ # For example, knife show /data_bags/x/y.json will call
93
+ # root.child('data_bags').child('x').child('y.json'), which can then
94
+ # directly perform a network request to retrieve the y.json data bag. No
95
+ # network request was necessary to retrieve
96
+ def child(name)
97
+ NonexistentFSObject.new(name, self)
98
+ end
99
+
100
+ # Override children to report your *actual* list of children as an array.
101
+ def children
102
+ raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
103
+ []
104
+ end
105
+
106
+ # Expand this entry into a chef object (Chef::Role, ::Node, etc.)
114
107
  def chef_object
115
- raise ChefFS::FileSystem::NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
108
+ raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
116
109
  nil
117
110
  end
118
111
 
112
+ # Create a child of this entry with the given name and contents. If
113
+ # contents is nil, create a directory.
114
+ #
115
+ # NOTE: create_child_from is an optional method that can also be added to
116
+ # your entry class, and will be called without actually reading the
117
+ # file_contents. This is used for knife upload /cookbooks/cookbookname.
118
+ def create_child(name, file_contents)
119
+ raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
120
+ raise OperationNotAllowedError.new(:create_child), "#{path_for_printing} cannot have a child created under it."
121
+ end
122
+
123
+ # Delete this item, possibly recursively. Entries MUST NOT delete a
124
+ # directory unless recurse is true.
125
+ def delete(recurse)
126
+ raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
127
+ raise OperationNotAllowedError.new(:delete), "#{path_for_printing} cannot be deleted."
128
+ end
129
+
130
+ # Ask whether this entry is a directory. If not, it is a file.
131
+ def dir?
132
+ false
133
+ end
134
+
135
+ # Ask whether this entry exists.
136
+ def exists?
137
+ true
138
+ end
139
+
140
+ # Printable path, generally used to distinguish paths in one root from
141
+ # paths in another.
142
+ def path_for_printing
143
+ if parent
144
+ parent_path = parent.path_for_printing
145
+ if parent_path == '.'
146
+ name
147
+ else
148
+ ChefFS::PathUtils::join(parent.path_for_printing, name)
149
+ end
150
+ else
151
+ name
152
+ end
153
+ end
154
+
155
+ def root
156
+ parent ? parent.root : self
157
+ end
158
+
159
+ # Read the contents of this file entry.
160
+ def read
161
+ raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
162
+ raise OperationNotAllowedError.new(:read), "#{path_for_printing} cannot be read."
163
+ end
164
+
165
+ # Write the contents of this file entry.
166
+ def write(file_contents)
167
+ raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
168
+ raise OperationNotAllowedError.new(:write), "#{path_for_printing} cannot be updated."
169
+ end
170
+
119
171
  # Important directory attributes: name, parent, path, root
120
172
  # Overridable attributes: dir?, child(name), path_for_printing
121
173
  # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to
122
174
  end
123
175
  end
124
176
  end
177
+
178
+ require 'chef_fs/file_system/nonexistent_fs_object'
@@ -24,7 +24,11 @@ module ChefFS
24
24
  class ChefRepositoryFileSystemCookbooksDir < ChefRepositoryFileSystemEntry
25
25
  def initialize(name, parent, file_path)
26
26
  super(name, parent, file_path)
27
- @chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
27
+ begin
28
+ @chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
29
+ rescue Errno::EISDIR
30
+ # Work around a bug in Chefignore when chefignore is a directory
31
+ end
28
32
  end
29
33
 
30
34
  attr_reader :chefignore
@@ -32,6 +36,15 @@ module ChefFS
32
36
  def ignore_empty_directories?
33
37
  true
34
38
  end
39
+
40
+ def ignored?(entry)
41
+ return true if !entry.dir?
42
+ result = super(entry)
43
+ if result
44
+ Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
45
+ end
46
+ result
47
+ end
35
48
  end
36
49
  end
37
50
  end
@@ -21,6 +21,7 @@ require 'chef_fs/file_system/rest_list_dir'
21
21
  require 'chef_fs/file_system/cookbooks_dir'
22
22
  require 'chef_fs/file_system/data_bags_dir'
23
23
  require 'chef_fs/file_system/nodes_dir'
24
+ require 'chef_fs/file_system/environments_dir'
24
25
 
25
26
  module ChefFS
26
27
  module FileSystem
@@ -62,7 +63,7 @@ module ChefFS
62
63
  result = [
63
64
  CookbooksDir.new(self),
64
65
  DataBagsDir.new(self),
65
- RestListDir.new("environments", self),
66
+ EnvironmentsDir.new(self),
66
67
  RestListDir.new("roles", self)
67
68
  ]
68
69
  if repo_mode == 'everything'
@@ -37,7 +37,13 @@ module ChefFS
37
37
  old_sign_on_redirect = rest.sign_on_redirect
38
38
  rest.sign_on_redirect = false
39
39
  begin
40
- rest.get_rest(file[:url])
40
+ begin
41
+ tmpfile = rest.get_rest(file[:url], true)
42
+ tmpfile.open
43
+ tmpfile.read
44
+ ensure
45
+ tmpfile.close!
46
+ end
41
47
  ensure
42
48
  rest.sign_on_redirect = old_sign_on_redirect
43
49
  end
@@ -44,12 +44,19 @@ module ChefFS
44
44
  # TODO this only works on the file system. And it can't be broken into
45
45
  # pieces.
46
46
  begin
47
- uploader = Chef::CookbookUploader.new(other_cookbook_version, other.parent.file_path)
48
- # Chef 11 changes this API
49
- if uploader.respond_to?(:upload_cookbook)
50
- uploader.upload_cookbook
51
- else
52
- uploader.upload_cookbooks
47
+ uploader = Chef::CookbookUploader.new(other_cookbook_version, other.parent.file_path, :rest => rest)
48
+ # Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet)
49
+ old_cookbook_path = Chef::Config.cookbook_path
50
+ Chef::Config.cookbook_path = other.parent.file_path if !Chef::Config.cookbook_path
51
+ begin
52
+ # Chef 11 changes this API
53
+ if uploader.respond_to?(:upload_cookbook)
54
+ uploader.upload_cookbook
55
+ else
56
+ uploader.upload_cookbooks
57
+ end
58
+ ensure
59
+ Chef::Config.cookbook_path = old_cookbook_path
53
60
  end
54
61
  rescue Net::HTTPServerException => e
55
62
  case e.response.code
@@ -0,0 +1,59 @@
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/base_fs_dir'
20
+ require 'chef_fs/file_system/rest_list_entry'
21
+ require 'chef_fs/file_system/not_found_error'
22
+ require 'chef_fs/file_system/operation_skipped_error'
23
+
24
+ module ChefFS
25
+ module FileSystem
26
+ class EnvironmentsDir < RestListDir
27
+ def initialize(parent)
28
+ super("environments", parent)
29
+ end
30
+
31
+ def _make_child_entry(name, exists = nil)
32
+ if name == '_default.json'
33
+ DefaultEnvironmentEntry.new(name, self, exists)
34
+ else
35
+ super
36
+ end
37
+ end
38
+
39
+ class DefaultEnvironmentEntry < RestListEntry
40
+ def initialize(name, parent, exists = nil)
41
+ super(name, parent)
42
+ @exists = exists
43
+ end
44
+
45
+ def delete(recurse)
46
+ Chef::Log.warn("The default environment (#{name}) cannot be deleted. Skipping.")
47
+ raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
48
+ raise OperationSkippedError.new(:delete), "#{path_for_printing} cannot be deleted."
49
+ end
50
+
51
+ def write(file_contents)
52
+ Chef::Log.warn("The default environment (#{name}) cannot be deleted. Skipping.")
53
+ raise NotFoundError, "Nonexistent #{path_for_printing}" if !exists?
54
+ raise OperationSkippedError.new(:write), "#{path_for_printing} cannot be updated."
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -23,7 +23,7 @@ module ChefFS
23
23
  multiplexed_dirs.each do |dir|
24
24
  dir.children.each do |child|
25
25
  if seen[child.name]
26
- Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{child.path_for_printing} and #{seen[child.name].path_for_printing}")
26
+ Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{seen[child.name].path_for_printing} and #{child.path_for_printing}")
27
27
  else
28
28
  result << child
29
29
  seen[child.name] = child
@@ -28,6 +28,7 @@ module ChefFS
28
28
  end
29
29
 
30
30
  # Override children to respond to environment
31
+ # TODO let's not do this mmkay
31
32
  def children
32
33
  @children ||= begin
33
34
  env_api_path = environment ? "environments/#{environment}/#{api_path}" : api_path
@@ -0,0 +1,29 @@
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 OperationNotAllowedError < FileSystemError
24
+ def initialize(operation, cause = nil)
25
+ super(cause)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
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/operation_not_allowed_error'
20
+
21
+ module ChefFS
22
+ module FileSystem
23
+ class OperationSkippedError < OperationNotAllowedError
24
+ def initialize(operation, cause = nil)
25
+ super(operation, cause)
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/chef_fs/knife.rb CHANGED
@@ -35,32 +35,38 @@ module ChefFS
35
35
  @chef_fs ||= ChefFS::FileSystem::ChefServerRootDir.new("remote", Chef::Config, config[:repo_mode])
36
36
  end
37
37
 
38
- def chef_repo_path
39
- @chef_repo_path ||= begin
40
- if Chef::Config.chef_repo_path
41
- File.expand_path(Chef::Config.chef_repo_path)
42
- elsif Chef::Config.cookbook_path
43
- File.expand_path('..', Array(Chef::Config.cookbook_path).flatten.first)
38
+ def chef_repo_paths
39
+ @chef_repo_paths ||= begin
40
+ result = config_paths(:chef_repo_path)
41
+ if result
42
+ result
44
43
  else
45
- nil
44
+ if Chef::Config[:cookbook_path]
45
+ Array(Chef::Config[:cookbook_path]).flatten.map { |path| File.expand_path('..', path) }
46
+ else
47
+ nil
48
+ end
46
49
  end
47
50
  end
48
51
  end
49
52
 
50
53
  # Smooth out some inappropriate (for now) variable defaults in Chef.
51
- def config_var(name)
52
- case name
54
+ def config_paths(name)
55
+ result = case name
53
56
  when :data_bag_path
54
57
  Chef::Config[name] == Chef::Config.platform_specific_path('/var/chef/data_bags') ? nil : Chef::Config[name]
55
58
  when :node_path
56
59
  Chef::Config[name] == '/var/chef/node' ? nil : Chef::Config[name]
57
60
  when :role_path
58
61
  Chef::Config[name] == Chef::Config.platform_specific_path('/var/chef/roles') ? nil : Chef::Config[name]
59
- when :chef_repo_path
60
- chef_repo_path
61
62
  else
62
63
  Chef::Config[name]
63
64
  end
65
+ if result
66
+ Array(result).flatten
67
+ else
68
+ nil
69
+ end
64
70
  end
65
71
 
66
72
  def object_paths
@@ -73,17 +79,15 @@ module ChefFS
73
79
  end
74
80
  object_names.each do |object_name|
75
81
  variable_name = "#{object_name[0..-2]}_path" # cookbooks -> cookbook_path
76
- paths = config_var(variable_name.to_sym)
82
+ paths = config_paths(variable_name.to_sym)
77
83
  if !paths
78
- if !chef_repo_path
79
- # TODO if chef_repo is not specified and repo_mode does not require
80
- # clients/users/nodes, don't require them to be specified.
84
+ if !chef_repo_paths
81
85
  Chef::Log.error("Must specify either chef_repo_path or #{variable_name} in Chef config file")
82
86
  exit(1)
83
87
  end
84
- paths = File.join(chef_repo_path, object_name)
88
+ paths = chef_repo_paths.map { |path| File.join(path, object_name) }
85
89
  end
86
- paths = Array(paths).flatten.map { |path| File.expand_path(path) }
90
+ paths = paths.flatten.map { |path| File.expand_path(path) }
87
91
  result[object_name] = paths
88
92
  end
89
93
  result
@@ -118,7 +122,9 @@ module ChefFS
118
122
  object_paths.each_pair do |name, paths|
119
123
  paths.each do |path|
120
124
  realest_path = ChefFS::PathUtils.realest_path(path)
121
- if absolute_path[0,realest_path.length] == realest_path
125
+ if absolute_path[0,realest_path.length] == realest_path &&
126
+ (absolute_path.length == realest_path.length ||
127
+ absolute_path[realest_path.length] =~ /#{PathUtils.regexp_path_separator}/)
122
128
  relative_path = ChefFS::PathUtils::relative_to(absolute_path, realest_path)
123
129
  return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
124
130
  end
@@ -126,9 +132,11 @@ module ChefFS
126
132
  end
127
133
 
128
134
  # Check chef_repo_path
129
- realest_chef_repo_path = ChefFS::PathUtils.realest_path(chef_repo_path)
130
- if absolute_path == realest_chef_repo_path
131
- return '/'
135
+ chef_repo_paths.each do |chef_repo_path|
136
+ realest_chef_repo_path = ChefFS::PathUtils.realest_path(chef_repo_path)
137
+ if absolute_path == realest_chef_repo_path
138
+ return '/'
139
+ end
132
140
  end
133
141
 
134
142
  nil