knife-essentials 0.2.1 → 0.3

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.
@@ -7,6 +7,7 @@ to manipulate Chef using a common set of verbs that work on *everything*
7
7
  that is stored in the Chef server.
8
8
 
9
9
  knife diff cookbooks/*apache*
10
+ knife download roles data_bags cookbooks/emacs
10
11
  knife list data_bags/users
11
12
  knife show roles/*base*
12
13
 
@@ -53,6 +54,7 @@ This plugin builds a Ruby Gem (but has not yet been released to RubyGems). To i
53
54
  chef_fs installs a number of useful knife verbs, including:
54
55
 
55
56
  knife diff [pattern1 pattern2 ...]
57
+ knife download [pattern1 pattern2 ...]
56
58
  knife list [pattern1 pattern2 ...]
57
59
  knife show [pattern2 pattern2 ...]
58
60
 
@@ -81,6 +83,13 @@ To run the knife plugin functionality, install a version of Chef > 0.10.10:
81
83
  Diffs objects on the server against files in the local repository. Output is similar to
82
84
  +git diff+.
83
85
 
86
+ == knife download
87
+
88
+ knife download [pattern1 pattern2 ...]
89
+
90
+ Downloads objects from the server to your local repository. Pass --purge to delete local
91
+ files and directories which do not exist on the server.
92
+
84
93
  == knife list
85
94
 
86
95
  knife list [pattern1 pattern2 ...]
@@ -0,0 +1,32 @@
1
+ require 'chef_fs/knife'
2
+ require 'chef_fs/command_line'
3
+
4
+ class Chef
5
+ class Knife
6
+ class Download < ChefFS::Knife
7
+ banner "download PATTERNS"
8
+
9
+ option :recurse,
10
+ :long => '--[no-]recurse',
11
+ :boolean => true,
12
+ :default => true,
13
+ :description => "List directories recursively."
14
+
15
+ option :purge,
16
+ :long => '--[no-]purge',
17
+ :boolean => true,
18
+ :default => false,
19
+ :description => "Delete matching local files and directories that do not exist remotely."
20
+
21
+ def run
22
+ patterns = pattern_args_from(name_args.length > 0 ? name_args : [ "" ])
23
+
24
+ # Get the matches (recursively)
25
+ patterns.each do |pattern|
26
+ ChefFS::FileSystem.copy_to(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, config[:purge])
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -12,7 +12,7 @@ module ChefFS
12
12
  Digest::MD5.hexdigest(value)
13
13
  end
14
14
 
15
- def self.diff_files(old_file, new_file)
15
+ def self.diff_files_quick(old_file, new_file)
16
16
  #
17
17
  # Short-circuit expensive comparison (could be an extra network
18
18
  # request) if a pre-calculated checksum is there
@@ -42,7 +42,16 @@ module ChefFS
42
42
  end
43
43
 
44
44
  # If the checksums are the same, they are the same. Return.
45
- return false if old_checksum == new_checksum
45
+ return [ false, old_value, new_value ] if old_checksum == new_checksum
46
+ end
47
+
48
+ return [ nil, old_value, new_value ]
49
+ end
50
+
51
+ def self.diff_files(old_file, new_file)
52
+ different, old_value, new_value = diff_files_quick(old_file, new_file)
53
+ if different != nil
54
+ return different
46
55
  end
47
56
 
48
57
  #
@@ -87,8 +96,8 @@ module ChefFS
87
96
  ChefFS::FileSystem.list(a_root, pattern) do |a|
88
97
  found_paths << a.path
89
98
  b = ChefFS::FileSystem.resolve_path(b_root, a.path)
90
- diffable_leaves(a, b, recurse_depth) do |a_leaf, b_leaf|
91
- yield [ a_leaf, b_leaf ]
99
+ diffable_leaves(a, b, recurse_depth) do |a_leaf, b_leaf, leaf_recurse_depth|
100
+ yield [ a_leaf, b_leaf, leaf_recurse_depth ]
92
101
  end
93
102
  end
94
103
 
@@ -97,7 +106,7 @@ module ChefFS
97
106
  ChefFS::FileSystem.list(b_root, pattern) do |b|
98
107
  if !found_paths.include?(b.path)
99
108
  a = ChefFS::FileSystem.resolve_path(a_root, b.path)
100
- yield [ a, b ]
109
+ yield [ a, b, recurse_depth ]
101
110
  end
102
111
  end
103
112
  end
@@ -126,15 +135,15 @@ module ChefFS
126
135
  a_children_names = Set.new
127
136
  a.children.each do |a_child|
128
137
  a_children_names << a_child.name
129
- diffable_leaves(a_child, b.child(a_child.name), recurse_depth ? recurse_depth - 1 : nil) do |a_leaf, b_leaf|
130
- yield [ a_leaf, b_leaf ]
138
+ diffable_leaves(a_child, b.child(a_child.name), recurse_depth ? recurse_depth - 1 : nil) do |a_leaf, b_leaf, leaf_recurse_depth|
139
+ yield [ a_leaf, b_leaf, leaf_recurse_depth ]
131
140
  end
132
141
  end
133
142
 
134
143
  # Check b for children that aren't in a
135
144
  b.children.each do |b_child|
136
145
  if !a_children_names.include?(b_child.name)
137
- yield [ a.child(b_child.name), b_child ]
146
+ yield [ a.child(b_child.name), b_child, recurse_depth ]
138
147
  end
139
148
  end
140
149
  return
@@ -1,7 +1,12 @@
1
1
  require 'chef_fs/path_utils'
2
+ require 'chef_fs/diff'
2
3
 
3
4
  module ChefFS
4
5
  module FileSystem
6
+ def self.copy_to(pattern, src_root, dest_root)
7
+
8
+ end
9
+
5
10
  # Yields a list of all things under (and including) this entry that match the
6
11
  # given pattern.
7
12
  #
@@ -64,6 +69,109 @@ module ChefFS
64
69
  end
65
70
  result
66
71
  end
72
+
73
+ # Copy everything matching the given pattern from src to dest.
74
+ #
75
+ # After this method completes, everything in dest matching the
76
+ # given pattern will look identical to src.
77
+ #
78
+ # ==== Attributes
79
+ #
80
+ # * +pattern+ - ChefFS::FilePattern to match children under
81
+ # * +src_root+ - the root from which things will be copied
82
+ # * +dest_root+ - the root to which things will be copied
83
+ # * +recurse_depth+ - the maximum depth to copy things. +nil+
84
+ # means infinite depth. 0 means no recursion.
85
+ # * +purge+ - if +true+, items in +dest+ that are not in +src+
86
+ # will be deleted from +dest+. If +false+, these items will
87
+ # be left alone.
88
+ #
89
+ # ==== Examples
90
+ #
91
+ # ChefFS::FileSystem.copy_to(FilePattern.new('/cookbooks', chef_fs, local_fs, nil, true)
92
+ #
93
+ def self.copy_to(pattern, src_root, dest_root, recurse_depth, purge)
94
+ found_result = false
95
+ # Find things we might want to copy
96
+ ChefFS::Diff::diffable_leaves_from_pattern(pattern, src_root, dest_root, recurse_depth) do |src_leaf, dest_leaf, child_recurse_depth|
97
+ found_result = true
98
+ copy_leaves(src_leaf, dest_leaf, child_recurse_depth, purge)
99
+ end
100
+ if !found_result && pattern.exact_path
101
+ yield "#{pattern}: No such file or directory on remote or local"
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ # Copy two known leaves (could be files or dirs)
108
+ def self.copy_leaves(src_entry, dest_entry, recurse_depth, purge)
109
+ # A NOTE about this algorithm:
110
+ # There are cases where this algorithm does too many network requests.
111
+ # knife upload with a specific filename will first check if the file
112
+ # exists (a "dir" in the parent) before deciding whether to POST or
113
+ # PUT it. If we just tried PUT (or POST) and then tried the other if
114
+ # the conflict failed, we wouldn't need to check existence.
115
+ # On the other hand, we may already have DONE the request, in which
116
+ # case we shouldn't waste time trying PUT if we know the file doesn't
117
+ # exist.
118
+ # Will need to decide how that works with checksums, though.
119
+
120
+ if !src_entry.exists?
121
+ if purge
122
+ # If we would not have uploaded it, we will not purge it.
123
+ if src_entry.parent.can_have_child?(dest_entry.name, dest_entry.dir?)
124
+ Chef::Log.info("Deleting extra entry #{dest_entry.path_for_printing} (purge is on)")
125
+ dest_entry.delete
126
+ end
127
+ end
128
+
129
+ elsif !dest_entry.exists?
130
+ if dest_entry.parent.can_have_child?(src_entry.name, src_entry.dir?)
131
+ if src_entry.dir?
132
+ Chef::Log.info("Creating #{dest_entry.path_for_printing}/")
133
+ new_dest_dir = dest_entry.parent.create_child(src_entry.name, nil)
134
+ # Directory creation is recursive.
135
+ if recurse_depth != 0
136
+ src_entry.children.each do |src_child|
137
+ new_dest_child = new_dest_dir.child(src_child.name)
138
+ copy_leaves(src_child, new_dest_child, recurse_depth ? recurse_depth - 1 : recurse_depth, purge)
139
+ end
140
+ end
141
+ else
142
+ Chef::Log.info("Creating #{dest_entry.path_for_printing}")
143
+ dest_entry.parent.create_child(src_entry.name, src_entry.read)
144
+ end
145
+ end
146
+
147
+ else
148
+ # Both exist.
149
+ # If they are different types, log an error.
150
+ if src_entry.dir?
151
+ if dest_entry.dir?
152
+ # If they are both directories, we'll end up recursing later.
153
+ else
154
+ # If they are different types.
155
+ Chef::Log.error("File #{dest_entry.path_for_printing} is a directory while file #{dest_entry.path_for_printing} is a regular file\n")
156
+ return
157
+ end
158
+ else
159
+ if dest_entry.dir?
160
+ Chef::Log.error("File #{dest_entry.path_for_printing} is a directory while file #{dest_entry.path_for_printing} is a regular file\n")
161
+ return
162
+ else
163
+ # Both are files! Copy them unless we're sure they are the same.
164
+ different, src_value, dest_value = ChefFS::Diff.diff_files_quick(src_entry, dest_entry)
165
+ if different || different == nil
166
+ Chef::Log.info("Copying #{src_entry.path_for_printing} to #{dest_entry.path_for_printing}")
167
+ src_value = src_entry.read if src_value == :not_retrieved
168
+ dest_entry.write(src_value)
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+
67
175
  end
68
176
  end
69
177
 
@@ -2,6 +2,7 @@ require 'chef_fs/file_system/base_fs_dir'
2
2
  require 'chef_fs/file_system/rest_list_dir'
3
3
  require 'chef_fs/file_system/not_found_error'
4
4
  require 'chef_fs/path_utils'
5
+ require 'fileutils'
5
6
 
6
7
  module ChefFS
7
8
  module FileSystem
@@ -18,18 +19,46 @@ module ChefFS
18
19
  end
19
20
 
20
21
  def children
21
- @children ||= Dir.entries(file_path).select { |entry| entry != '.' && entry != '..' }.map { |entry| FileSystemEntry.new(entry, self) }
22
+ begin
23
+ @children ||= Dir.entries(file_path).select { |entry| entry != '.' && entry != '..' }.map { |entry| FileSystemEntry.new(entry, self) }
24
+ rescue Errno::ENOENT
25
+ raise ChefFS::FileSystem::NotFoundError.new($!), "#{file_path} not found"
26
+ end
27
+ end
28
+
29
+ def create_child(child_name, file_contents=nil)
30
+ result = FileSystemEntry.new(child_name, self)
31
+ if file_contents
32
+ result.write(file_contents)
33
+ else
34
+ Dir.mkdir(result.file_path)
35
+ end
36
+ result
22
37
  end
23
38
 
24
39
  def dir?
25
40
  File.directory?(file_path)
26
41
  end
27
42
 
43
+ def delete
44
+ if dir?
45
+ FileUtils.rm_rf(file_path)
46
+ else
47
+ File.delete(file_path)
48
+ end
49
+ end
50
+
28
51
  def read
29
52
  begin
30
53
  IO.read(file_path)
31
- rescue IONotFoundError # TODO real exception
32
- raise ChefFS::FileSystem::NotFoundError.new($!), "#{path_for_printing} not found"
54
+ rescue Errno::ENOENT
55
+ raise ChefFS::FileSystem::NotFoundError.new($!), "#{file_path} not found"
56
+ end
57
+ end
58
+
59
+ def write(content)
60
+ File.open(file_path, 'w') do |file|
61
+ file.write(content)
33
62
  end
34
63
  end
35
64
  end
@@ -1,4 +1,4 @@
1
1
  module ChefFS
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3"
3
3
  end
4
4
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-essentials
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: '0.3'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -23,6 +23,7 @@ files:
23
23
  - README.rdoc
24
24
  - Rakefile
25
25
  - lib/chef/knife/diff.rb
26
+ - lib/chef/knife/download.rb
26
27
  - lib/chef/knife/list.rb
27
28
  - lib/chef/knife/show.rb
28
29
  - lib/chef_fs/command_line.rb