berkshelf 0.1.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.
Files changed (93) hide show
  1. data/.gitignore +20 -0
  2. data/.rbenv-version +1 -0
  3. data/Gemfile +3 -0
  4. data/Guardfile +23 -0
  5. data/LICENSE +22 -0
  6. data/README.rdoc +102 -0
  7. data/Thorfile +47 -0
  8. data/berkshelf.gemspec +39 -0
  9. data/features/config.sample.yml +3 -0
  10. data/features/init_command.feature +40 -0
  11. data/features/install.feature +55 -0
  12. data/features/lockfile.feature +22 -0
  13. data/features/step_definitions/chef_server_steps.rb +13 -0
  14. data/features/step_definitions/cli_steps.rb +56 -0
  15. data/features/step_definitions/filesystem_steps.rb +79 -0
  16. data/features/support/env.rb +55 -0
  17. data/features/update.feature +23 -0
  18. data/features/upload_command.feature +43 -0
  19. data/features/without.feature +25 -0
  20. data/lib/berkshelf.rb +90 -0
  21. data/lib/berkshelf/berksfile.rb +170 -0
  22. data/lib/berkshelf/cached_cookbook.rb +253 -0
  23. data/lib/berkshelf/cookbook_source.rb +139 -0
  24. data/lib/berkshelf/cookbook_source/git_location.rb +54 -0
  25. data/lib/berkshelf/cookbook_source/path_location.rb +27 -0
  26. data/lib/berkshelf/cookbook_source/site_location.rb +206 -0
  27. data/lib/berkshelf/cookbook_store.rb +77 -0
  28. data/lib/berkshelf/core_ext.rb +3 -0
  29. data/lib/berkshelf/core_ext/file.rb +14 -0
  30. data/lib/berkshelf/core_ext/kernel.rb +33 -0
  31. data/lib/berkshelf/core_ext/pathname.rb +24 -0
  32. data/lib/berkshelf/downloader.rb +92 -0
  33. data/lib/berkshelf/dsl.rb +39 -0
  34. data/lib/berkshelf/errors.rb +20 -0
  35. data/lib/berkshelf/generator_files/Berksfile.erb +3 -0
  36. data/lib/berkshelf/generator_files/chefignore +44 -0
  37. data/lib/berkshelf/git.rb +70 -0
  38. data/lib/berkshelf/init_generator.rb +38 -0
  39. data/lib/berkshelf/lockfile.rb +42 -0
  40. data/lib/berkshelf/resolver.rb +176 -0
  41. data/lib/berkshelf/tx_result.rb +12 -0
  42. data/lib/berkshelf/tx_result_set.rb +37 -0
  43. data/lib/berkshelf/uploader.rb +153 -0
  44. data/lib/berkshelf/version.rb +3 -0
  45. data/lib/chef/knife/berks_init.rb +29 -0
  46. data/lib/chef/knife/berks_install.rb +27 -0
  47. data/lib/chef/knife/berks_update.rb +23 -0
  48. data/lib/chef/knife/berks_upload.rb +39 -0
  49. data/spec/fixtures/Berksfile +3 -0
  50. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/README.md +12 -0
  51. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb +6 -0
  52. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/recipes/default.rb +8 -0
  53. data/spec/fixtures/cookbooks/example_cookbook/README.md +12 -0
  54. data/spec/fixtures/cookbooks/example_cookbook/metadata.rb +6 -0
  55. data/spec/fixtures/cookbooks/example_cookbook/recipes/default.rb +8 -0
  56. data/spec/fixtures/cookbooks/invalid_ruby_files-1.0.0/recipes/default.rb +1 -0
  57. data/spec/fixtures/cookbooks/invalid_template_files-1.0.0/templates/default/broken.erb +1 -0
  58. data/spec/fixtures/cookbooks/nginx-0.100.5/README.md +77 -0
  59. data/spec/fixtures/cookbooks/nginx-0.100.5/attributes/default.rb +65 -0
  60. data/spec/fixtures/cookbooks/nginx-0.100.5/definitions/nginx_site.rb +35 -0
  61. data/spec/fixtures/cookbooks/nginx-0.100.5/files/default/mime.types +73 -0
  62. data/spec/fixtures/cookbooks/nginx-0.100.5/files/ubuntu/mime.types +73 -0
  63. data/spec/fixtures/cookbooks/nginx-0.100.5/libraries/nginxlib.rb +1 -0
  64. data/spec/fixtures/cookbooks/nginx-0.100.5/metadata.rb +91 -0
  65. data/spec/fixtures/cookbooks/nginx-0.100.5/providers/defprovider.rb +1 -0
  66. data/spec/fixtures/cookbooks/nginx-0.100.5/recipes/default.rb +59 -0
  67. data/spec/fixtures/cookbooks/nginx-0.100.5/resources/defresource.rb +1 -0
  68. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/nginx.pill.erb +15 -0
  69. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/plugins/nginx.rb.erb +66 -0
  70. data/spec/fixtures/lockfile_spec/with_lock/Berksfile +1 -0
  71. data/spec/fixtures/lockfile_spec/without_lock/Berksfile.lock +5 -0
  72. data/spec/spec_helper.rb +92 -0
  73. data/spec/support/chef_api.rb +27 -0
  74. data/spec/support/matchers/file_system_matchers.rb +115 -0
  75. data/spec/support/matchers/filepath_matchers.rb +19 -0
  76. data/spec/unit/berkshelf/cached_cookbook_spec.rb +420 -0
  77. data/spec/unit/berkshelf/cookbook_source/git_location_spec.rb +59 -0
  78. data/spec/unit/berkshelf/cookbook_source/path_location_spec.rb +34 -0
  79. data/spec/unit/berkshelf/cookbook_source/site_location_spec.rb +166 -0
  80. data/spec/unit/berkshelf/cookbook_source_spec.rb +194 -0
  81. data/spec/unit/berkshelf/cookbook_store_spec.rb +71 -0
  82. data/spec/unit/berkshelf/cookbookfile_spec.rb +160 -0
  83. data/spec/unit/berkshelf/downloader_spec.rb +82 -0
  84. data/spec/unit/berkshelf/dsl_spec.rb +42 -0
  85. data/spec/unit/berkshelf/git_spec.rb +63 -0
  86. data/spec/unit/berkshelf/init_generator_spec.rb +52 -0
  87. data/spec/unit/berkshelf/lockfile_spec.rb +25 -0
  88. data/spec/unit/berkshelf/resolver_spec.rb +126 -0
  89. data/spec/unit/berkshelf/tx_result_set_spec.rb +77 -0
  90. data/spec/unit/berkshelf/tx_result_spec.rb +21 -0
  91. data/spec/unit/berkshelf/uploader_spec.rb +71 -0
  92. data/spec/unit/berkshelf_spec.rb +29 -0
  93. metadata +411 -0
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ module Berkshelf
4
+ describe CookbookStore do
5
+ subject { CookbookStore.new(tmp_path.join("cbstore_rspec")) }
6
+
7
+ describe "#initialize" do
8
+ it "creates the storage_path" do
9
+ storage_path = tmp_path.join("random_storage")
10
+ subject.class.new(storage_path)
11
+
12
+ storage_path.should exist
13
+ end
14
+ end
15
+
16
+ describe "#cookbook_path" do
17
+ let(:cookbook_name) { "nginx" }
18
+ let(:cookbook_version) { "0.101.2" }
19
+
20
+ before(:each) do
21
+ @cb_path = subject.cookbook_path(cookbook_name, cookbook_version)
22
+ end
23
+
24
+ it "returns an instance of Pathname" do
25
+ @cb_path.should be_a(Pathname)
26
+ end
27
+
28
+ it "returns a Cookbook Version's filepath within the storage path" do
29
+ @cb_path.dirname.should eql(subject.storage_path)
30
+ end
31
+
32
+ it "returns a basename containing the cookbook name and version separated by a dash" do
33
+ @cb_path.basename.to_s.should eql("#{cookbook_name}-#{cookbook_version}")
34
+ end
35
+ end
36
+
37
+ describe "#downloaded?" do
38
+ it "returns true if the store contains a Cookbook of the given name and version" do
39
+ CookbookSource.new("nginx", "0.101.2").download(subject.storage_path)
40
+
41
+ subject.downloaded?("nginx", "0.101.2").should be_true
42
+ end
43
+
44
+ it "returns false if the store does not contain a Cookbook of the given name and version" do
45
+ subject.downloaded?("notthere", "0.0.0").should be_false
46
+ end
47
+ end
48
+
49
+ describe "#cookbook" do
50
+ subject { CookbookStore.new(fixtures_path.join("cookbooks")) }
51
+
52
+ it "returns a CachedCookbook if the specified cookbook version exists" do
53
+ subject.cookbook("example_cookbook", "0.5.0").should be_a(CachedCookbook)
54
+ end
55
+
56
+ it "returns nil if the specified cookbook version does not exist" do
57
+ subject.cookbook("doesnotexist", "0.1.0").should be_nil
58
+ end
59
+ end
60
+
61
+ describe "#cookbooks" do
62
+ it "returns a list of CachedCookbooks" do
63
+ CookbookSource.new("nginx", "0.101.2").download(subject.storage_path)
64
+
65
+ subject.cookbooks.each do |cb|
66
+ cb.should be_a(CachedCookbook)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ module Berkshelf
4
+ describe Berksfile do
5
+ describe "ClassMethods" do
6
+ subject { Berksfile }
7
+
8
+ let(:content) do
9
+ <<-EOF
10
+ cookbook 'ntp', '<= 1.0.0'
11
+ cookbook 'mysql'
12
+ cookbook 'nginx', '< 0.101.2'
13
+ cookbook 'ssh_known_hosts2', :git => 'https://github.com/erikh/chef-ssh_known_hosts2.git'
14
+ EOF
15
+ end
16
+
17
+ describe "#read" do
18
+ it "reads the content of a Berksfile and adds the sources to the Shelf" do
19
+ cbfile = subject.read(content)
20
+
21
+ ['ntp', 'mysql', 'nginx', 'ssh_known_hosts2'].each do |name|
22
+ cbfile.should have_source(name)
23
+ end
24
+ end
25
+
26
+ it "returns an instance of Berksfile" do
27
+ subject.read(content).should be_a(Berksfile)
28
+ end
29
+ end
30
+
31
+ describe "#from_file" do
32
+ let(:cookbook_file) { fixtures_path.join('lockfile_spec', 'with_lock', 'Berksfile') }
33
+
34
+ it "reads a Berksfile and returns an instance Berksfile" do
35
+ subject.from_file(cookbook_file).should be_a(Berksfile)
36
+ end
37
+
38
+ context "when Berksfile does not exist at given path" do
39
+ let(:bad_path) { tmp_path.join("thisdoesnotexist") }
40
+
41
+ it "raises BerksfileNotFound" do
42
+ lambda {
43
+ subject.from_file(bad_path)
44
+ }.should raise_error(BerksfileNotFound)
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "#filter_sources" do
50
+ context "given one of the sources is a member of one of the excluded groups" do
51
+ let(:excluded_groups) { [:nautilus, :skarner] }
52
+ let(:source_one) { double('source_one') }
53
+ let(:source_two) { double('source_two') }
54
+
55
+ before(:each) do
56
+ source_one.stub(:groups) { [:nautilus] }
57
+ source_two.stub(:groups) { [:riven] }
58
+ @sources = [source_one, source_two]
59
+ end
60
+
61
+ it "returns an array without sources that were members of the excluded groups" do
62
+ result = subject.filter_sources(@sources, excluded_groups)
63
+
64
+ result.should_not include(source_one)
65
+ end
66
+
67
+ it "does not remove sources that were not a member of the excluded groups" do
68
+ result = subject.filter_sources(@sources, excluded_groups)
69
+
70
+ result.should include(source_two)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ let(:source_one) { double('source_one', name: "nginx") }
77
+ let(:source_two) { double('source_two', name: "mysql") }
78
+
79
+ subject do
80
+ cbf = Berksfile.new
81
+ cbf.add_source(source_one)
82
+ cbf.add_source(source_two)
83
+ cbf
84
+ end
85
+
86
+ describe "#sources" do
87
+ it "returns all CookbookSources added to the instance of Berksfile" do
88
+ result = subject.sources
89
+
90
+ result.should have(2).items
91
+ result.should include(source_one)
92
+ result.should include(source_two)
93
+ end
94
+
95
+ context "given the option :exclude" do
96
+ it "filters the sources before returning them" do
97
+ subject.class.should_receive(:filter_sources).with(subject.sources, :nautilus)
98
+
99
+ subject.sources(exclude: :nautilus)
100
+ end
101
+ end
102
+ end
103
+
104
+ describe "#groups" do
105
+ before(:each) do
106
+ source_one.stub(:groups) { [:nautilus, :skarner] }
107
+ source_two.stub(:groups) { [:nautilus, :riven] }
108
+ end
109
+
110
+ it "returns a hash containing keys for every group a source is a member of" do
111
+ subject.groups.keys.should have(3).items
112
+ subject.groups.should have_key(:nautilus)
113
+ subject.groups.should have_key(:skarner)
114
+ subject.groups.should have_key(:riven)
115
+ end
116
+
117
+ it "returns an Array of CookbookSources who are members of the group for value" do
118
+ subject.groups[:nautilus].should include(source_one)
119
+ subject.groups[:nautilus].should include(source_two)
120
+ subject.groups[:riven].should_not include(source_one)
121
+ end
122
+ end
123
+
124
+ describe "#install" do
125
+ let(:resolver) { double('resolver') }
126
+ before(:each) { Berkshelf::Resolver.stub(:new) { resolver } }
127
+
128
+ context "when a lockfile is not present" do
129
+ before(:each) do
130
+ subject.should_receive(:lockfile_present?).and_return(false)
131
+ resolver.should_receive(:sources).and_return([])
132
+ end
133
+
134
+ it "creates a new resolver and finds a solution by calling resolve on the resolver" do
135
+ resolver.should_receive(:resolve)
136
+
137
+ subject.install
138
+ end
139
+
140
+ it "writes a lockfile with the resolvers sources" do
141
+ resolver.should_receive(:resolve)
142
+ subject.should_receive(:write_lockfile).with([])
143
+
144
+ subject.install
145
+ end
146
+ end
147
+
148
+ context "when a lockfile is present" do
149
+ before(:each) { subject.should_receive(:lockfile_present?).and_return(true) }
150
+
151
+ it "does not write a new lock file" do
152
+ resolver.should_receive(:resolve)
153
+ subject.should_not_receive(:write_lockfile)
154
+
155
+ subject.install
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ module Berkshelf
4
+ describe Downloader do
5
+ subject { Downloader.new(CookbookStore.new(tmp_path)) }
6
+ let(:source) { CookbookSource.new("sparkle_motion") }
7
+
8
+ describe "#enqueue" do
9
+ it "should add a source to the queue" do
10
+ subject.enqueue(source)
11
+
12
+ subject.queue.should have(1).source
13
+ end
14
+
15
+ it "should not allow you to add an invalid source" do
16
+ lambda {
17
+ subject.enqueue("a string, not a source")
18
+ }.should raise_error(ArgumentError)
19
+ end
20
+ end
21
+
22
+ describe "#dequeue" do
23
+ before(:each) { subject.enqueue(source) }
24
+
25
+ it "should remove a source from the queue" do
26
+ subject.dequeue(source)
27
+
28
+ subject.queue.should be_empty
29
+ end
30
+ end
31
+
32
+ describe "#download_all" do
33
+ let(:source_one) { CookbookSource.new("nginx") }
34
+ let(:source_two) { CookbookSource.new("mysql") }
35
+
36
+ before(:each) do
37
+ subject.enqueue source_one
38
+ subject.enqueue source_two
39
+ end
40
+
41
+ it "should remove each item from the queue after a successful download" do
42
+ subject.download_all
43
+
44
+ subject.queue.should be_empty
45
+ end
46
+
47
+ it "should not remove the item from the queue if the download failed" do
48
+ subject.enqueue CookbookSource.new("does_not_exist_no_way")
49
+ subject.download_all
50
+
51
+ subject.queue.should have(1).sources
52
+ end
53
+
54
+ it "should return a TXResultSet" do
55
+ results = subject.download_all
56
+
57
+ results.should be_a(TXResultSet)
58
+ end
59
+ end
60
+
61
+ describe "#download" do
62
+ let(:source) { CookbookSource.new("nginx") }
63
+ let(:bad_source) { CookbookSource.new("donowaytexists") }
64
+
65
+ it "returns a TXResult" do
66
+ subject.download(source).should be_a(TXResult)
67
+ end
68
+
69
+ context "when successful" do
70
+ it "returns a successesful TXResult" do
71
+ subject.download(source).should be_success
72
+ end
73
+ end
74
+
75
+ context "when failure" do
76
+ it "returns a failed TXResult" do
77
+ subject.download(bad_source).should be_failed
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'berkshelf/dsl'
3
+
4
+ module Berkshelf
5
+ describe DSL do
6
+ subject do
7
+ Class.new do
8
+ include Berkshelf::DSL
9
+ end.new
10
+ end
11
+
12
+ describe "#cookbook" do
13
+ it "calls add source to the instance of the implementing class with a CookbookSource" do
14
+ subject.should_receive(:add_source).with(kind_of(CookbookSource))
15
+
16
+ subject.cookbook "ntp"
17
+ end
18
+ end
19
+
20
+ describe '#group' do
21
+ it "calls add source to the instance of the implementing class with a CookbookSource" do
22
+ subject.should_receive(:add_source).with(kind_of(CookbookSource))
23
+
24
+ subject.group "awesome" do
25
+ subject.cookbook "ntp"
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#metadata" do
31
+ before(:each) do
32
+ Dir.chdir fixtures_path.join('cookbooks/example_cookbook')
33
+ end
34
+
35
+ it "calls add source to the instance of the implementing class with a CookbookSource" do
36
+ subject.should_receive(:add_source).with(kind_of(CookbookSource))
37
+
38
+ subject.metadata
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ module Berkshelf
4
+ describe Git do
5
+ describe "ClassMethods" do
6
+ subject { Git }
7
+
8
+ describe "#find_git" do
9
+ it "should find git" do
10
+ subject.find_git.should_not be_nil
11
+ end
12
+
13
+ it "should raise if it can't find git" do
14
+ begin
15
+ path = ENV["PATH"]
16
+ ENV["PATH"] = ""
17
+
18
+ lambda { subject.find_git }.should raise_error
19
+ ensure
20
+ ENV["PATH"] = path
21
+ end
22
+ end
23
+ end
24
+
25
+ describe "#clone" do
26
+ let(:target) { tmp_path.join("nginx") }
27
+
28
+ it "clones the repository to the target path" do
29
+ subject.clone("git://github.com/opscode-cookbooks/nginx.git", target)
30
+
31
+ target.should exist
32
+ target.should be_directory
33
+ end
34
+ end
35
+
36
+ describe "#checkout" do
37
+ let(:repo_path) { tmp_path.join("nginx") }
38
+ let(:repo) { subject.clone("git://github.com/opscode-cookbooks/nginx.git", repo_path) }
39
+ let(:tag) { "0.101.2" }
40
+
41
+ it "checks out the specified path of the given repository" do
42
+ subject.checkout(repo, tag)
43
+
44
+ Dir.chdir repo_path do
45
+ %x[git rev-parse #{tag}].should == %x[git rev-parse HEAD]
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "#rev_parse" do
51
+ let(:repo_path) { tmp_path.join("nginx") }
52
+ before(:each) do
53
+ subject.clone("git://github.com/opscode-cookbooks/nginx.git", repo_path)
54
+ subject.checkout(repo_path, "0e4887d9eef8cb83972f974a85890983c8204c3b")
55
+ end
56
+
57
+ it "returns the ref for HEAD" do
58
+ subject.rev_parse(repo_path).should eql("0e4887d9eef8cb83972f974a85890983c8204c3b")
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ module Berkshelf
4
+ describe InitGenerator do
5
+ subject { InitGenerator }
6
+
7
+ let(:target_root) { tmp_path.join("some_cookbook") }
8
+
9
+ context "with default options" do
10
+ before do
11
+ generator = subject.new([], :path => target_root)
12
+ capture(:stdout) { generator.invoke_all }
13
+ end
14
+
15
+ specify do
16
+ target_root.should have_structure {
17
+ file "Berksfile"
18
+ no_file ".chefignore"
19
+ }
20
+ end
21
+ end
22
+
23
+ context "with a .chefignore" do
24
+ before do
25
+ generator = subject.new([], :path => target_root, :chefignore => true)
26
+ capture(:stdout) { generator.invoke_all }
27
+ end
28
+
29
+ specify do
30
+ target_root.should have_structure {
31
+ file "Berksfile"
32
+ file ".chefignore"
33
+ }
34
+ end
35
+ end
36
+
37
+ context "with a metadata entry in the Berksfile" do
38
+ before do
39
+ generator = subject.new([], :path => target_root, :metadata_entry => true)
40
+ capture(:stdout) { generator.invoke_all }
41
+ end
42
+
43
+ specify do
44
+ target_root.should have_structure {
45
+ file "Berksfile" do
46
+ contains "metadata"
47
+ end
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end