librarian-chef 0.0.1.beta.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 +9 -0
- data/.rspec +1 -0
- data/.travis.yml +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +386 -0
- data/Rakefile +1 -0
- data/lib/librarian-chef.rb +1 -0
- data/lib/librarian/chef.rb +1 -0
- data/lib/librarian/chef/cli.rb +47 -0
- data/lib/librarian/chef/dsl.rb +16 -0
- data/lib/librarian/chef/environment.rb +32 -0
- data/lib/librarian/chef/extension.rb +9 -0
- data/lib/librarian/chef/integration/knife.rb +46 -0
- data/lib/librarian/chef/manifest_reader.rb +59 -0
- data/lib/librarian/chef/source.rb +4 -0
- data/lib/librarian/chef/source/git.rb +25 -0
- data/lib/librarian/chef/source/github.rb +27 -0
- data/lib/librarian/chef/source/local.rb +74 -0
- data/lib/librarian/chef/source/path.rb +12 -0
- data/lib/librarian/chef/source/site.rb +442 -0
- data/lib/librarian/chef/templates/Cheffile +15 -0
- data/lib/librarian/chef/version.rb +5 -0
- data/librarian-chef.gemspec +27 -0
- data/spec/functional/chef/cli_spec.rb +195 -0
- data/spec/functional/chef/source/site_spec.rb +266 -0
- data/spec/integration/chef/source/git_spec.rb +451 -0
- data/spec/integration/chef/source/site_spec.rb +217 -0
- metadata +176 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#^syntax detection
|
3
|
+
|
4
|
+
site 'http://community.opscode.com/api/v1'
|
5
|
+
|
6
|
+
# cookbook 'chef-client'
|
7
|
+
|
8
|
+
# cookbook 'apache2', '>= 1.0.0'
|
9
|
+
|
10
|
+
# cookbook 'rvm',
|
11
|
+
# :git => 'https://github.com/fnichol/chef-rvm'
|
12
|
+
|
13
|
+
# cookbook 'postgresql',
|
14
|
+
# :git => 'https://github.com/findsyou/cookbooks',
|
15
|
+
# :ref => 'postgresql-improvements'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'librarian/chef/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "librarian-chef"
|
8
|
+
gem.version = Librarian::Chef::VERSION
|
9
|
+
gem.authors = ["Jay Feldblum"]
|
10
|
+
gem.email = ["y_feldblum@yahoo.com"]
|
11
|
+
gem.description = %q{Librarian::Chef}
|
12
|
+
gem.summary = %q{Librarian::Chef}
|
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 "librarian", ">= 0.1.0.beta.1"
|
21
|
+
gem.add_dependency "chef", ">= 0.10"
|
22
|
+
gem.add_dependency "archive-tar-minitar", ">= 0.5.2"
|
23
|
+
|
24
|
+
gem.add_development_dependency "rake"
|
25
|
+
gem.add_development_dependency "rspec"
|
26
|
+
gem.add_development_dependency "webmock"
|
27
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
require "librarian/rspec/support/cli_macro"
|
4
|
+
|
5
|
+
require "librarian/chef/cli"
|
6
|
+
|
7
|
+
module Librarian
|
8
|
+
module Chef
|
9
|
+
describe Cli do
|
10
|
+
include Librarian::RSpec::Support::CliMacro
|
11
|
+
|
12
|
+
describe "init" do
|
13
|
+
before do
|
14
|
+
cli! "init"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create a file named Cheffile" do
|
18
|
+
pwd.should have_file "Cheffile"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "version" do
|
23
|
+
before do
|
24
|
+
cli! "version"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should print the version" do
|
28
|
+
stdout.should == strip_heredoc(<<-STDOUT)
|
29
|
+
librarian-#{Librarian::VERSION}
|
30
|
+
librarian-chef-#{Librarian::Chef::VERSION}
|
31
|
+
STDOUT
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "install" do
|
36
|
+
|
37
|
+
context "a simple Cheffile with one cookbook" do
|
38
|
+
let(:metadata) { {
|
39
|
+
"name" => "apt",
|
40
|
+
"version" => "1.0.0",
|
41
|
+
"dependencies" => { },
|
42
|
+
} }
|
43
|
+
|
44
|
+
before do
|
45
|
+
write_json_file! "cookbook-sources/apt/metadata.json", metadata
|
46
|
+
write_file! "Cheffile", strip_heredoc(<<-CHEFFILE)
|
47
|
+
cookbook 'apt',
|
48
|
+
:path => 'cookbook-sources'
|
49
|
+
CHEFFILE
|
50
|
+
|
51
|
+
cli! "install"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should write a lockfile" do
|
55
|
+
pwd.should have_file "Cheffile.lock"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should install the cookbook" do
|
59
|
+
pwd.should have_json_file "cookbooks/apt/metadata.json", metadata
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "a simple Cheffile with one cookbook with one dependency" do
|
64
|
+
let(:main_metadata) { {
|
65
|
+
"name" => "main",
|
66
|
+
"version" => "1.0.0",
|
67
|
+
"dependencies" => {
|
68
|
+
"sub" => "1.0.0",
|
69
|
+
}
|
70
|
+
} }
|
71
|
+
let(:sub_metadata) { {
|
72
|
+
"name" => "sub",
|
73
|
+
"version" => "1.0.0",
|
74
|
+
"dependencies" => { },
|
75
|
+
} }
|
76
|
+
|
77
|
+
before do
|
78
|
+
write_json_file! "cookbook-sources/main/metadata.json", main_metadata
|
79
|
+
write_json_file! "cookbook-sources/sub/metadata.json", sub_metadata
|
80
|
+
write_file! "Cheffile", strip_heredoc(<<-CHEFFILE)
|
81
|
+
path 'cookbook-sources'
|
82
|
+
cookbook 'main'
|
83
|
+
CHEFFILE
|
84
|
+
|
85
|
+
cli! "install"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should write a lockfile" do
|
89
|
+
pwd.should have_file "Cheffile.lock"
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should install the dependant cookbook" do
|
93
|
+
pwd.should have_json_file "cookbooks/main/metadata.json", main_metadata
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should install the independent cookbook" do
|
97
|
+
pwd.should have_json_file "cookbooks/sub/metadata.json", sub_metadata
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "show" do
|
104
|
+
let(:main_metadata) { {
|
105
|
+
"name" => "main",
|
106
|
+
"version" => "1.0.0",
|
107
|
+
"dependencies" => {
|
108
|
+
"sub" => "1.0.0",
|
109
|
+
}
|
110
|
+
} }
|
111
|
+
let(:sub_metadata) { {
|
112
|
+
"name" => "sub",
|
113
|
+
"version" => "1.0.0",
|
114
|
+
"dependencies" => { },
|
115
|
+
} }
|
116
|
+
|
117
|
+
before do
|
118
|
+
write_json_file! "cookbook-sources/main/metadata.json", main_metadata
|
119
|
+
write_json_file! "cookbook-sources/sub/metadata.json", sub_metadata
|
120
|
+
write_file! "Cheffile", strip_heredoc(<<-CHEFFILE)
|
121
|
+
path 'cookbook-sources'
|
122
|
+
cookbook 'main'
|
123
|
+
CHEFFILE
|
124
|
+
|
125
|
+
cli! "install"
|
126
|
+
end
|
127
|
+
|
128
|
+
context "showing all without a lockfile" do
|
129
|
+
before do
|
130
|
+
pwd.join("Cheffile.lock").delete
|
131
|
+
|
132
|
+
cli! "show"
|
133
|
+
end
|
134
|
+
|
135
|
+
specify { exit_status.should == 1 }
|
136
|
+
|
137
|
+
it "should print a warning" do
|
138
|
+
stdout.should == strip_heredoc(<<-STDOUT)
|
139
|
+
Be sure to install first!
|
140
|
+
STDOUT
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "showing all" do
|
145
|
+
before do
|
146
|
+
cli! "show"
|
147
|
+
end
|
148
|
+
|
149
|
+
specify { exit_status.should == 0 }
|
150
|
+
|
151
|
+
it "should print a summary" do
|
152
|
+
stdout.should == strip_heredoc(<<-STDOUT)
|
153
|
+
main (1.0.0)
|
154
|
+
sub (1.0.0)
|
155
|
+
STDOUT
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "showing one without dependencies" do
|
160
|
+
before do
|
161
|
+
cli! "show", "sub"
|
162
|
+
end
|
163
|
+
|
164
|
+
specify { exit_status.should == 0 }
|
165
|
+
|
166
|
+
it "should print the details" do
|
167
|
+
stdout.should == strip_heredoc(<<-STDOUT)
|
168
|
+
sub (1.0.0)
|
169
|
+
source: cookbook-sources
|
170
|
+
STDOUT
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context "showing one with dependencies" do
|
175
|
+
before do
|
176
|
+
cli! "show", "main"
|
177
|
+
end
|
178
|
+
|
179
|
+
specify { exit_status.should == 0 }
|
180
|
+
|
181
|
+
it "should print the details" do
|
182
|
+
stdout.should == strip_heredoc(<<-STDOUT)
|
183
|
+
main (1.0.0)
|
184
|
+
source: cookbook-sources
|
185
|
+
dependencies:
|
186
|
+
sub (= 1.0.0)
|
187
|
+
STDOUT
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'json'
|
3
|
+
require 'webmock'
|
4
|
+
|
5
|
+
require 'librarian'
|
6
|
+
require 'librarian/helpers'
|
7
|
+
require 'librarian/chef'
|
8
|
+
require 'librarian/linter/source_linter'
|
9
|
+
|
10
|
+
module Librarian
|
11
|
+
module Chef
|
12
|
+
module Source
|
13
|
+
describe Site do
|
14
|
+
|
15
|
+
include WebMock::API
|
16
|
+
|
17
|
+
let(:project_path) do
|
18
|
+
project_path = Pathname.new(__FILE__).expand_path
|
19
|
+
project_path = project_path.dirname until project_path.join("Rakefile").exist?
|
20
|
+
project_path
|
21
|
+
end
|
22
|
+
let(:tmp_path) { project_path.join("tmp/spec/functional/chef/source/site") }
|
23
|
+
after { tmp_path.rmtree if tmp_path && tmp_path.exist? }
|
24
|
+
let(:sample_path) { tmp_path.join("sample") }
|
25
|
+
let(:sample_metadata) do
|
26
|
+
Helpers.strip_heredoc(<<-METADATA)
|
27
|
+
version "0.6.5"
|
28
|
+
METADATA
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:api_url) { "http://site.cookbooks.com" }
|
32
|
+
|
33
|
+
let(:sample_index_data) do
|
34
|
+
{
|
35
|
+
"name" => "sample",
|
36
|
+
"versions" => [
|
37
|
+
"#{api_url}/cookbooks/sample/versions/0_6_5"
|
38
|
+
]
|
39
|
+
}
|
40
|
+
end
|
41
|
+
let(:sample_0_6_5_data) do
|
42
|
+
{
|
43
|
+
"version" => "0.6.5",
|
44
|
+
"file" => "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar.gz"
|
45
|
+
}
|
46
|
+
end
|
47
|
+
let(:sample_0_6_5_package) do
|
48
|
+
s = StringIO.new
|
49
|
+
z = Zlib::GzipWriter.new(s, Zlib::NO_COMPRESSION)
|
50
|
+
t = Archive::Tar::Minitar::Output.new(z)
|
51
|
+
t.tar.add_file_simple("sample/metadata.rb", :mode => 0700,
|
52
|
+
:size => sample_metadata.bytesize){|io| io.write(sample_metadata)}
|
53
|
+
t.close
|
54
|
+
z.close unless z.closed?
|
55
|
+
s.string
|
56
|
+
end
|
57
|
+
|
58
|
+
# depends on repo_path being defined in each context
|
59
|
+
let(:env) { Environment.new(:project_path => repo_path) }
|
60
|
+
|
61
|
+
before do
|
62
|
+
stub_request(:get, "#{api_url}/cookbooks/sample").
|
63
|
+
to_return(:body => JSON.dump(sample_index_data))
|
64
|
+
|
65
|
+
stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5").
|
66
|
+
to_return(:body => JSON.dump(sample_0_6_5_data))
|
67
|
+
|
68
|
+
stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar.gz").
|
69
|
+
to_return(:body => sample_0_6_5_package)
|
70
|
+
end
|
71
|
+
|
72
|
+
after do
|
73
|
+
WebMock.reset!
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:repo_path) { tmp_path.join("methods") }
|
77
|
+
before { repo_path.mkpath }
|
78
|
+
|
79
|
+
describe "lint" do
|
80
|
+
it "lints" do
|
81
|
+
Librarian::Linter::SourceLinter.lint! described_class
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "class methods" do
|
86
|
+
|
87
|
+
describe ".lock_name" do
|
88
|
+
specify { described_class.lock_name.should == "SITE" }
|
89
|
+
end
|
90
|
+
|
91
|
+
describe ".from_spec_args" do
|
92
|
+
it "gives the expected source" do
|
93
|
+
args = { }
|
94
|
+
source = described_class.from_spec_args(env, api_url, args)
|
95
|
+
source.uri.should == api_url
|
96
|
+
end
|
97
|
+
|
98
|
+
it "raises on unexpected args" do
|
99
|
+
args = {:k => 3}
|
100
|
+
expect { described_class.from_spec_args(env, api_url, args) }.
|
101
|
+
to raise_error Librarian::Error, "unrecognized options: k"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe ".from_lock_options" do
|
106
|
+
it "gives the expected source" do
|
107
|
+
options = {:remote => api_url}
|
108
|
+
source = described_class.from_lock_options(env, options)
|
109
|
+
source.uri.should == api_url
|
110
|
+
end
|
111
|
+
|
112
|
+
it "roundtrips" do
|
113
|
+
options = {:remote => api_url}
|
114
|
+
source = described_class.from_lock_options(env, options)
|
115
|
+
source.to_lock_options.should == options
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "instance methods" do
|
122
|
+
let(:source) { described_class.new(env, api_url) }
|
123
|
+
|
124
|
+
describe "#manifests" do
|
125
|
+
it "gives a list of all manifests" do
|
126
|
+
manifests = source.manifests("sample")
|
127
|
+
manifests.should have(1).item
|
128
|
+
manifest = manifests.first
|
129
|
+
manifest.source.should be source
|
130
|
+
manifest.version.should == Manifest::Version.new("0.6.5")
|
131
|
+
manifest.dependencies.should be_empty
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#fetch_version" do
|
136
|
+
it "fetches the version based on extra" do
|
137
|
+
extra = "#{api_url}/cookbooks/sample/versions/0_6_5"
|
138
|
+
source.fetch_version("sample", extra).should == "0.6.5"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#fetch_dependencies" do
|
143
|
+
it "fetches the dependencies based on extra" do
|
144
|
+
extra = "#{api_url}/cookbooks/sample/versions/0_6_5"
|
145
|
+
source.fetch_dependencies("sample", "0.6.5", extra).should == [ ]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "#pinned?" do
|
150
|
+
it "returns false" do
|
151
|
+
source.should_not be_pinned
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#unpin!" do
|
156
|
+
it "is a no-op" do
|
157
|
+
source.unpin!
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "#install!" do
|
162
|
+
before { env.install_path.mkpath }
|
163
|
+
|
164
|
+
context "directly" do
|
165
|
+
it "installs the manifest" do
|
166
|
+
manifest = Manifest.new(source, "sample")
|
167
|
+
manifest.version = "0.6.5"
|
168
|
+
source.install!(manifest)
|
169
|
+
text = env.install_path.join("sample/metadata.rb").read
|
170
|
+
text.should == sample_metadata
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context "indirectly" do
|
175
|
+
it "installs the manifest" do
|
176
|
+
manifest = source.manifests("sample").first
|
177
|
+
source.install!(manifest)
|
178
|
+
text = env.install_path.join("sample/metadata.rb").read
|
179
|
+
text.should == sample_metadata
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "#to_spec_args" do
|
185
|
+
it "gives the expected spec args" do
|
186
|
+
source.to_spec_args.should == [api_url, { }]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "#to_lock_options" do
|
191
|
+
it "gives the expected lock options" do
|
192
|
+
source.to_lock_options.should == {:remote => api_url}
|
193
|
+
end
|
194
|
+
|
195
|
+
it "roundtrips" do
|
196
|
+
options = source.to_lock_options
|
197
|
+
described_class.from_lock_options(env, options).should == source
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "following http redirects" do
|
204
|
+
let(:source) { described_class.new(env, api_url) }
|
205
|
+
|
206
|
+
def redirect_to(url)
|
207
|
+
{:status => 302, :headers => {"Location" => url}}
|
208
|
+
end
|
209
|
+
|
210
|
+
context "with a sequence of http redirects" do
|
211
|
+
before do
|
212
|
+
stub_request(:get, "#{api_url}/cookbooks/sample").
|
213
|
+
to_return redirect_to "#{api_url}/cookbooks/sample-1"
|
214
|
+
stub_request(:get, "#{api_url}/cookbooks/sample-1").
|
215
|
+
to_return redirect_to "#{api_url}/cookbooks/sample-2"
|
216
|
+
stub_request(:get, "#{api_url}/cookbooks/sample-2").
|
217
|
+
to_return(:body => JSON.dump(sample_index_data))
|
218
|
+
end
|
219
|
+
|
220
|
+
it "follows a sequence of redirects" do
|
221
|
+
manifest = source.manifests("sample").first
|
222
|
+
manifest.version.to_s.should == "0.6.5"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context "with too many http redirects" do
|
227
|
+
before do
|
228
|
+
stub_request(:get, "#{api_url}/cookbooks/sample").
|
229
|
+
to_return redirect_to "#{api_url}/cookbooks/sample-1"
|
230
|
+
(1 .. 11).each do |i|
|
231
|
+
stub_request(:get, "#{api_url}/cookbooks/sample-#{i}").
|
232
|
+
to_return redirect_to "#{api_url}/cookbooks/sample-#{i + 1}"
|
233
|
+
end
|
234
|
+
stub_request(:get, "#{api_url}/cookbooks/sample-12").
|
235
|
+
to_return(:body => JSON.dump(sample_index_data))
|
236
|
+
end
|
237
|
+
|
238
|
+
it "raises, warning of too many redirects" do
|
239
|
+
expect { source.manifests("sample").first }.
|
240
|
+
to raise_error Librarian::Error, /because too many redirects!/
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
context "with a redirect cycle" do
|
245
|
+
before do
|
246
|
+
stub_request(:get, "#{api_url}/cookbooks/sample").
|
247
|
+
to_return redirect_to "#{api_url}/cookbooks/sample-1"
|
248
|
+
(1 .. 8).each do |i|
|
249
|
+
stub_request(:get, "#{api_url}/cookbooks/sample-#{i}").
|
250
|
+
to_return redirect_to "#{api_url}/cookbooks/sample-#{i + 1}"
|
251
|
+
end
|
252
|
+
stub_request(:get, "#{api_url}/cookbooks/sample-9").
|
253
|
+
to_return redirect_to "#{api_url}/cookbooks/sample-6"
|
254
|
+
end
|
255
|
+
|
256
|
+
it "raises, warning of a redirect cycle" do
|
257
|
+
expect { source.manifests("sample").first }.
|
258
|
+
to raise_error Librarian::Error, /because redirect cycle!/
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|