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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +1 -0
- data/knife_cookbook_sync.gemspec +21 -0
- data/lib/chef/knife/cookbook_sync.rb +151 -0
- data/lib/knife_cookbook_sync.rb +4 -0
- data/lib/knife_cookbook_sync/version.rb +3 -0
- metadata +71 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
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:
|