librarian-chef-nochef 0.1.0

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.
@@ -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,5 @@
1
+ module Librarian
2
+ module Chef
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "librarian-chef-nochef"
7
+ gem.version = "0.1.0"
8
+ gem.authors = ["Emiliano Ticci", "Jay Feldblum"]
9
+ gem.email = ["emiticci@gmail.com", "y_feldblum@yahoo.com"]
10
+ gem.summary = %q{A Bundler for your Chef Cookbooks that does not depends on chef.}
11
+ gem.description = %q{A Bundler for your Chef Cookbooks that does not depends on chef.}
12
+ gem.homepage = "https://github.com/emyl/librarian-chef-nochef"
13
+ gem.license = "MIT"
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"
21
+ gem.add_dependency "minitar", ">= 0.5.2"
22
+
23
+ gem.add_development_dependency "rake"
24
+ gem.add_development_dependency "rspec"
25
+ gem.add_development_dependency "webmock"
26
+ 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,409 @@
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
+ require 'support/project_path'
11
+
12
+ module Librarian
13
+ module Chef
14
+ module Source
15
+ describe Site do
16
+
17
+ include WebMock::API
18
+
19
+ let(:project_path) { ::Support::ProjectPath.project_path }
20
+ let(:tmp_path) { project_path.join("tmp/spec/functional/chef/source/site") }
21
+ after { tmp_path.rmtree if tmp_path && tmp_path.exist? }
22
+
23
+ let(:api_url) { "http://site.cookbooks.com" }
24
+
25
+ let(:repo_path) { tmp_path.join("repo") }
26
+ let(:env) { repo_path.mkpath ; Environment.new(:project_path => repo_path) }
27
+
28
+ after { WebMock.reset! }
29
+
30
+ describe "lint" do
31
+ it "lints" do
32
+ Librarian::Linter::SourceLinter.lint! described_class
33
+ end
34
+ end
35
+
36
+ describe "class methods" do
37
+
38
+ describe ".lock_name" do
39
+ specify { described_class.lock_name.should == "SITE" }
40
+ end
41
+
42
+ describe ".from_spec_args" do
43
+ it "gives the expected source" do
44
+ args = { }
45
+ source = described_class.from_spec_args(env, api_url, args)
46
+ source.uri.should == api_url
47
+ end
48
+
49
+ it "raises on unexpected args" do
50
+ args = {:k => 3}
51
+ expect { described_class.from_spec_args(env, api_url, args) }.
52
+ to raise_error Librarian::Error, "unrecognized options: k"
53
+ end
54
+ end
55
+
56
+ describe ".from_lock_options" do
57
+ it "gives the expected source" do
58
+ options = {:remote => api_url}
59
+ source = described_class.from_lock_options(env, options)
60
+ source.uri.should == api_url
61
+ end
62
+
63
+ it "roundtrips" do
64
+ options = {:remote => api_url}
65
+ source = described_class.from_lock_options(env, options)
66
+ source.to_lock_options.should == options
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ describe "instance methods" do
73
+
74
+ let(:sample_metadata) do
75
+ Helpers.strip_heredoc(<<-METADATA)
76
+ version "0.6.5"
77
+ METADATA
78
+ end
79
+
80
+ let(:sample_index_data) do
81
+ {
82
+ "name" => "sample",
83
+ "versions" => [
84
+ "#{api_url}/cookbooks/sample/versions/0_6_5"
85
+ ]
86
+ }
87
+ end
88
+ let(:sample_0_6_5_data) do
89
+ {
90
+ "version" => "0.6.5",
91
+ "file" => "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar.gz"
92
+ }
93
+ end
94
+ let(:sample_0_6_5_package) do
95
+ s = StringIO.new
96
+ z = Zlib::GzipWriter.new(s, Zlib::NO_COMPRESSION)
97
+ t = Archive::Tar::Minitar::Output.new(z)
98
+ t.tar.add_file_simple("sample/metadata.rb", :mode => 0700,
99
+ :size => sample_metadata.bytesize){|io| io.write(sample_metadata)}
100
+ t.close
101
+ z.close unless z.closed?
102
+ s.string
103
+ end
104
+
105
+ before do
106
+ stub_request(:get, "#{api_url}/cookbooks/sample").
107
+ to_return(:body => JSON.dump(sample_index_data))
108
+
109
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5").
110
+ to_return(:body => JSON.dump(sample_0_6_5_data))
111
+
112
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar.gz").
113
+ to_return(:body => sample_0_6_5_package)
114
+ end
115
+
116
+ let(:source) { described_class.new(env, api_url) }
117
+
118
+ describe "#manifests" do
119
+ it "gives a list of all manifests" do
120
+ manifests = source.manifests("sample")
121
+ manifests.should have(1).item
122
+ manifest = manifests.first
123
+ manifest.source.should be source
124
+ manifest.version.should == Manifest::Version.new("0.6.5")
125
+ manifest.dependencies.should be_empty
126
+ end
127
+ end
128
+
129
+ describe "#fetch_version" do
130
+ it "fetches the version based on extra" do
131
+ extra = "#{api_url}/cookbooks/sample/versions/0_6_5"
132
+ source.fetch_version("sample", extra).should == "0.6.5"
133
+ end
134
+ end
135
+
136
+ describe "#fetch_dependencies" do
137
+ it "fetches the dependencies based on extra" do
138
+ extra = "#{api_url}/cookbooks/sample/versions/0_6_5"
139
+ source.fetch_dependencies("sample", "0.6.5", extra).should == [ ]
140
+ end
141
+ end
142
+
143
+ describe "#pinned?" do
144
+ it "returns false" do
145
+ source.should_not be_pinned
146
+ end
147
+ end
148
+
149
+ describe "#unpin!" do
150
+ it "is a no-op" do
151
+ source.unpin!
152
+ end
153
+ end
154
+
155
+ describe "#install!" do
156
+ before { env.install_path.mkpath }
157
+
158
+ context "directly" do
159
+ it "installs the manifest" do
160
+ manifest = Manifest.new(source, "sample")
161
+ manifest.version = "0.6.5"
162
+ source.install!(manifest)
163
+ text = env.install_path.join("sample/metadata.rb").read
164
+ text.should == sample_metadata
165
+ end
166
+ end
167
+
168
+ context "indirectly" do
169
+ it "installs the manifest" do
170
+ manifest = source.manifests("sample").first
171
+ source.install!(manifest)
172
+ text = env.install_path.join("sample/metadata.rb").read
173
+ text.should == sample_metadata
174
+ end
175
+ end
176
+ end
177
+
178
+ describe "#to_spec_args" do
179
+ it "gives the expected spec args" do
180
+ source.to_spec_args.should == [api_url, { }]
181
+ end
182
+ end
183
+
184
+ describe "#to_lock_options" do
185
+ it "gives the expected lock options" do
186
+ source.to_lock_options.should == {:remote => api_url}
187
+ end
188
+
189
+ it "roundtrips" do
190
+ options = source.to_lock_options
191
+ described_class.from_lock_options(env, options).should == source
192
+ end
193
+ end
194
+
195
+ end
196
+
197
+ describe "following http redirects" do
198
+ let(:source) { described_class.new(env, api_url) }
199
+
200
+ let(:sample_metadata) do
201
+ Helpers.strip_heredoc(<<-METADATA)
202
+ version "0.6.5"
203
+ METADATA
204
+ end
205
+
206
+ let(:sample_index_data) do
207
+ {
208
+ "name" => "sample",
209
+ "versions" => [
210
+ "#{api_url}/cookbooks/sample/versions/0_6_5"
211
+ ]
212
+ }
213
+ end
214
+ let(:sample_0_6_5_data) do
215
+ {
216
+ "version" => "0.6.5",
217
+ "file" => "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar.gz"
218
+ }
219
+ end
220
+ let(:sample_0_6_5_package) do
221
+ s = StringIO.new
222
+ z = Zlib::GzipWriter.new(s, Zlib::NO_COMPRESSION)
223
+ t = Archive::Tar::Minitar::Output.new(z)
224
+ t.tar.add_file_simple("sample/metadata.rb", :mode => 0700,
225
+ :size => sample_metadata.bytesize){|io| io.write(sample_metadata)}
226
+ t.close
227
+ z.close unless z.closed?
228
+ s.string
229
+ end
230
+
231
+ def redirect_to(url)
232
+ {:status => 302, :headers => {"Location" => url}}
233
+ end
234
+
235
+ context "with a sequence of http redirects" do
236
+ before do
237
+ stub_request(:get, "#{api_url}/cookbooks/sample").
238
+ to_return redirect_to "#{api_url}/cookbooks/sample-1"
239
+ stub_request(:get, "#{api_url}/cookbooks/sample-1").
240
+ to_return redirect_to "#{api_url}/cookbooks/sample-2"
241
+ stub_request(:get, "#{api_url}/cookbooks/sample-2").
242
+ to_return(:body => JSON.dump(sample_index_data))
243
+
244
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5").
245
+ to_return(:body => JSON.dump(sample_0_6_5_data))
246
+
247
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar.gz").
248
+ to_return(:body => sample_0_6_5_package)
249
+ end
250
+
251
+ it "follows a sequence of redirects" do
252
+ manifest = source.manifests("sample").first
253
+ manifest.version.to_s.should == "0.6.5"
254
+ end
255
+ end
256
+
257
+ context "with too many http redirects" do
258
+ before do
259
+ stub_request(:get, "#{api_url}/cookbooks/sample").
260
+ to_return redirect_to "#{api_url}/cookbooks/sample-1"
261
+ (1 .. 11).each do |i|
262
+ stub_request(:get, "#{api_url}/cookbooks/sample-#{i}").
263
+ to_return redirect_to "#{api_url}/cookbooks/sample-#{i + 1}"
264
+ end
265
+ stub_request(:get, "#{api_url}/cookbooks/sample-12").
266
+ to_return(:body => JSON.dump(sample_index_data))
267
+ end
268
+
269
+ it "raises, warning of too many redirects" do
270
+ expect { source.manifests("sample").first }.
271
+ to raise_error Librarian::Error, /because too many redirects!/
272
+ end
273
+ end
274
+
275
+ context "with a redirect cycle" do
276
+ before do
277
+ stub_request(:get, "#{api_url}/cookbooks/sample").
278
+ to_return redirect_to "#{api_url}/cookbooks/sample-1"
279
+ (1 .. 8).each do |i|
280
+ stub_request(:get, "#{api_url}/cookbooks/sample-#{i}").
281
+ to_return redirect_to "#{api_url}/cookbooks/sample-#{i + 1}"
282
+ end
283
+ stub_request(:get, "#{api_url}/cookbooks/sample-9").
284
+ to_return redirect_to "#{api_url}/cookbooks/sample-6"
285
+ end
286
+
287
+ it "raises, warning of a redirect cycle" do
288
+ expect { source.manifests("sample").first }.
289
+ to raise_error Librarian::Error, /because redirect cycle!/
290
+ end
291
+ end
292
+ end
293
+
294
+ describe "extracting .tar packages" do
295
+ let(:source) { described_class.new(env, api_url) }
296
+
297
+ let(:sample_metadata) do
298
+ Helpers.strip_heredoc(<<-METADATA)
299
+ version "0.6.5"
300
+ METADATA
301
+ end
302
+
303
+ let(:sample_index_data) do
304
+ {
305
+ "name" => "sample",
306
+ "versions" => [
307
+ "#{api_url}/cookbooks/sample/versions/0_6_5"
308
+ ]
309
+ }
310
+ end
311
+ let(:sample_0_6_5_data) do
312
+ {
313
+ "version" => "0.6.5",
314
+ "file" => "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar"
315
+ }
316
+ end
317
+ let(:sample_0_6_5_package) do
318
+ s = StringIO.new
319
+ t = Archive::Tar::Minitar::Output.new(s)
320
+ t.tar.add_file_simple("sample/metadata.rb", :mode => 0700,
321
+ :size => sample_metadata.bytesize){|io| io.write(sample_metadata)}
322
+ t.close
323
+ s.string
324
+ end
325
+
326
+ before do
327
+ stub_request(:get, "#{api_url}/cookbooks/sample").
328
+ to_return(:body => JSON.dump(sample_index_data))
329
+
330
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5").
331
+ to_return(:body => JSON.dump(sample_0_6_5_data))
332
+
333
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar").
334
+ to_return(:body => sample_0_6_5_package)
335
+ end
336
+
337
+ it "installs the manifest" do
338
+ env.install_path.mkpath
339
+ manifest = source.manifests("sample").first
340
+ source.install!(manifest)
341
+ text = env.install_path.join("sample/metadata.rb").read
342
+ text.should == sample_metadata
343
+ end
344
+
345
+ end
346
+
347
+ describe "extracting .tar packages with long filenames" do
348
+ let(:source) { described_class.new(env, api_url) }
349
+
350
+ before { tmp_path.mkpath unless tmp_path.exist? }
351
+ let(:bad_name_size) { Librarian::Posix.run!(%W[getconf NAME_MAX #{tmp_path}]).to_i }
352
+ let(:bad_name_path) { SecureRandom.hex((bad_name_size.to_f/2).ceil)[0...bad_name_size] }
353
+
354
+ before { expect(bad_name_path.size).to eq bad_name_size }
355
+
356
+ let(:sample_metadata) do
357
+ Helpers.strip_heredoc(<<-METADATA)
358
+ version "0.6.5"
359
+ METADATA
360
+ end
361
+
362
+ let(:sample_index_data) do
363
+ {
364
+ "name" => "sample",
365
+ "versions" => [
366
+ "#{api_url}/cookbooks/sample/versions/0_6_5"
367
+ ]
368
+ }
369
+ end
370
+ let(:sample_0_6_5_data) do
371
+ {
372
+ "version" => "0.6.5",
373
+ "file" => "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar"
374
+ }
375
+ end
376
+ let(:sample_0_6_5_package) do
377
+ tar_path = tmp_path + "tar-source"
378
+ tar_path.mkpath
379
+ tar_path.join("sample").mkpath
380
+ tar_path.join("sample/metadata.rb").open("w"){|io| io.write(sample_metadata)}
381
+ tar_path.join("sample/#{bad_name_path}").open("w"){|io| io.write("?")}
382
+ Librarian::Posix.run!(%W[tar cz -C #{tar_path} . ])
383
+ end
384
+
385
+ before do
386
+ stub_request(:get, "#{api_url}/cookbooks/sample").
387
+ to_return(:body => JSON.dump(sample_index_data))
388
+
389
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5").
390
+ to_return(:body => JSON.dump(sample_0_6_5_data))
391
+
392
+ stub_request(:get, "#{api_url}/cookbooks/sample/versions/0_6_5/file.tar").
393
+ to_return(:body => sample_0_6_5_package)
394
+ end
395
+
396
+ it "installs the manifest" do
397
+ env.install_path.mkpath
398
+ manifest = source.manifests("sample").first
399
+ source.install!(manifest)
400
+ text = env.install_path.join("sample/#{bad_name_path}").read
401
+ text.should == "?"
402
+ end
403
+
404
+ end
405
+
406
+ end
407
+ end
408
+ end
409
+ end