knife-essentials 0.2.1 → 0.3

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