knife-essentials 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. data/LICENSE +201 -0
  2. data/README.rdoc +114 -0
  3. data/Rakefile +24 -0
  4. data/lib/chef/knife/diff.rb +28 -0
  5. data/lib/chef/knife/list.rb +107 -0
  6. data/lib/chef/knife/show.rb +30 -0
  7. data/lib/chef_fs.rb +9 -0
  8. data/lib/chef_fs/command_line.rb +91 -0
  9. data/lib/chef_fs/diff.rb +158 -0
  10. data/lib/chef_fs/file_pattern.rb +292 -0
  11. data/lib/chef_fs/file_system.rb +69 -0
  12. data/lib/chef_fs/file_system/base_fs_dir.rb +23 -0
  13. data/lib/chef_fs/file_system/base_fs_object.rb +52 -0
  14. data/lib/chef_fs/file_system/chef_server_root_dir.rb +48 -0
  15. data/lib/chef_fs/file_system/cookbook_dir.rb +80 -0
  16. data/lib/chef_fs/file_system/cookbook_file.rb +32 -0
  17. data/lib/chef_fs/file_system/cookbook_subdir.rb +25 -0
  18. data/lib/chef_fs/file_system/cookbooks_dir.rb +21 -0
  19. data/lib/chef_fs/file_system/data_bag_dir.rb +22 -0
  20. data/lib/chef_fs/file_system/data_bags_dir.rb +23 -0
  21. data/lib/chef_fs/file_system/file_system_entry.rb +36 -0
  22. data/lib/chef_fs/file_system/file_system_root_dir.rb +11 -0
  23. data/lib/chef_fs/file_system/nonexistent_fs_object.rb +23 -0
  24. data/lib/chef_fs/file_system/not_found_exception.rb +12 -0
  25. data/lib/chef_fs/file_system/rest_list_dir.rb +42 -0
  26. data/lib/chef_fs/file_system/rest_list_entry.rb +72 -0
  27. data/lib/chef_fs/knife.rb +47 -0
  28. data/lib/chef_fs/path_utils.rb +43 -0
  29. data/lib/chef_fs/version.rb +4 -0
  30. data/spec/chef_fs/diff_spec.rb +263 -0
  31. data/spec/chef_fs/file_pattern_spec.rb +507 -0
  32. data/spec/chef_fs/file_system_spec.rb +117 -0
  33. data/spec/support/file_system_support.rb +108 -0
  34. metadata +79 -0
@@ -0,0 +1,11 @@
1
+ require 'chef_fs/file_system/file_system_entry'
2
+
3
+ module ChefFS
4
+ module FileSystem
5
+ class FileSystemRootDir < FileSystemEntry
6
+ def initialize(file_path)
7
+ super("", nil, file_path)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ require 'chef_fs/file_system/base_fs_object'
2
+
3
+ module ChefFS
4
+ module FileSystem
5
+ class NonexistentFSObject < BaseFSObject
6
+ def initialize(name, parent)
7
+ super
8
+ end
9
+
10
+ def exists?
11
+ false
12
+ end
13
+
14
+ def child(name)
15
+ NonexistentFSObject.new(name, self)
16
+ end
17
+
18
+ def read
19
+ raise ChefFS::FileSystem::NotFoundException, "Nonexistent object"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ module ChefFS
2
+ module FileSystem
3
+ class NotFoundException < Exception
4
+ def initialize(other_exception)
5
+ super(other_exception.respond_to?(:message) ? other_exception.message : message)
6
+ @exception = other_exception
7
+ end
8
+
9
+ attr_reader :exception
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,42 @@
1
+ require 'chef_fs/file_system/base_fs_dir'
2
+ require 'chef_fs/file_system/rest_list_entry'
3
+
4
+ # TODO: take environment into account
5
+
6
+ module ChefFS
7
+ module FileSystem
8
+ class RestListDir < BaseFSDir
9
+ def initialize(name, parent, api_path = nil)
10
+ super(name, parent)
11
+ @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}")
12
+ end
13
+
14
+ attr_reader :api_path
15
+
16
+ def child(name)
17
+ result = @children.select { |child| child.name == name }.first if @children
18
+ result || RestListEntry.new(name, self)
19
+ end
20
+
21
+ def children
22
+ begin
23
+ @children ||= rest.get_rest(api_path).keys.map { |key| RestListEntry.new("#{key}.json", self, true) }
24
+ rescue Net::HTTPServerException
25
+ if $!.response.code == "404"
26
+ raise ChefFS::FileSystem::NotFoundException, $!
27
+ else
28
+ raise
29
+ end
30
+ end
31
+ end
32
+
33
+ def environment
34
+ parent.environment
35
+ end
36
+
37
+ def rest
38
+ parent.rest
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,72 @@
1
+ require 'chef_fs/file_system/base_fs_object'
2
+ require 'chef_fs/file_system/not_found_exception'
3
+ # TODO: these are needed for rest.get_rest() to work. This seems strange.
4
+ require 'chef/role'
5
+ require 'chef/node'
6
+
7
+ # TODO: take environment into account
8
+ module ChefFS
9
+ module FileSystem
10
+ class RestListEntry < BaseFSObject
11
+ def initialize(name, parent, exists = nil)
12
+ super(name, parent)
13
+ @exists = exists
14
+ end
15
+
16
+ def api_path
17
+ if name.length < 5 && name[-5,5] != ".json"
18
+ raise "Invalid name #{name}: must include .json"
19
+ end
20
+ api_child_name = name[0,name.length-5]
21
+ environment ? "#{parent.api_path}/#{api_child_name}/environments/#{environment}" : "#{parent.api_path}/#{api_child_name}"
22
+ end
23
+
24
+ def environment
25
+ parent.environment
26
+ end
27
+
28
+ def exists?
29
+ if @exists.nil?
30
+ @exists = parent.children.any? { |child| child.name == name }
31
+ end
32
+ @exists
33
+ end
34
+
35
+ def delete
36
+ begin
37
+ rest.delete_rest(api_path)
38
+ rescue Net::HTTPServerException
39
+ if $!.response.code == "404"
40
+ raise ChefFS::FileSystem::NotFoundException, $!
41
+ else
42
+ raise
43
+ end
44
+ end
45
+ end
46
+
47
+ def read
48
+ begin
49
+ Chef::JSONCompat.to_json_pretty(rest.get_rest(api_path).to_hash)
50
+ rescue Net::HTTPServerException
51
+ if $!.response.code == "404"
52
+ raise ChefFS::FileSystem::NotFoundException, $!
53
+ else
54
+ raise
55
+ end
56
+ end
57
+ end
58
+
59
+ def rest
60
+ parent.rest
61
+ end
62
+
63
+ def content_type
64
+ :json
65
+ end
66
+
67
+ def write(contents)
68
+ rest.put_rest(api_path, contents)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ require 'chef_fs/file_system/chef_server_root_dir'
2
+ require 'chef_fs/file_system/file_system_root_dir'
3
+ require 'chef_fs/file_pattern'
4
+ require 'chef_fs/path_utils'
5
+ require 'chef/config'
6
+
7
+ module ChefFS
8
+ class Knife < Chef::Knife
9
+ def base_path
10
+ @base_path ||= "/" + ChefFS::PathUtils::relative_to(File.absolute_path(Dir.pwd), chef_repo)
11
+ end
12
+
13
+ def chef_fs
14
+ @chef_fs ||= ChefFS::FileSystem::ChefServerRootDir.new("remote", Chef::Config)
15
+ end
16
+
17
+ def chef_repo
18
+ @chef_repo ||= File.absolute_path(File.join(Chef::Config.cookbook_path, ".."))
19
+ end
20
+
21
+ def format_path(path)
22
+ if path[0,base_path.length] == base_path
23
+ if path == base_path
24
+ return "."
25
+ elsif path[base_path.length] == "/"
26
+ return path[base_path.length + 1, path.length - base_path.length - 1]
27
+ elsif base_path == "/" && path[0] == "/"
28
+ return path[1, path.length - 1]
29
+ end
30
+ end
31
+ path
32
+ end
33
+
34
+ def local_fs
35
+ @local_fs ||= ChefFS::FileSystem::FileSystemRootDir.new(chef_repo)
36
+ end
37
+
38
+ def pattern_args
39
+ @pattern_args ||= pattern_args_from(name_args)
40
+ end
41
+
42
+ def pattern_args_from(args)
43
+ args.map { |arg| ChefFS::FilePattern::relative_to(base_path, arg) }.to_a
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,43 @@
1
+ require 'chef_fs'
2
+
3
+ module ChefFS
4
+ class PathUtils
5
+
6
+ # If you are in 'source', this is what you would have to type to reach 'dest'
7
+ # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
8
+ # relative_to('/a/b', '/a/b') == ''
9
+ def self.relative_to(dest, source)
10
+ # Skip past the common parts
11
+ source_parts = ChefFS::PathUtils.split(source)
12
+ dest_parts = ChefFS::PathUtils.split(dest)
13
+ i = 0
14
+ until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != source_parts[i]
15
+ i+=1
16
+ end
17
+ # dot-dot up from 'source' to the common ancestor, then
18
+ # descend to 'dest' from the common ancestor
19
+ ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i]))
20
+ end
21
+
22
+ def self.join(*parts)
23
+ return "" if parts.length == 0
24
+ # Determine if it started with a slash
25
+ absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0][0] =~ /^#{regexp_path_separator}/
26
+ # Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away)
27
+ parts = parts.map { |part| part.gsub(/^\/|\/$/, "") }
28
+ # Don't join empty bits
29
+ result = parts.select { |part| part != "" }.join("/")
30
+ # Put the / back on
31
+ absolute ? "/#{result}" : result
32
+ end
33
+
34
+ def self.split(path)
35
+ path.split(Regexp.new(regexp_path_separator))
36
+ end
37
+
38
+ def self.regexp_path_separator
39
+ ChefFS::windows? ? '[/\\]' : '/'
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ module ChefFS
2
+ VERSION = "0.1"
3
+ end
4
+
@@ -0,0 +1,263 @@
1
+ require 'support/file_system_support'
2
+ require 'chef_fs/diff'
3
+ require 'chef_fs/file_pattern'
4
+ require 'chef_fs/command_line'
5
+
6
+ describe ChefFS::Diff do
7
+ include FileSystemSupport
8
+
9
+ context 'with two filesystems with all types of difference' do
10
+ let(:a) {
11
+ memory_fs('a', {
12
+ :both_dirs => {
13
+ :sub_both_dirs => { :subsub => nil },
14
+ :sub_both_files => nil,
15
+ :sub_both_files_different => "a\n",
16
+ :sub_both_dirs_empty => {},
17
+ :sub_a_only_dir => { :subsub => nil },
18
+ :sub_a_only_file => nil,
19
+ :sub_dir_in_a_file_in_b => {},
20
+ :sub_file_in_a_dir_in_b => nil
21
+ },
22
+ :both_files => nil,
23
+ :both_files_different => "a\n",
24
+ :both_dirs_empty => {},
25
+ :a_only_dir => { :subsub => nil },
26
+ :a_only_file => nil,
27
+ :dir_in_a_file_in_b => {},
28
+ :file_in_a_dir_in_b => nil
29
+ })
30
+ }
31
+ let(:b) {
32
+ memory_fs('b', {
33
+ :both_dirs => {
34
+ :sub_both_dirs => { :subsub => nil },
35
+ :sub_both_files => nil,
36
+ :sub_both_files_different => "b\n",
37
+ :sub_both_dirs_empty => {},
38
+ :sub_b_only_dir => { :subsub => nil },
39
+ :sub_b_only_file => nil,
40
+ :sub_dir_in_a_file_in_b => nil,
41
+ :sub_file_in_a_dir_in_b => {}
42
+ },
43
+ :both_files => nil,
44
+ :both_files_different => "b\n",
45
+ :both_dirs_empty => {},
46
+ :b_only_dir => { :subsub => nil },
47
+ :b_only_file => nil,
48
+ :dir_in_a_file_in_b => nil,
49
+ :file_in_a_dir_in_b => {}
50
+ })
51
+ }
52
+ it 'diffable_leaves' do
53
+ diffable_leaves_should_yield_paths(a, b, nil,
54
+ %w(
55
+ /both_dirs/sub_both_dirs/subsub
56
+ /both_dirs/sub_both_files
57
+ /both_dirs/sub_both_files_different
58
+ /both_dirs/sub_both_dirs_empty
59
+ /both_dirs/sub_a_only_dir
60
+ /both_dirs/sub_a_only_file
61
+ /both_dirs/sub_b_only_dir
62
+ /both_dirs/sub_b_only_file
63
+ /both_dirs/sub_dir_in_a_file_in_b
64
+ /both_dirs/sub_file_in_a_dir_in_b
65
+ /both_files
66
+ /both_files_different
67
+ /both_dirs_empty
68
+ /a_only_dir
69
+ /a_only_file
70
+ /b_only_dir
71
+ /b_only_file
72
+ /dir_in_a_file_in_b
73
+ /file_in_a_dir_in_b
74
+ ))
75
+ end
76
+ it 'diffable_leaves_from_pattern(/**file*)' do
77
+ diffable_leaves_from_pattern_should_yield_paths(pattern('/**file*'), a, b, nil,
78
+ %w(
79
+ /both_dirs/sub_both_files
80
+ /both_dirs/sub_both_files_different
81
+ /both_dirs/sub_a_only_file
82
+ /both_dirs/sub_b_only_file
83
+ /both_dirs/sub_dir_in_a_file_in_b
84
+ /both_dirs/sub_file_in_a_dir_in_b
85
+ /both_files
86
+ /both_files_different
87
+ /a_only_file
88
+ /b_only_file
89
+ /dir_in_a_file_in_b
90
+ /file_in_a_dir_in_b
91
+ ))
92
+ end
93
+ it 'diffable_leaves_from_pattern(/*dir*)' do
94
+ diffable_leaves_from_pattern_should_yield_paths(pattern('/*dir*'), a, b, nil,
95
+ %w(
96
+ /both_dirs/sub_both_dirs/subsub
97
+ /both_dirs/sub_both_files
98
+ /both_dirs/sub_both_files_different
99
+ /both_dirs/sub_both_dirs_empty
100
+ /both_dirs/sub_a_only_dir
101
+ /both_dirs/sub_a_only_file
102
+ /both_dirs/sub_b_only_dir
103
+ /both_dirs/sub_b_only_file
104
+ /both_dirs/sub_dir_in_a_file_in_b
105
+ /both_dirs/sub_file_in_a_dir_in_b
106
+ /both_dirs_empty
107
+ /a_only_dir
108
+ /b_only_dir
109
+ /dir_in_a_file_in_b
110
+ /file_in_a_dir_in_b
111
+ ))
112
+ end
113
+ it 'ChefFS::CommandLine.diff(/)' do
114
+ results = []
115
+ ChefFS::CommandLine.diff(pattern('/'), a, b, nil) do |diff|
116
+ results << diff.gsub(/\s+\d\d\d\d-\d\d-\d\d\s\d?\d:\d\d:\d\d\.\d{9} -\d\d\d\d/, ' DATE')
117
+ end
118
+ results.should =~ [
119
+ 'diff --knife a/both_dirs/sub_both_files_different b/both_dirs/sub_both_files_different
120
+ --- a/both_dirs/sub_both_files_different DATE
121
+ +++ b/both_dirs/sub_both_files_different DATE
122
+ @@ -1 +1 @@
123
+ -a
124
+ +b
125
+ ','Common subdirectories: /both_dirs/sub_both_dirs_empty
126
+ ','Only in a/both_dirs: sub_a_only_dir
127
+ ','diff --knife a/both_dirs/sub_a_only_file b/both_dirs/sub_a_only_file
128
+ deleted file
129
+ --- a/both_dirs/sub_a_only_file DATE
130
+ +++ /dev/null DATE
131
+ @@ -1 +0,0 @@
132
+ -sub_a_only_file
133
+ ','File b/both_dirs/sub_dir_in_a_file_in_b is a directory while file b/both_dirs/sub_dir_in_a_file_in_b is a regular file
134
+ ','File a/both_dirs/sub_file_in_a_dir_in_b is a regular file while file a/both_dirs/sub_file_in_a_dir_in_b is a directory
135
+ ','Only in b/both_dirs: sub_b_only_dir
136
+ ','diff --knife a/both_dirs/sub_b_only_file b/both_dirs/sub_b_only_file
137
+ new file
138
+ --- /dev/null DATE
139
+ +++ b/both_dirs/sub_b_only_file DATE
140
+ @@ -0,0 +1 @@
141
+ +sub_b_only_file
142
+ ','diff --knife a/both_files_different b/both_files_different
143
+ --- a/both_files_different DATE
144
+ +++ b/both_files_different DATE
145
+ @@ -1 +1 @@
146
+ -a
147
+ +b
148
+ ','Common subdirectories: /both_dirs_empty
149
+ ','Only in a: a_only_dir
150
+ ','diff --knife a/a_only_file b/a_only_file
151
+ deleted file
152
+ --- a/a_only_file DATE
153
+ +++ /dev/null DATE
154
+ @@ -1 +0,0 @@
155
+ -a_only_file
156
+ ','File b/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file
157
+ ','File a/file_in_a_dir_in_b is a regular file while file a/file_in_a_dir_in_b is a directory
158
+ ','Only in b: b_only_dir
159
+ ','diff --knife a/b_only_file b/b_only_file
160
+ new file
161
+ --- /dev/null DATE
162
+ +++ b/b_only_file DATE
163
+ @@ -0,0 +1 @@
164
+ +b_only_file
165
+ ' ]
166
+ end
167
+ it 'ChefFS::CommandLine.diff(/both_dirs)' do
168
+ results = []
169
+ ChefFS::CommandLine.diff(pattern('/both_dirs'), a, b, nil) do |diff|
170
+ results << diff.gsub(/\s+\d\d\d\d-\d\d-\d\d\s\d?\d:\d\d:\d\d\.\d{9} -\d\d\d\d/, ' DATE')
171
+ end
172
+ results.should =~ [
173
+ 'diff --knife a/both_dirs/sub_both_files_different b/both_dirs/sub_both_files_different
174
+ --- a/both_dirs/sub_both_files_different DATE
175
+ +++ b/both_dirs/sub_both_files_different DATE
176
+ @@ -1 +1 @@
177
+ -a
178
+ +b
179
+ ','Common subdirectories: /both_dirs/sub_both_dirs_empty
180
+ ','Only in a/both_dirs: sub_a_only_dir
181
+ ','diff --knife a/both_dirs/sub_a_only_file b/both_dirs/sub_a_only_file
182
+ deleted file
183
+ --- a/both_dirs/sub_a_only_file DATE
184
+ +++ /dev/null DATE
185
+ @@ -1 +0,0 @@
186
+ -sub_a_only_file
187
+ ','File b/both_dirs/sub_dir_in_a_file_in_b is a directory while file b/both_dirs/sub_dir_in_a_file_in_b is a regular file
188
+ ','File a/both_dirs/sub_file_in_a_dir_in_b is a regular file while file a/both_dirs/sub_file_in_a_dir_in_b is a directory
189
+ ','Only in b/both_dirs: sub_b_only_dir
190
+ ','diff --knife a/both_dirs/sub_b_only_file b/both_dirs/sub_b_only_file
191
+ new file
192
+ --- /dev/null DATE
193
+ +++ b/both_dirs/sub_b_only_file DATE
194
+ @@ -0,0 +1 @@
195
+ +sub_b_only_file
196
+ ' ]
197
+ end
198
+ it 'ChefFS::CommandLine.diff(/) with depth 1' do
199
+ results = []
200
+ ChefFS::CommandLine.diff(pattern('/'), a, b, 1) do |diff|
201
+ results << diff.gsub(/\s+\d\d\d\d-\d\d-\d\d\s\d?\d:\d\d:\d\d\.\d{9} -\d\d\d\d/, ' DATE')
202
+ end
203
+ results.should =~ [
204
+ 'Common subdirectories: /both_dirs
205
+ ','diff --knife a/both_files_different b/both_files_different
206
+ --- a/both_files_different DATE
207
+ +++ b/both_files_different DATE
208
+ @@ -1 +1 @@
209
+ -a
210
+ +b
211
+ ','Common subdirectories: /both_dirs_empty
212
+ ','Only in a: a_only_dir
213
+ ','diff --knife a/a_only_file b/a_only_file
214
+ deleted file
215
+ --- a/a_only_file DATE
216
+ +++ /dev/null DATE
217
+ @@ -1 +0,0 @@
218
+ -a_only_file
219
+ ','File b/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file
220
+ ','File a/file_in_a_dir_in_b is a regular file while file a/file_in_a_dir_in_b is a directory
221
+ ','Only in b: b_only_dir
222
+ ','diff --knife a/b_only_file b/b_only_file
223
+ new file
224
+ --- /dev/null DATE
225
+ +++ b/b_only_file DATE
226
+ @@ -0,0 +1 @@
227
+ +b_only_file
228
+ ' ]
229
+ end
230
+ it 'ChefFS::CommandLine.diff(/*_*) with depth 0' do
231
+ results = []
232
+ ChefFS::CommandLine.diff(pattern('/*_*'), a, b, 0) do |diff|
233
+ results << diff.gsub(/\s+\d\d\d\d-\d\d-\d\d\s\d?\d:\d\d:\d\d\.\d{9} -\d\d\d\d/, ' DATE')
234
+ end
235
+ results.should =~ [
236
+ 'Common subdirectories: /both_dirs
237
+ ','diff --knife a/both_files_different b/both_files_different
238
+ --- a/both_files_different DATE
239
+ +++ b/both_files_different DATE
240
+ @@ -1 +1 @@
241
+ -a
242
+ +b
243
+ ','Common subdirectories: /both_dirs_empty
244
+ ','Only in a: a_only_dir
245
+ ','diff --knife a/a_only_file b/a_only_file
246
+ deleted file
247
+ --- a/a_only_file DATE
248
+ +++ /dev/null DATE
249
+ @@ -1 +0,0 @@
250
+ -a_only_file
251
+ ','File b/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file
252
+ ','File a/file_in_a_dir_in_b is a regular file while file a/file_in_a_dir_in_b is a directory
253
+ ','Only in b: b_only_dir
254
+ ','diff --knife a/b_only_file b/b_only_file
255
+ new file
256
+ --- /dev/null DATE
257
+ +++ b/b_only_file DATE
258
+ @@ -0,0 +1 @@
259
+ +b_only_file
260
+ ' ]
261
+ end
262
+ end
263
+ end