knife-essentials 0.1

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