knife_cookbook_sync 0.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in knife_cookbook_sync.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Erik Hollensbe
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # knife cookbook sync
2
+
3
+ Sync your cookbooks faster than `knife cookbook upload` or alternatives.
4
+
5
+ `knife cookbook sync` is primarily a development tool, but can be used for
6
+ production work with careful coordination with an external cookbook resolver.
7
+
8
+ Here's the meat. `knife cookbook sync` vs `knife cookbook upload` with a
9
+ pre-uploaded corpus of 39 cookbooks, using the standard unix `time` utility to
10
+ benchmark on MRI 1.9.3-p327:
11
+
12
+ * `knife cookbook sync -a`: 1.31s user 0.15s system 72% cpu **2.020 total**
13
+ * `knife cookbook upload -a`: 1.34s user 0.15s system 15% cpu **9.684 total**
14
+
15
+ Instead of resolving and uploading everything (or even what you ask to upload),
16
+ it uses the cryptographic sums chef already generates to determine what needs
17
+ to be uploaded, and only uploads what's different.
18
+
19
+ This means it **does not check versions and dependencies**. It cheats, so you
20
+ should be sure you have your ducks in a row before uploading by using a
21
+ cookbook resolver. This only matters for resolution purposes -- cookbooks
22
+ uploaded with `knife cookbook sync` are no different otherwise (and in fact use
23
+ chef's own cookbook uploading tooling to do it).
24
+
25
+ Unsurprisingly, the more that has changed, the more the performance will
26
+ decrease slowly towards `knife cookbook upload` performance. The gains are
27
+ really only seen when you need to sync whole repositories where most or all of
28
+ the product that's on-disk has already been uploaded. It's particularly nice
29
+ for fast test cycles where you just don't want to care just yet what cookbooks
30
+ have changed.
31
+
32
+ `knife cookbook sync` has no dependencies other than chef, and should be
33
+ forward compatible with all versions of chef in the 10.x series, and probably
34
+ beyond.
35
+
36
+ ## Installation
37
+
38
+ Add this line to your application's Gemfile:
39
+
40
+ gem 'knife_cookbook_sync'
41
+
42
+ And then execute:
43
+
44
+ $ bundle
45
+
46
+ Or install it yourself as:
47
+
48
+ $ gem install knife_cookbook_sync
49
+
50
+ ## Usage
51
+
52
+ `knife cookbook sync` takes a list of cookbooks or `-a` to upload everything.
53
+ It uses your `cookbook_path` `knife.rb` settings to determine what's available
54
+ to upload which is overridable by `-o`.
55
+
56
+ If you pass `-d`, it'll perform a "dry run" and just show you what it would
57
+ upload.
58
+
59
+ For more information, use `knife cookbook sync --help`.
60
+
61
+ ## Contributing
62
+
63
+ 1. Fork it
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create new Pull Request
68
+
69
+ ## Authors
70
+
71
+ * Erik Hollensbe <erik+github@hollensbe.org>
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'knife_cookbook_sync/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "knife_cookbook_sync"
8
+ gem.version = KnifeCookbookSync::VERSION
9
+ gem.authors = ["Erik Hollensbe"]
10
+ gem.email = ["erik+github@hollensbe.org"]
11
+ gem.description = %q{Sync only what's changed -- faster than cookbook upload}
12
+ gem.summary = %q{Sync only what's changed -- faster than cookbook upload}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'chef', '~> 10.0'
21
+ end
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Thread.abort_on_exception = true
4
+
5
+ module Knife
6
+ class CookbookSync < Chef::Knife
7
+
8
+ deps do
9
+ require 'chef/knife/cookbook_metadata'
10
+ require 'chef/rest'
11
+ require 'chef/checksum_cache'
12
+ require 'chef/cookbook_loader'
13
+ require 'chef/cookbook_uploader'
14
+ require 'chef/cookbook_version'
15
+ end
16
+
17
+ banner "knife cookbook sync [COOKBOOKS...]"
18
+
19
+ option :dry_run,
20
+ :short => '-d',
21
+ :long => '--dry-run',
22
+ :boolean => true,
23
+ :description => "Do a dry run -- do not sync anything, just show what would be synced"
24
+
25
+ option :all,
26
+ :short => '-a',
27
+ :long => '--all',
28
+ :boolean => true,
29
+ :description => "Sync all cookbooks"
30
+
31
+ option :cookbook_path,
32
+ :short => '-o [COOKBOOK PATH]',
33
+ :long => '--cookbook-path [COOKBOOK PATH]',
34
+ :default => %w[cookbooks site-cookbooks],
35
+ :description => "The path that cookbooks should be loaded from (path:path)",
36
+ :proc => proc { |x| x.split(":") }
37
+
38
+
39
+ def distill_manifest(cookbook)
40
+ files = { }
41
+ cookbook.manifest.values.select { |x| x.kind_of?(Array) }.flatten.each { |f| files[f['path']] = f['checksum'] }
42
+ # don't check metadata.json since json output is indeterministic, metadata.rb should be all that's needed anw
43
+ files.delete('metadata.json')
44
+ return files
45
+ end
46
+
47
+ def sync_cookbooks(cookbooks, cl)
48
+ uploaded = false
49
+
50
+ print_mutex = Mutex.new
51
+
52
+ cookbooks.each do |cookbook|
53
+ Thread.new do
54
+ upload = false
55
+ print_mutex.synchronize do
56
+ ui.msg "Checking cookbook '#{cookbook}' for sync necessity"
57
+ end
58
+
59
+ remote_cookbook = Chef::CookbookVersion.load(cookbook.to_s)
60
+ local_cookbook = cl[cookbook.to_s] rescue nil
61
+
62
+ unless local_cookbook
63
+ print_mutex.synchronize do
64
+ ui.fatal "Cookbook '#{cookbook}' does not exist locally."
65
+ end
66
+ exit 1
67
+ end
68
+
69
+ if local_cookbook and !remote_cookbook
70
+ upload = true
71
+ else
72
+ remote_files = distill_manifest(remote_cookbook) rescue { }
73
+ local_files = distill_manifest(local_cookbook) rescue { }
74
+
75
+ if local_files.keys.length != remote_files.keys.length
76
+ upload = true
77
+ else
78
+ (local_files.keys + remote_files.keys).uniq.each do |filename|
79
+ if local_files[filename] != remote_files[filename]
80
+ upload = true
81
+ break
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ if upload
88
+ print_mutex.synchronize do
89
+ ui.msg "sync necessary; uploading '#{cookbook}'"
90
+ end
91
+
92
+ retries_left = 5
93
+
94
+ begin
95
+ #
96
+ # XXX
97
+ #
98
+ # For some godawful reason, if we use the local_cookbook referenced
99
+ # above, the MD5 sums are off.
100
+ #
101
+ # So we reload the cookbooks here because it seems to work, with an
102
+ # optional retry if the chef server is being pissy.
103
+ #
104
+ cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
105
+
106
+ if config[:dry_run]
107
+ print_mutex.synchronize do
108
+ ui.warn "dry run: would sync '#{cookbook}'"
109
+ end
110
+ else
111
+ Chef::CookbookUploader.new(cl[cookbook], Chef::Config[:cookbook_path]).upload_cookbooks
112
+ end
113
+
114
+ uploaded = true
115
+ rescue Exception => e
116
+ print_mutex.synchronize do
117
+ ui.error "Failed to upload; retrying up to #{retries_left} times"
118
+ end
119
+
120
+ retries_left -= 1
121
+ if retries_left > 0
122
+ retry
123
+ else
124
+ raise e
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ Thread.list.reject { |x| x == Thread.current }.each(&:join)
132
+
133
+ # exit with an exit status of 5 if we've uploaded anything.
134
+ exit uploaded ? 5 : 0
135
+ end
136
+
137
+ def run
138
+ Chef::Config[:cookbook_path] = config[:cookbook_path]
139
+
140
+ Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest) }
141
+
142
+ cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
143
+
144
+ if config[:all]
145
+ sync_cookbooks cl.cookbooks.map(&:name), cl
146
+ else
147
+ sync_cookbooks name_args, cl
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,4 @@
1
+ require "knife_cookbook_sync/version"
2
+
3
+ module KnifeCookbookSync
4
+ end
@@ -0,0 +1,3 @@
1
+ module KnifeCookbookSync
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife_cookbook_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Erik Hollensbe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: chef
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '10.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '10.0'
30
+ description: Sync only what's changed -- faster than cookbook upload
31
+ email:
32
+ - erik+github@hollensbe.org
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - knife_cookbook_sync.gemspec
43
+ - lib/chef/knife/cookbook_sync.rb
44
+ - lib/knife_cookbook_sync.rb
45
+ - lib/knife_cookbook_sync/version.rb
46
+ homepage: ''
47
+ licenses: []
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.24
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Sync only what's changed -- faster than cookbook upload
70
+ test_files: []
71
+ has_rdoc: