knife_cookbook_dependencies 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +22 -0
- data/.rbenv-version +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +90 -0
- data/LICENSE +22 -0
- data/README.rdoc +5 -0
- data/Rakefile +29 -0
- data/knife_cookbook_dependencies.gemspec +30 -0
- data/lib/chef/knife/cookbook_dependencies_install.rb +17 -0
- data/lib/knife_cookbook_dependencies/cookbook.rb +184 -0
- data/lib/knife_cookbook_dependencies/cookbookfile.rb +33 -0
- data/lib/knife_cookbook_dependencies/dependency_reader.rb +46 -0
- data/lib/knife_cookbook_dependencies/dsl.rb +7 -0
- data/lib/knife_cookbook_dependencies/git.rb +86 -0
- data/lib/knife_cookbook_dependencies/knife_utils.rb +13 -0
- data/lib/knife_cookbook_dependencies/lockfile.rb +25 -0
- data/lib/knife_cookbook_dependencies/metacookbook.rb +18 -0
- data/lib/knife_cookbook_dependencies/shelf.rb +70 -0
- data/lib/knife_cookbook_dependencies/version.rb +3 -0
- data/lib/knife_cookbook_dependencies.rb +42 -0
- data/spec/fixtures/cookbooks/example_cookbook/README.md +12 -0
- data/spec/fixtures/cookbooks/example_cookbook/metadata.rb +6 -0
- data/spec/fixtures/cookbooks/example_cookbook/recipes/default.rb +8 -0
- data/spec/lib/knife_cookbook_dependencies/cookbook_spec.rb +109 -0
- data/spec/lib/knife_cookbook_dependencies/cookbookfile_spec.rb +26 -0
- data/spec/lib/knife_cookbook_dependencies/dependency_reader_spec.rb +42 -0
- data/spec/lib/knife_cookbook_dependencies/dsl_spec.rb +29 -0
- data/spec/lib/knife_cookbook_dependencies/git_spec.rb +58 -0
- data/spec/lib/knife_cookbook_dependencies/shelf_spec.rb +37 -0
- data/spec/spec_helper.rb +21 -0
- data/todo.txt +13 -0
- metadata +163 -0
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
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
|
18
|
+
cookbooks
|
19
|
+
*~
|
20
|
+
*.tar*
|
21
|
+
\#*
|
22
|
+
Cookbookfile*
|
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p125
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
knife_cookbook_dependencies (0.0.1)
|
5
|
+
chef (~> 0.10.0)
|
6
|
+
dep_selector
|
7
|
+
minitar
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
bunny (0.7.9)
|
13
|
+
chef (0.10.8)
|
14
|
+
bunny (>= 0.6.0)
|
15
|
+
erubis
|
16
|
+
highline
|
17
|
+
json (>= 1.4.4, <= 1.6.1)
|
18
|
+
mixlib-authentication (>= 1.1.0)
|
19
|
+
mixlib-cli (>= 1.1.0)
|
20
|
+
mixlib-config (>= 1.1.2)
|
21
|
+
mixlib-log (>= 1.3.0)
|
22
|
+
moneta
|
23
|
+
net-ssh (~> 2.1.3)
|
24
|
+
net-ssh-multi (~> 1.1.0)
|
25
|
+
ohai (>= 0.6.0)
|
26
|
+
rest-client (>= 1.0.4, < 1.7.0)
|
27
|
+
treetop (~> 1.4.9)
|
28
|
+
uuidtools
|
29
|
+
dep_selector (0.0.8)
|
30
|
+
diff-lcs (1.1.3)
|
31
|
+
erubis (2.7.0)
|
32
|
+
highline (1.6.11)
|
33
|
+
ipaddress (0.8.0)
|
34
|
+
json (1.6.1)
|
35
|
+
mime-types (1.18)
|
36
|
+
minitar (0.5.3)
|
37
|
+
mixlib-authentication (1.1.4)
|
38
|
+
mixlib-log
|
39
|
+
mixlib-cli (1.2.2)
|
40
|
+
mixlib-config (1.1.2)
|
41
|
+
mixlib-log (1.3.0)
|
42
|
+
moneta (0.6.0)
|
43
|
+
multi_json (1.3.4)
|
44
|
+
net-ssh (2.1.4)
|
45
|
+
net-ssh-gateway (1.1.0)
|
46
|
+
net-ssh (>= 1.99.1)
|
47
|
+
net-ssh-multi (1.1)
|
48
|
+
net-ssh (>= 2.1.4)
|
49
|
+
net-ssh-gateway (>= 0.99.0)
|
50
|
+
ohai (0.6.12)
|
51
|
+
ipaddress
|
52
|
+
mixlib-cli
|
53
|
+
mixlib-config
|
54
|
+
mixlib-log
|
55
|
+
systemu
|
56
|
+
yajl-ruby
|
57
|
+
polyglot (0.3.3)
|
58
|
+
rake (0.9.2.2)
|
59
|
+
rdoc (3.12)
|
60
|
+
json (~> 1.4)
|
61
|
+
rest-client (1.6.7)
|
62
|
+
mime-types (>= 1.16)
|
63
|
+
rspec (2.9.0)
|
64
|
+
rspec-core (~> 2.9.0)
|
65
|
+
rspec-expectations (~> 2.9.0)
|
66
|
+
rspec-mocks (~> 2.9.0)
|
67
|
+
rspec-core (2.9.0)
|
68
|
+
rspec-expectations (2.9.1)
|
69
|
+
diff-lcs (~> 1.1.3)
|
70
|
+
rspec-mocks (2.9.0)
|
71
|
+
simplecov (0.6.2)
|
72
|
+
multi_json (~> 1.3)
|
73
|
+
simplecov-html (~> 0.5.3)
|
74
|
+
simplecov-html (0.5.3)
|
75
|
+
systemu (2.5.0)
|
76
|
+
treetop (1.4.10)
|
77
|
+
polyglot
|
78
|
+
polyglot (>= 0.3.1)
|
79
|
+
uuidtools (2.1.2)
|
80
|
+
yajl-ruby (1.1.0)
|
81
|
+
|
82
|
+
PLATFORMS
|
83
|
+
ruby
|
84
|
+
|
85
|
+
DEPENDENCIES
|
86
|
+
knife_cookbook_dependencies!
|
87
|
+
rake (~> 0.9.0)
|
88
|
+
rdoc (~> 3.0)
|
89
|
+
rspec
|
90
|
+
simplecov
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Josiah Kiehl
|
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.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rdoc/task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
desc "check documentation coverage"
|
6
|
+
task "rdoc:check" do
|
7
|
+
sh "rdoc -C " + Dir["lib/**/*.rb"].join(" ")
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "clean up doco/coverage"
|
11
|
+
task :clean do
|
12
|
+
sh "rm -fr rdoc coverage"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "generate documentation"
|
16
|
+
RDoc::Task.new :rdoc do |r|
|
17
|
+
r.main = "README.rdoc"
|
18
|
+
r.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
19
|
+
r.rdoc_dir = "rdoc"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Run specs"
|
23
|
+
RSpec::Core::RakeTask.new do |t|
|
24
|
+
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
25
|
+
# Put spec opts in a file named .rspec in root
|
26
|
+
end
|
27
|
+
|
28
|
+
task :check => [:default, "rdoc:check"]
|
29
|
+
task :default => [:clean, :spec]
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/knife_cookbook_dependencies/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
|
6
|
+
# TODO FIXME need to modify all of these
|
7
|
+
|
8
|
+
gem.authors = ["Josiah Kiehl"]
|
9
|
+
gem.email = ["josiah@skirmisher.net"]
|
10
|
+
gem.description = %q{Resolves cookbook dependencies}
|
11
|
+
gem.summary = gem.description
|
12
|
+
gem.homepage = "http://github.com/RiotGames/knife_cookbook_dependencies"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($\)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.name = "knife_cookbook_dependencies"
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.version = KnifeCookbookDependencies::VERSION
|
20
|
+
|
21
|
+
# FIXME will want to adjust this later
|
22
|
+
gem.add_runtime_dependency 'dep_selector', '>= 0'
|
23
|
+
gem.add_runtime_dependency 'chef', '~> 0.10.0'
|
24
|
+
gem.add_runtime_dependency 'minitar', '>= 0'
|
25
|
+
|
26
|
+
gem.add_development_dependency 'rake', '~> 0.9.0'
|
27
|
+
gem.add_development_dependency 'rspec', '>= 0'
|
28
|
+
gem.add_development_dependency 'rdoc', '~> 3.0'
|
29
|
+
gem.add_development_dependency 'simplecov', '>= 0'
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
require 'knife_cookbook_dependencies'
|
3
|
+
|
4
|
+
module KnifeCookbookDependencies
|
5
|
+
class CookbookDependenciesInstall < Chef::Knife
|
6
|
+
banner "knife cookbook dependencies install"
|
7
|
+
|
8
|
+
def run
|
9
|
+
ui.info 'Reading Cookbookfile'
|
10
|
+
::KnifeCookbookDependencies.ui = ui
|
11
|
+
::KnifeCookbookDependencies::Cookbookfile.process_install
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CookbookDepsInstall < CookbookDependenciesInstall; end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'knife_cookbook_dependencies/knife_utils'
|
2
|
+
require 'knife_cookbook_dependencies/git'
|
3
|
+
require 'chef/knife/cookbook_site_download'
|
4
|
+
require 'chef/knife/cookbook_site_show'
|
5
|
+
|
6
|
+
module KnifeCookbookDependencies
|
7
|
+
class Cookbook
|
8
|
+
attr_reader :name, :version_constraints
|
9
|
+
attr_accessor :locked_version
|
10
|
+
|
11
|
+
DOWNLOAD_LOCATION = ENV["TMPDIR"] || '/tmp'
|
12
|
+
|
13
|
+
def initialize *args
|
14
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
15
|
+
|
16
|
+
if from_git? and from_path?
|
17
|
+
raise "Invalid: path and git options provided to #{args[0]}. They are mutually exclusive."
|
18
|
+
end
|
19
|
+
|
20
|
+
@options[:path] = File.expand_path(@options[:path]) if from_path?
|
21
|
+
@name, constraint_string = args
|
22
|
+
|
23
|
+
add_version_constraint(if from_path?
|
24
|
+
"= #{version_from_metadata_file.to_s}"
|
25
|
+
else
|
26
|
+
constraint_string
|
27
|
+
end)
|
28
|
+
@locked_version = DepSelector::Version.new(@options[:locked_version]) if @options[:locked_version]
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_version_constraint constraint_string
|
32
|
+
@version_constraints ||= []
|
33
|
+
@version_constraints << DepSelector::VersionConstraint.new(constraint_string) unless @version_constraints.collect(&:to_s).include? constraint_string
|
34
|
+
end
|
35
|
+
|
36
|
+
def download(show_output = false)
|
37
|
+
return if @downloaded
|
38
|
+
return if !from_git? and downloaded_archive_exists?
|
39
|
+
|
40
|
+
if from_git?
|
41
|
+
@git ||= KnifeCookbookDependencies::Git.new(@options[:git])
|
42
|
+
@git.clone
|
43
|
+
@git.checkout(@options[:ref]) if @options[:ref]
|
44
|
+
@options[:path] ||= @git.directory
|
45
|
+
elsif from_path?
|
46
|
+
return
|
47
|
+
else
|
48
|
+
csd = Chef::Knife::CookbookSiteDownload.new([name, latest_constrained_version.to_s, "--file", download_filename])
|
49
|
+
output = KnifeCookbookDependencies::KnifeUtils.capture_knife_output(csd)
|
50
|
+
|
51
|
+
if show_output
|
52
|
+
puts output
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@downloaded = true
|
57
|
+
end
|
58
|
+
|
59
|
+
def copy_to_cookbooks_directory
|
60
|
+
FileUtils.mkdir_p KnifeCookbookDependencies::COOKBOOKS_DIRECTORY
|
61
|
+
|
62
|
+
target = File.join(KnifeCookbookDependencies::COOKBOOKS_DIRECTORY, @name)
|
63
|
+
FileUtils.rm_rf target
|
64
|
+
FileUtils.cp_r full_path, target
|
65
|
+
FileUtils.rm_rf File.join(target, '.git') if from_git?
|
66
|
+
end
|
67
|
+
|
68
|
+
# TODO: Clean up download repetition functionality here, in #download and the associated test.
|
69
|
+
def unpack(location = unpacked_cookbook_path, do_clean = false, do_download = true)
|
70
|
+
return true if from_path?
|
71
|
+
self.clean(File.join(location, @name)) if do_clean
|
72
|
+
download if do_download
|
73
|
+
fname = download_filename
|
74
|
+
if File.directory? location
|
75
|
+
true # noop
|
76
|
+
elsif downloaded_archive_exists?
|
77
|
+
Archive::Tar::Minitar.unpack(Zlib::GzipReader.new(File.open(fname)), location)
|
78
|
+
true
|
79
|
+
else
|
80
|
+
# TODO: Raise friendly error message class
|
81
|
+
raise "Archive hasn't been downloaded yet"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def dependencies
|
86
|
+
download
|
87
|
+
unpack
|
88
|
+
@dependencies ||= DependencyReader.new(self).read
|
89
|
+
end
|
90
|
+
|
91
|
+
def latest_constrained_version
|
92
|
+
return @locked_version if @locked_version
|
93
|
+
return version_from_metadata_file if from_path? or from_git?
|
94
|
+
|
95
|
+
versions.reverse.each do |v|
|
96
|
+
return v if version_constraints_include? v
|
97
|
+
end
|
98
|
+
KnifeCookbookDependencies.ui.fatal "No version available to fit the following constraints for #{@name}: #{version_constraints.inspect}\nAvailable versions: #{versions.inspect}"
|
99
|
+
exit 1
|
100
|
+
end
|
101
|
+
|
102
|
+
def version_constraints_include? version
|
103
|
+
@version_constraints.inject(true) { |check, constraint| check and constraint.include? version }
|
104
|
+
end
|
105
|
+
|
106
|
+
def versions
|
107
|
+
return [latest_constrained_version] if @locked_version
|
108
|
+
return [version_from_metadata_file] if from_path? or from_git?
|
109
|
+
cookbook_data['versions'].collect { |v| DepSelector::Version.new(v.split(/\//).last.gsub(/_/, '.')) }.sort
|
110
|
+
end
|
111
|
+
|
112
|
+
def version_from_metadata_file
|
113
|
+
# TODO: make a generic metadata file reader to replace
|
114
|
+
# dependencyreader and incorporate pulling the version as
|
115
|
+
# well... knife probably has something like this I can use/steal
|
116
|
+
DepSelector::Version.new(metadata_file.match(/version\s+[\"\']([0-9\.]*)[\"\']/)[1])
|
117
|
+
end
|
118
|
+
|
119
|
+
def cookbook_data
|
120
|
+
css = Chef::Knife::CookbookSiteShow.new([@name])
|
121
|
+
@cookbook_data ||= JSON.parse(KnifeCookbookDependencies::KnifeUtils.capture_knife_output(css))
|
122
|
+
end
|
123
|
+
|
124
|
+
def download_filename
|
125
|
+
return nil if from_path?
|
126
|
+
File.join(DOWNLOAD_LOCATION, "#{@name}-#{latest_constrained_version}.tar.gz")
|
127
|
+
end
|
128
|
+
|
129
|
+
def unpacked_cookbook_path
|
130
|
+
@options[:path] || File.join(File.dirname(download_filename), File.basename(download_filename, '.tar.gz'))
|
131
|
+
end
|
132
|
+
|
133
|
+
def full_path
|
134
|
+
if @git
|
135
|
+
unpacked_cookbook_path
|
136
|
+
else
|
137
|
+
File.join(unpacked_cookbook_path, @name)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def metadata_filename
|
142
|
+
File.join(full_path, "metadata.rb")
|
143
|
+
end
|
144
|
+
|
145
|
+
def metadata_file
|
146
|
+
download
|
147
|
+
unpack
|
148
|
+
File.open(metadata_filename).read
|
149
|
+
end
|
150
|
+
|
151
|
+
def from_path?
|
152
|
+
!!@options[:path]
|
153
|
+
end
|
154
|
+
|
155
|
+
def from_git?
|
156
|
+
!!@options[:git]
|
157
|
+
end
|
158
|
+
|
159
|
+
def git_repo
|
160
|
+
@options[:git]
|
161
|
+
end
|
162
|
+
|
163
|
+
def git_ref
|
164
|
+
(from_git? && @git) ? @git.ref : nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def downloaded_archive_exists?
|
168
|
+
download_filename && File.exists?(download_filename)
|
169
|
+
end
|
170
|
+
|
171
|
+
def clean(location = unpacked_cookbook_path)
|
172
|
+
if @git
|
173
|
+
@git.clean
|
174
|
+
else
|
175
|
+
FileUtils.rm_rf location
|
176
|
+
FileUtils.rm_f download_filename
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def == other
|
181
|
+
other.name == @name and other.version_constraints == @version_constraints
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'knife_cookbook_dependencies/dsl'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
class Cookbookfile
|
5
|
+
class << self
|
6
|
+
include DSL
|
7
|
+
def read content
|
8
|
+
# This will populate KnifeCookbookDependencies.shelf. TODO: consider making this
|
9
|
+
# build and return the shelf rather than building the shelf as
|
10
|
+
# a side effect.
|
11
|
+
instance_eval(content)
|
12
|
+
end
|
13
|
+
|
14
|
+
def process_install
|
15
|
+
# TODO: friendly error message when the file doesn't exist
|
16
|
+
|
17
|
+
filename = KnifeCookbookDependencies::DEFAULT_FILENAME + ".lock"
|
18
|
+
lockfile = false
|
19
|
+
|
20
|
+
if File.exist?(filename)
|
21
|
+
lockfile = true
|
22
|
+
else
|
23
|
+
filename = KnifeCookbookDependencies::DEFAULT_FILENAME unless File.exist?(filename)
|
24
|
+
end
|
25
|
+
|
26
|
+
read File.open(filename).read
|
27
|
+
KnifeCookbookDependencies.shelf.resolve_dependencies
|
28
|
+
KnifeCookbookDependencies.shelf.populate_cookbooks_directory
|
29
|
+
KnifeCookbookDependencies.shelf.write_lockfile unless lockfile
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module KnifeCookbookDependencies
|
2
|
+
class DependencyReader
|
3
|
+
attr_reader :dependency_list, :cookbook
|
4
|
+
|
5
|
+
def initialize(cookbook)
|
6
|
+
@cookbook = cookbook
|
7
|
+
@dependency_list = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def read
|
11
|
+
Dir.chdir(@cookbook.full_path) do
|
12
|
+
# XXX this filename is required because it sets __FILE__, which is
|
13
|
+
# used for README.md parsing among other things in metadata.rb files
|
14
|
+
instance_eval(@cookbook.metadata_file, @cookbook.metadata_filename)
|
15
|
+
end
|
16
|
+
@dependency_list
|
17
|
+
end
|
18
|
+
|
19
|
+
def depends(*args)
|
20
|
+
name, constraint = args
|
21
|
+
|
22
|
+
dependency_cookbook = KnifeCookbookDependencies.shelf.get_cookbook(name) || get_dependency(name)
|
23
|
+
if dependency_cookbook
|
24
|
+
dependency_cookbook.add_version_constraint constraint
|
25
|
+
else
|
26
|
+
@dependency_list << Cookbook.new(*args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_dependency(name)
|
31
|
+
@dependency_list.select { |c| c.name == name }.first
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(method, *args)
|
35
|
+
# Don't blow up when other metadata DSL methods are called, we
|
36
|
+
# only care about #depends.
|
37
|
+
end
|
38
|
+
|
39
|
+
def name(the_name)
|
40
|
+
# Module#name is defined, so method_missing won't catch it
|
41
|
+
# when running instance_eval on a metadata.rb that overrides
|
42
|
+
# the name
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
class Git
|
5
|
+
class << self
|
6
|
+
def git
|
7
|
+
@git ||= find_git
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# This is to defeat aliases/shell functions called 'git' and a number of
|
12
|
+
# other problems.
|
13
|
+
#
|
14
|
+
def find_git
|
15
|
+
git_path = nil
|
16
|
+
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
17
|
+
potential_path = File.join(path, 'git')
|
18
|
+
if File.executable?(potential_path)
|
19
|
+
git_path = potential_path
|
20
|
+
break
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
unless git_path
|
25
|
+
raise "Could not find git. Please ensure it is in your path."
|
26
|
+
end
|
27
|
+
|
28
|
+
return git_path
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :directory
|
33
|
+
attr_reader :repository
|
34
|
+
|
35
|
+
def initialize(repo)
|
36
|
+
@repository = repo
|
37
|
+
end
|
38
|
+
|
39
|
+
def clone
|
40
|
+
# XXX not sure how resilient this is, maybe a fetch/merge strategy would be better.
|
41
|
+
if @directory
|
42
|
+
Dir.chdir @directory do
|
43
|
+
system(self.class.git, "pull")
|
44
|
+
end
|
45
|
+
else
|
46
|
+
@directory = Dir.mktmpdir
|
47
|
+
system(self.class.git, "clone", @repository, @directory)
|
48
|
+
end
|
49
|
+
|
50
|
+
error_check
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def checkout(ref)
|
55
|
+
clone
|
56
|
+
|
57
|
+
Dir.chdir @directory do
|
58
|
+
system(self.class.git, "checkout", "-q", ref)
|
59
|
+
end
|
60
|
+
|
61
|
+
error_check
|
62
|
+
end
|
63
|
+
|
64
|
+
def ref
|
65
|
+
return nil unless @directory
|
66
|
+
|
67
|
+
this_ref = nil
|
68
|
+
|
69
|
+
Dir.chdir @directory do
|
70
|
+
this_ref = `#{self.class.git} rev-parse HEAD`.strip
|
71
|
+
end
|
72
|
+
|
73
|
+
return this_ref
|
74
|
+
end
|
75
|
+
|
76
|
+
def clean
|
77
|
+
FileUtils.rm_rf @directory if @directory
|
78
|
+
end
|
79
|
+
|
80
|
+
def error_check
|
81
|
+
if $?.exitstatus != 0
|
82
|
+
raise "Did not succeed executing git; check the output above."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'chef/config'
|
3
|
+
|
4
|
+
module KnifeCookbookDependencies
|
5
|
+
module KnifeUtils
|
6
|
+
def self.capture_knife_output(knife_obj)
|
7
|
+
knife_obj.ui = Chef::Knife::UI.new(StringIO.new, StringIO.new, StringIO.new, { :format => :json })
|
8
|
+
knife_obj.run
|
9
|
+
knife_obj.ui.stdout.rewind
|
10
|
+
knife_obj.ui.stdout.read
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'knife_cookbook_dependencies/cookbookfile'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
class Lockfile
|
5
|
+
def initialize(cookbooks)
|
6
|
+
@cookbooks = cookbooks
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(filename = KnifeCookbookDependencies::DEFAULT_FILENAME)
|
10
|
+
content = @cookbooks.map do |cookbook|
|
11
|
+
get_cookbook_definition(cookbook)
|
12
|
+
end.join("\n")
|
13
|
+
File.write(filename + ".lock", content)
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_cookbook_definition(cookbook)
|
17
|
+
definition = "cookbook '#{cookbook.name}', :locked_version => '#{cookbook.locked_version}'"
|
18
|
+
if cookbook.git_repo
|
19
|
+
definition += ", :git => '#{cookbook.git_repo}', :ref => '#{cookbook.git_ref || 'HEAD'}'"
|
20
|
+
end
|
21
|
+
|
22
|
+
return definition
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module KnifeCookbookDependencies
|
2
|
+
class MetaCookbook
|
3
|
+
attr_reader :name, :dependencies
|
4
|
+
|
5
|
+
def initialize name, dependencies
|
6
|
+
@name = name
|
7
|
+
@dependencies = dependencies
|
8
|
+
end
|
9
|
+
|
10
|
+
def versions
|
11
|
+
@v ||= [DepSelector::Version.new('1.0.0')]
|
12
|
+
end
|
13
|
+
|
14
|
+
def latest_constrained_version
|
15
|
+
versions.first
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'knife_cookbook_dependencies/lockfile'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
class Shelf
|
5
|
+
META_COOKBOOK_NAME = 'cookbook_dependencies_shelf'
|
6
|
+
|
7
|
+
attr_accessor :cookbooks
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@cookbooks = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def shelve_cookbook(*args)
|
14
|
+
@cookbooks << (args.first.is_a?(Cookbook) ? args.first : Cookbook.new(*args))
|
15
|
+
end
|
16
|
+
|
17
|
+
def resolve_dependencies
|
18
|
+
graph = DepSelector::DependencyGraph.new
|
19
|
+
|
20
|
+
# all cookbooks in the Cookbookfile are dependencies of the shelf
|
21
|
+
shelf = MetaCookbook.new(META_COOKBOOK_NAME, @cookbooks)
|
22
|
+
|
23
|
+
self.class.populate_graph graph, shelf
|
24
|
+
|
25
|
+
selector = DepSelector::Selector.new(graph)
|
26
|
+
solution = selector.find_solution([DepSelector::SolutionConstraint.new(graph.package(META_COOKBOOK_NAME))])
|
27
|
+
solution.delete META_COOKBOOK_NAME
|
28
|
+
solution
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_lockfile
|
32
|
+
KnifeCookbookDependencies::Lockfile.new(@cookbooks).write
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_cookbook(name)
|
36
|
+
@cookbooks.select { |c| c.name == name }.first
|
37
|
+
end
|
38
|
+
|
39
|
+
def populate_cookbooks_directory
|
40
|
+
cookbooks_from_path = @cookbooks.select(&:from_path?) | @cookbooks.select(&:from_git?)
|
41
|
+
|
42
|
+
resolve_dependencies.each_pair do |cookbook_name, version|
|
43
|
+
cookbook = cookbooks_from_path.select { |c| c.name == cookbook_name }.first || Cookbook.new(cookbook_name, version.to_s)
|
44
|
+
@cookbooks << cookbook
|
45
|
+
cookbook.download
|
46
|
+
cookbook.unpack
|
47
|
+
cookbook.copy_to_cookbooks_directory
|
48
|
+
cookbook.locked_version = version
|
49
|
+
end
|
50
|
+
@cookbooks = @cookbooks.uniq.reject { |x| x.locked_version.nil? }
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def populate_graph(graph, cookbook)
|
55
|
+
package = graph.package cookbook.name
|
56
|
+
cookbook.versions.each { |v| package.add_version(v) }
|
57
|
+
cookbook.dependencies.each do |dependency|
|
58
|
+
graph = populate_graph(graph, dependency)
|
59
|
+
dep = graph.package(dependency.name)
|
60
|
+
version = package.versions.select { |v| v.version == cookbook.latest_constrained_version }.first
|
61
|
+
dependency.version_constraints.each do |constraint|
|
62
|
+
version.dependencies << DepSelector::Dependency.new(dep, constraint)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
graph
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'dep_selector'
|
2
|
+
require 'zlib'
|
3
|
+
require 'archive/tar/minitar'
|
4
|
+
require 'chef/config'
|
5
|
+
require 'chef/knife/cookbook_site_download'
|
6
|
+
|
7
|
+
require 'knife_cookbook_dependencies/version'
|
8
|
+
require 'knife_cookbook_dependencies/shelf'
|
9
|
+
require 'knife_cookbook_dependencies/cookbook'
|
10
|
+
require 'knife_cookbook_dependencies/metacookbook'
|
11
|
+
require 'knife_cookbook_dependencies/dependency_reader'
|
12
|
+
require 'knife_cookbook_dependencies/dsl'
|
13
|
+
require 'knife_cookbook_dependencies/cookbookfile'
|
14
|
+
require 'knife_cookbook_dependencies/git'
|
15
|
+
|
16
|
+
module KnifeCookbookDependencies
|
17
|
+
DEFAULT_FILENAME = 'Cookbookfile'
|
18
|
+
COOKBOOKS_DIRECTORY = 'cookbooks'
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def shelf
|
22
|
+
@shelf ||= KnifeCookbookDependencies::Shelf.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear_shelf!
|
26
|
+
@shelf = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def ui=(ui)
|
30
|
+
@ui = ui
|
31
|
+
end
|
32
|
+
|
33
|
+
def ui
|
34
|
+
@ui ||= Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
|
35
|
+
end
|
36
|
+
|
37
|
+
def clean
|
38
|
+
clear_shelf!
|
39
|
+
FileUtils.rm_rf COOKBOOKS_DIRECTORY
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
describe Cookbook do
|
5
|
+
subject { Cookbook.new('ntp') }
|
6
|
+
|
7
|
+
after do
|
8
|
+
subject.clean
|
9
|
+
end
|
10
|
+
|
11
|
+
describe do
|
12
|
+
|
13
|
+
before do
|
14
|
+
Cookbook.any_instance.stub(:versions).and_return ['0.1.1', '0.9.0', '1.0.0', '1.1.8'].collect {|v| Gem::Version.new(v) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# FIXME: This test is flakey
|
18
|
+
it "should raise an error if the cookbook is unpacked without being downloaded first" do
|
19
|
+
-> { subject.unpack(subject.unpacked_cookbook_path, true, false) }.should raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#unpacked_cookbook_path' do
|
23
|
+
it "should give the path to the directory where the archive should get unpacked" do
|
24
|
+
subject.unpacked_cookbook_path.should == File.join(ENV['TMPDIR'], 'ntp-1.1.8')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should treat cookbooks pulled from a path like a cookbook that has already been unpacked with the path as the unpacked location' do
|
29
|
+
example_cookbook_from_path.unpacked_cookbook_path.should == File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec", "fixtures", "cookbooks"))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not attempt to download a cookbook being pulled from a path" do
|
33
|
+
Chef::Knife::CookbookSiteDownload.any_instance.should_not_receive(:run)
|
34
|
+
example_cookbook_from_path.download
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#copy_to" do
|
38
|
+
it "should copy from the unpacked cookbook directory to the target" do
|
39
|
+
example_cookbook_from_path.copy_to_cookbooks_directory
|
40
|
+
File.exists?(File.join(KnifeCookbookDependencies::COOKBOOKS_DIRECTORY, example_cookbook_from_path.name)).should be_true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#versions" do
|
47
|
+
it "should return the version in the metadata file as the available versions for a path sourced cookbook" do
|
48
|
+
example_cookbook_from_path.versions.should == [DepSelector::Version.new('0.5.0')]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#version_from_metadata_file' do
|
53
|
+
it "should be able to handle single quoted strings" do
|
54
|
+
Cookbook.any_instance.stub(:metadata_file).and_return <<M
|
55
|
+
version '1.2.3'
|
56
|
+
M
|
57
|
+
subject.version_from_metadata_file.should == DepSelector::Version.new('1.2.3')
|
58
|
+
end
|
59
|
+
it "should be able to handle double quoted strings" do
|
60
|
+
Cookbook.any_instance.stub(:metadata_file).and_return <<M
|
61
|
+
version "1.2.3"
|
62
|
+
M
|
63
|
+
subject.version_from_metadata_file.should == DepSelector::Version.new('1.2.3')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#version_constraints_include?' do
|
68
|
+
it "should cycle through all the version constraints to confirm that all of them are satisfied" do
|
69
|
+
subject.add_version_constraint ">= 1.0.0"
|
70
|
+
subject.add_version_constraint "< 2.0.0"
|
71
|
+
subject.version_constraints_include?(DepSelector::Version.new('1.0.0')).should be_true
|
72
|
+
subject.version_constraints_include?(DepSelector::Version.new('1.5.0')).should be_true
|
73
|
+
subject.version_constraints_include?(DepSelector::Version.new('2.0.0')).should be_false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#add_version_constraint' do
|
78
|
+
it "should not duplicate version constraints" do
|
79
|
+
subject.add_version_constraint ">= 1.0.0"
|
80
|
+
subject.add_version_constraint ">= 1.0.0"
|
81
|
+
subject.add_version_constraint ">= 1.0.0"
|
82
|
+
subject.add_version_constraint ">= 1.0.0"
|
83
|
+
subject.version_constraints.size.should == 2 # 1 for the
|
84
|
+
# default when
|
85
|
+
# the cookbook
|
86
|
+
# was created in
|
87
|
+
# the subject
|
88
|
+
# instantiation
|
89
|
+
# line
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#dependencies' do
|
94
|
+
it "should not contain the cookbook itself" do
|
95
|
+
# TODO: Mock
|
96
|
+
Cookbook.new('riot_base', git: 'git@github.riotgames.com:cookbooks/riot_base.git').dependencies.collect(&:name).include?('riot_base').should_not be_true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# TODO figure out how to test this. Stubs on classes don't clear after a test.
|
101
|
+
# describe '#unpack' do
|
102
|
+
# it "should not unpack if it is already unpacked" do
|
103
|
+
# Archive::Tar::Minitar.should_receive(:unpack).once
|
104
|
+
# subject.unpack
|
105
|
+
# subject.unpack
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
describe Cookbookfile do
|
5
|
+
describe '::read' do
|
6
|
+
after do
|
7
|
+
KnifeCookbookDependencies.shelf.cookbooks.each(&:clean)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should read the cookbookfile and build a dependency list" do
|
11
|
+
described_class.read <<COOKBOOKFILE
|
12
|
+
cookbook 'ntp', '<= 1.0.0'
|
13
|
+
cookbook 'mysql'
|
14
|
+
cookbook 'nginx', '< 0.101.2'
|
15
|
+
cookbook 'ssh_known_hosts2', :git => 'https://github.com/erikh/chef-ssh_known_hosts2.git'
|
16
|
+
COOKBOOKFILE
|
17
|
+
|
18
|
+
['ntp', 'mysql', 'nginx', 'ssh_known_hosts2'].each do |dep|
|
19
|
+
KnifeCookbookDependencies.shelf.cookbooks.collect(&:name).should include dep
|
20
|
+
end
|
21
|
+
|
22
|
+
KnifeCookbookDependencies.shelf.populate_cookbooks_directory
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
describe DependencyReader do
|
5
|
+
subject { DependencyReader.new(Cookbook.new('mysql')) }
|
6
|
+
|
7
|
+
before do
|
8
|
+
@cookbook = Cookbook.new('mysql')
|
9
|
+
@cookbook.download # TODO: mock out
|
10
|
+
@cookbook.unpack
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
@cookbook.clean
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should parse the metadata file for dependencies" do
|
18
|
+
subject.read.should == [Cookbook.new('openssl')]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not blow up when reading a metadata.rb that overrides the name" do
|
22
|
+
Cookbook.any_instance.stub(:metadata_file).and_return <<M
|
23
|
+
name 'dontblowupplease'
|
24
|
+
version '1.2.3'
|
25
|
+
M
|
26
|
+
-> { subject.read }.should_not raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should display a warning when no version is defined in the metadata.rb'
|
30
|
+
|
31
|
+
it "should add a constraint to the cookbook on the shelf instead of adding a new dependency" do
|
32
|
+
KnifeCookbookDependencies.shelf.shelve_cookbook example_cookbook_from_path
|
33
|
+
subject.depends('example_cookbook')
|
34
|
+
subject.dependency_list.should be_empty
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should create a new cookbook if the cookbook is not found on the shelf" do
|
38
|
+
subject.depends('example_cookbook')
|
39
|
+
subject.dependency_list.first.name.should == 'example_cookbook'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'knife_cookbook_dependencies/dsl'
|
3
|
+
|
4
|
+
module KnifeCookbookDependencies
|
5
|
+
describe DSL do
|
6
|
+
include DSL
|
7
|
+
|
8
|
+
describe "#cookbook" do
|
9
|
+
after do
|
10
|
+
KnifeCookbookDependencies.shelf.cookbooks.each(&:clean)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should add the cookbooks to the shelf' do
|
14
|
+
cookbook "ntp"
|
15
|
+
cookbook "nginx"
|
16
|
+
|
17
|
+
['ntp', 'nginx'].each do |cookbook|
|
18
|
+
KnifeCookbookDependencies.shelf.cookbooks.collect(&:name).should include cookbook
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should take version constraints' do
|
23
|
+
Cookbook.any_instance.stub(:clean)
|
24
|
+
cookbook 'ntp', '= 1.2.3'
|
25
|
+
KnifeCookbookDependencies.shelf.cookbooks.select {|c| c.name == 'ntp'}.first.version_constraints.first.should == DepSelector::VersionConstraint.new('= 1.2.3')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
describe Git do
|
5
|
+
|
6
|
+
it "should find git" do
|
7
|
+
Git.find_git.should_not be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should raise if it can't find git" do
|
11
|
+
begin
|
12
|
+
path = ENV["PATH"]
|
13
|
+
ENV["PATH"] = ""
|
14
|
+
|
15
|
+
lambda { Git.find_git }.should raise_error
|
16
|
+
ensure
|
17
|
+
ENV["PATH"] = path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "instances" do
|
22
|
+
subject { Git.new("https://github.com/erikh/chef-ssh_known_hosts2.git") }
|
23
|
+
|
24
|
+
after do
|
25
|
+
subject.clean
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be set the repository accessor" do
|
29
|
+
subject.repository.should == "https://github.com/erikh/chef-ssh_known_hosts2.git"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should be able to clone this repository" do
|
33
|
+
subject.clone
|
34
|
+
|
35
|
+
File.exist?(subject.directory).should be_true
|
36
|
+
File.directory?(subject.directory).should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should clone, then pull if the directory is the same" do
|
40
|
+
subject.clone
|
41
|
+
dir = subject.directory
|
42
|
+
subject.clone
|
43
|
+
dir2 = subject.directory
|
44
|
+
|
45
|
+
dir.should == dir2
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should checkout the v0.1.0 tag" do
|
49
|
+
lambda { subject.checkout "fart" }.should raise_error
|
50
|
+
lambda { subject.checkout "v0.1.0" }.should_not raise_error
|
51
|
+
|
52
|
+
Dir.chdir subject.directory do
|
53
|
+
%x[git rev-parse v0.1.0].should == %x[git rev-parse HEAD]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module KnifeCookbookDependencies
|
4
|
+
describe Shelf do
|
5
|
+
describe '#get_cookbook' do
|
6
|
+
it "should return nil if a cookbook doesn't exist on the shelf" do
|
7
|
+
Shelf.new.get_cookbook('arbitrary').should be_nil
|
8
|
+
end
|
9
|
+
it "should return the cookbook if the cookbook exists on the shelf" do
|
10
|
+
s = Shelf.new
|
11
|
+
s.shelve_cookbook example_cookbook_from_path
|
12
|
+
s.get_cookbook(example_cookbook_from_path.name).should_not be_nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#shelve_cookbook" do
|
17
|
+
subject { Shelf.new }
|
18
|
+
it 'should store shelved cookbooks' do
|
19
|
+
subject.shelve_cookbook 'acookbook'
|
20
|
+
subject.cookbooks.collect(&:name).should include 'acookbook'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should take version constraints' do
|
24
|
+
subject.shelve_cookbook 'acookbook', '= 1.2.3'
|
25
|
+
subject.cookbooks.last.version_constraints.should == [DepSelector::VersionConstraint.new('= 1.2.3')]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should resolve the dependency graph of the cookbooks on the shelf" do
|
29
|
+
subject.shelve_cookbook 'mysql', "= 1.2.4"
|
30
|
+
|
31
|
+
subject.resolve_dependencies.should == ({"mysql" => DepSelector::Version.new("1.2.4"), "openssl" => DepSelector::Version.new("1.0.0")})
|
32
|
+
Cookbook.new('mysql').clean
|
33
|
+
Cookbook.new('openssl').clean
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.push File.join(File.dirname(__FILE__), '..')
|
2
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'simplecov'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.after do
|
9
|
+
KnifeCookbookDependencies.clean
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
SimpleCov.start do
|
14
|
+
add_filter 'spec/'
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'knife_cookbook_dependencies'
|
18
|
+
|
19
|
+
def example_cookbook_from_path
|
20
|
+
@example_cookbook_from_path ||= KnifeCookbookDependencies::Cookbook.new('example_cookbook', path: File.join(File.dirname(__FILE__), 'fixtures', 'cookbooks'))
|
21
|
+
end
|
data/todo.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Add git support to Cookbookfile
|
2
|
+
- Allow a cookbook to be pulled from a git repository
|
3
|
+
- Also support 'ref' to give a commit hash to pull
|
4
|
+
- Possibly support 'branch' to pull the HEAD from a given branch
|
5
|
+
Add path support to Cookbookfile
|
6
|
+
- Pull cookbook from a path on the local filesystem
|
7
|
+
Add source support to Cookbookfile
|
8
|
+
- Allow unofficial cookbook repositories to be used via 'source' in Cookbookfile
|
9
|
+
Create local mock API service for specs
|
10
|
+
- Instead of mocking out methods, spin up a local mock cookbook repo for tests to run against. This should be able to serve cookbooks as gzips. This might require some monkey patching of knife to get it to talk to the mock server instead of the opscode community site.
|
11
|
+
Optimize cookbook retrieval
|
12
|
+
- Possible: Pester Opscode till they release an API call to retrieve cookbook dependencies
|
13
|
+
- Possible: Keep an index of dependencies so we don't have to download cookbook versions we've seen before
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: knife_cookbook_dependencies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josiah Kiehl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: dep_selector
|
16
|
+
requirement: &70196647928780 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70196647928780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: chef
|
27
|
+
requirement: &70196647928060 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.10.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70196647928060
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitar
|
38
|
+
requirement: &70196647943200 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70196647943200
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &70196647940420 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.9.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70196647940420
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: &70196647936660 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70196647936660
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rdoc
|
71
|
+
requirement: &70196647944920 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '3.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70196647944920
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: simplecov
|
82
|
+
requirement: &70196647960000 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70196647960000
|
91
|
+
description: Resolves cookbook dependencies
|
92
|
+
email:
|
93
|
+
- josiah@skirmisher.net
|
94
|
+
executables: []
|
95
|
+
extensions: []
|
96
|
+
extra_rdoc_files: []
|
97
|
+
files:
|
98
|
+
- .gitignore
|
99
|
+
- .rbenv-version
|
100
|
+
- Gemfile
|
101
|
+
- Gemfile.lock
|
102
|
+
- LICENSE
|
103
|
+
- README.rdoc
|
104
|
+
- Rakefile
|
105
|
+
- knife_cookbook_dependencies.gemspec
|
106
|
+
- lib/chef/knife/cookbook_dependencies_install.rb
|
107
|
+
- lib/knife_cookbook_dependencies.rb
|
108
|
+
- lib/knife_cookbook_dependencies/cookbook.rb
|
109
|
+
- lib/knife_cookbook_dependencies/cookbookfile.rb
|
110
|
+
- lib/knife_cookbook_dependencies/dependency_reader.rb
|
111
|
+
- lib/knife_cookbook_dependencies/dsl.rb
|
112
|
+
- lib/knife_cookbook_dependencies/git.rb
|
113
|
+
- lib/knife_cookbook_dependencies/knife_utils.rb
|
114
|
+
- lib/knife_cookbook_dependencies/lockfile.rb
|
115
|
+
- lib/knife_cookbook_dependencies/metacookbook.rb
|
116
|
+
- lib/knife_cookbook_dependencies/shelf.rb
|
117
|
+
- lib/knife_cookbook_dependencies/version.rb
|
118
|
+
- spec/fixtures/cookbooks/example_cookbook/README.md
|
119
|
+
- spec/fixtures/cookbooks/example_cookbook/metadata.rb
|
120
|
+
- spec/fixtures/cookbooks/example_cookbook/recipes/default.rb
|
121
|
+
- spec/lib/knife_cookbook_dependencies/cookbook_spec.rb
|
122
|
+
- spec/lib/knife_cookbook_dependencies/cookbookfile_spec.rb
|
123
|
+
- spec/lib/knife_cookbook_dependencies/dependency_reader_spec.rb
|
124
|
+
- spec/lib/knife_cookbook_dependencies/dsl_spec.rb
|
125
|
+
- spec/lib/knife_cookbook_dependencies/git_spec.rb
|
126
|
+
- spec/lib/knife_cookbook_dependencies/shelf_spec.rb
|
127
|
+
- spec/spec_helper.rb
|
128
|
+
- todo.txt
|
129
|
+
homepage: http://github.com/RiotGames/knife_cookbook_dependencies
|
130
|
+
licenses: []
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ! '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - ! '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 1.8.11
|
150
|
+
signing_key:
|
151
|
+
specification_version: 3
|
152
|
+
summary: Resolves cookbook dependencies
|
153
|
+
test_files:
|
154
|
+
- spec/fixtures/cookbooks/example_cookbook/README.md
|
155
|
+
- spec/fixtures/cookbooks/example_cookbook/metadata.rb
|
156
|
+
- spec/fixtures/cookbooks/example_cookbook/recipes/default.rb
|
157
|
+
- spec/lib/knife_cookbook_dependencies/cookbook_spec.rb
|
158
|
+
- spec/lib/knife_cookbook_dependencies/cookbookfile_spec.rb
|
159
|
+
- spec/lib/knife_cookbook_dependencies/dependency_reader_spec.rb
|
160
|
+
- spec/lib/knife_cookbook_dependencies/dsl_spec.rb
|
161
|
+
- spec/lib/knife_cookbook_dependencies/git_spec.rb
|
162
|
+
- spec/lib/knife_cookbook_dependencies/shelf_spec.rb
|
163
|
+
- spec/spec_helper.rb
|