berkshelf 0.3.3 → 0.3.7

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 (39) hide show
  1. data/Guardfile +1 -1
  2. data/features/install.feature +129 -2
  3. data/features/lockfile.feature +1 -1
  4. data/features/step_definitions/filesystem_steps.rb +12 -0
  5. data/features/update.feature +1 -1
  6. data/features/upload_command.feature +1 -1
  7. data/lib/berkshelf/berksfile.rb +4 -1
  8. data/lib/berkshelf/cached_cookbook.rb +36 -9
  9. data/lib/berkshelf/cli.rb +2 -2
  10. data/lib/berkshelf/cookbook_source.rb +26 -63
  11. data/lib/berkshelf/cookbook_source/git_location.rb +21 -10
  12. data/lib/berkshelf/cookbook_source/location.rb +50 -0
  13. data/lib/berkshelf/cookbook_source/path_location.rb +13 -5
  14. data/lib/berkshelf/cookbook_source/site_location.rb +14 -4
  15. data/lib/berkshelf/cookbook_store.rb +34 -16
  16. data/lib/berkshelf/core_ext/string.rb +12 -0
  17. data/lib/berkshelf/downloader.rb +0 -4
  18. data/lib/berkshelf/errors.rb +41 -1
  19. data/lib/berkshelf/git.rb +83 -14
  20. data/lib/berkshelf/resolver.rb +85 -66
  21. data/lib/berkshelf/version.rb +1 -1
  22. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb +1 -0
  23. data/spec/fixtures/cookbooks/example_metadata_name/metadata.rb +2 -0
  24. data/spec/fixtures/cookbooks/example_metadata_no_name/metadata.rb +1 -0
  25. data/spec/fixtures/cookbooks/example_no_metadata/recipes/default.rb +1 -0
  26. data/spec/support/chef_api.rb +42 -0
  27. data/spec/unit/berkshelf/cached_cookbook_spec.rb +55 -11
  28. data/spec/unit/berkshelf/cookbook_source/git_location_spec.rb +65 -15
  29. data/spec/unit/berkshelf/cookbook_source/location_spec.rb +42 -0
  30. data/spec/unit/berkshelf/cookbook_source/path_location_spec.rb +22 -5
  31. data/spec/unit/berkshelf/cookbook_source/site_location_spec.rb +32 -13
  32. data/spec/unit/berkshelf/cookbook_source_spec.rb +24 -33
  33. data/spec/unit/berkshelf/cookbook_store_spec.rb +47 -7
  34. data/spec/unit/berkshelf/git_spec.rb +97 -12
  35. data/spec/unit/berkshelf/resolver_spec.rb +72 -48
  36. data/spec/unit/berkshelf/uploader_spec.rb +12 -4
  37. metadata +13 -7
  38. data/spec/fixtures/cookbooks/invalid_ruby_files-1.0.0/recipes/default.rb +0 -1
  39. data/spec/fixtures/cookbooks/invalid_template_files-1.0.0/templates/default/broken.erb +0 -1
@@ -0,0 +1,42 @@
1
+ module Berkshelf
2
+ describe CookbookSource::Location do
3
+ let(:name) { "nginx" }
4
+ let(:constraint) { double('constraint') }
5
+
6
+ subject do
7
+ Class.new do
8
+ include CookbookSource::Location
9
+ end.new(name, constraint)
10
+ end
11
+
12
+ it "sets the downloaded? state to false" do
13
+ subject.downloaded?.should be_false
14
+ end
15
+
16
+ describe "#download" do
17
+ it "raises a NotImplementedError if not overridden" do
18
+ lambda {
19
+ subject.download(double('destination'))
20
+ }.should raise_error(NotImplementedError)
21
+ end
22
+ end
23
+
24
+ describe "#validate_cached" do
25
+ let(:cached) { double('cached-cb', version: "0.1.0") }
26
+
27
+ it "raises a ConstraintNotSatisfied error if the version constraint does not satisfy the cached version" do
28
+ constraint.should_receive(:include?).with(cached.version).and_return(false)
29
+
30
+ lambda {
31
+ subject.validate_cached(cached)
32
+ }.should raise_error(ConstraintNotSatisfied)
33
+ end
34
+
35
+ it "returns true if the version constraint satisfies the cached version" do
36
+ constraint.should_receive(:include?).with(cached.version).and_return(true)
37
+
38
+ subject.validate_cached(cached).should be_true
39
+ end
40
+ end
41
+ end
42
+ end
@@ -2,16 +2,23 @@ require 'spec_helper'
2
2
 
3
3
  module Berkshelf
4
4
  describe CookbookSource::PathLocation do
5
+ let(:complacent_constraint) { double('comp-vconstraint', include?: true) }
5
6
  let(:path) { fixtures_path.join("cookbooks", "example_cookbook").to_s }
6
- subject { CookbookSource::PathLocation.new("nginx", :path => path) }
7
+ subject { CookbookSource::PathLocation.new("nginx", complacent_constraint, path: path) }
7
8
 
8
9
  describe "#download" do
9
- it "returns the path to the cookbook" do
10
- subject.download(tmp_path).should eql(path)
10
+ it "returns an instance of CachedCookbook" do
11
+ subject.download(tmp_path).should be_a(CachedCookbook)
12
+ end
13
+
14
+ it "sets the downloaded status to true" do
15
+ subject.download(tmp_path)
16
+
17
+ subject.should be_downloaded
11
18
  end
12
19
 
13
20
  context "given a path that does not exist" do
14
- subject { CookbookSource::PathLocation.new("doesnot_exist", :path => tmp_path.join("doesntexist_noway")) }
21
+ subject { CookbookSource::PathLocation.new("doesnot_exist", complacent_constraint, path: tmp_path.join("doesntexist_noway")) }
15
22
 
16
23
  it "raises a CookbookNotFound error" do
17
24
  lambda {
@@ -21,7 +28,7 @@ module Berkshelf
21
28
  end
22
29
 
23
30
  context "given a path that does not contain a cookbook" do
24
- subject { CookbookSource::PathLocation.new("doesnot_exist", :path => fixtures_path) }
31
+ subject { CookbookSource::PathLocation.new("doesnot_exist", complacent_constraint, path: fixtures_path) }
25
32
 
26
33
  it "raises a CookbookNotFound error" do
27
34
  lambda {
@@ -29,6 +36,16 @@ module Berkshelf
29
36
  }.should raise_error(CookbookNotFound)
30
37
  end
31
38
  end
39
+
40
+ context "given the content at path does not satisfy the version constraint" do
41
+ subject { CookbookSource::PathLocation.new("nginx", double('constraint', include?: false), path: path) }
42
+
43
+ it "raises a ConstraintNotSatisfied error" do
44
+ lambda {
45
+ subject.download(double('path'))
46
+ }.should raise_error(ConstraintNotSatisfied)
47
+ end
48
+ end
32
49
  end
33
50
  end
34
51
  end
@@ -3,8 +3,8 @@ require 'spec_helper'
3
3
  module Berkshelf
4
4
  describe CookbookSource::SiteLocation do
5
5
  describe "ClassMethods" do
6
- subject { CookbookSource::SiteLocation }
7
6
  let(:constraint) { DepSelector::VersionConstraint.new("~> 0.101.2") }
7
+ subject { CookbookSource::SiteLocation }
8
8
  let(:versions) do
9
9
  {
10
10
  DepSelector::Version.new("0.101.2") => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_2",
@@ -39,19 +39,28 @@ module Berkshelf
39
39
  end
40
40
  end
41
41
 
42
- subject { CookbookSource::SiteLocation.new("nginx") }
42
+ let(:complacent_constraint) { double('comp-vconstraint', include?: true) }
43
+ subject { CookbookSource::SiteLocation.new("nginx", complacent_constraint) }
43
44
 
44
45
  describe "#download" do
45
- it "returns the path to the cookbook" do
46
+ it "returns a CachedCookbook" do
46
47
  result = subject.download(tmp_path)
47
48
  name = subject.name
48
49
  ver, uri = subject.latest_version
49
50
 
50
- result.should eql(tmp_path.join("#{name}-#{ver}").to_s)
51
+ result.should be_a(CachedCookbook)
52
+ end
53
+
54
+ it "sets the downloaded status to true" do
55
+ subject.download(tmp_path)
56
+
57
+ subject.should be_downloaded
51
58
  end
52
59
 
53
- context "when no version constraint is specified" do
54
- it "the latest version of the cookbook is downloaded to the given destination" do
60
+ context "given a wildcard '>= 0.0.0' version constraint is specified" do
61
+ subject { CookbookSource::SiteLocation.new("nginx", DepSelector::VersionConstraint.new(">= 0.0.0")) }
62
+
63
+ it "downloads the latest version of the cookbook to the given destination" do
55
64
  subject.download(tmp_path)
56
65
  name = subject.name
57
66
  ver, uri = subject.latest_version
@@ -64,8 +73,8 @@ module Berkshelf
64
73
  end
65
74
  end
66
75
 
67
- context "given an explicit version_constraint" do
68
- subject { CookbookSource::SiteLocation.new("nginx", :version_constraint => DepSelector::VersionConstraint.new("= 0.101.2")) }
76
+ context "given an exact match version constraint" do
77
+ subject { CookbookSource::SiteLocation.new("nginx", DepSelector::VersionConstraint.new("= 0.101.2")) }
69
78
 
70
79
  it "downloads the cookbook with the version matching the version_constraint to the given destination" do
71
80
  subject.download(tmp_path)
@@ -79,8 +88,8 @@ module Berkshelf
79
88
  end
80
89
  end
81
90
 
82
- context "given a more broad version_constraint" do
83
- subject { CookbookSource::SiteLocation.new("nginx", :version_constraint => DepSelector::VersionConstraint.new("~> 0.99.0")) }
91
+ context "given a more broad version constraint" do
92
+ subject { CookbookSource::SiteLocation.new("nginx", DepSelector::VersionConstraint.new("~> 0.99.0")) }
84
93
 
85
94
  it "downloads the best matching cookbook version for the constraint to the given destination" do
86
95
  subject.download(tmp_path)
@@ -95,7 +104,12 @@ module Berkshelf
95
104
  end
96
105
 
97
106
  context "given an explicit :site location key" do
98
- subject { CookbookSource::SiteLocation.new("nginx", :site => "http://cookbooks.opscode.com/api/v1/cookbooks") }
107
+ subject do
108
+ CookbookSource::SiteLocation.new("nginx",
109
+ complacent_constraint,
110
+ site: "http://cookbooks.opscode.com/api/v1/cookbooks"
111
+ )
112
+ end
99
113
 
100
114
  it "downloads the cookbook to the given destination" do
101
115
  subject.download(tmp_path)
@@ -111,12 +125,17 @@ module Berkshelf
111
125
  end
112
126
 
113
127
  context "given a cookbook that does not exist on the specified site" do
114
- subject { CookbookSource::SiteLocation.new("nowaythis_exists", :site => "http://cookbooks.opscode.com/api/v1/cookbooks") }
128
+ subject do
129
+ CookbookSource::SiteLocation.new("nowaythis_exists",
130
+ complacent_constraint,
131
+ site: "http://cookbooks.opscode.com/api/v1/cookbooks"
132
+ )
133
+ end
115
134
 
116
135
  it "raises a CookbookNotFound error" do
117
136
  lambda {
118
137
  subject.download(tmp_path)
119
- }.should raise_error(CookbookNotFound)
138
+ }.should raise_error(CookbookNotFound)
120
139
  end
121
140
  end
122
141
  end
@@ -45,15 +45,26 @@ module Berkshelf
45
45
  end
46
46
 
47
47
  context "given a location key :path" do
48
- let(:path) { "/Path/To/Cookbook" }
49
- let(:source) { subject.new(cookbook_name, :path => path) }
48
+ context "given a value for path that contains a cookbook" do
49
+ let(:path) { fixtures_path.join("cookbooks", "example_cookbook").to_s }
50
50
 
51
- it "initializes a PathLocation for location" do
52
- source.location.should be_a(subject::PathLocation)
51
+ it "initializes a PathLocation for location" do
52
+ subject.new(cookbook_name, path: path).location.should be_a(subject::PathLocation)
53
+ end
54
+
55
+ it "points to the specified path" do
56
+ subject.new(cookbook_name, path: path).location.path.should eql(path)
57
+ end
53
58
  end
54
59
 
55
- it "points to the specified path" do
56
- source.location.path.should eql(path)
60
+ context "given a value for path that does not contain a cookbook" do
61
+ let(:path) { "/does/not/exist" }
62
+
63
+ it "rasies Berkshelf::CookbookNotFound" do
64
+ lambda {
65
+ subject.new(cookbook_name, path: path)
66
+ }.should raise_error(Berkshelf::CookbookNotFound)
67
+ end
57
68
  end
58
69
  end
59
70
 
@@ -133,16 +144,10 @@ module Berkshelf
133
144
 
134
145
  describe "#download" do
135
146
  context "when download is successful" do
136
- it "writes a value to local_path" do
147
+ it "sets a CachedCookbook to the cached_cookbook attr" do
137
148
  subject.download(tmp_path)
138
149
 
139
- subject.local_path.should_not be_nil
140
- end
141
-
142
- it "writes a value to local_version" do
143
- subject.download(tmp_path)
144
-
145
- subject.local_version.should_not be_nil
150
+ subject.cached_cookbook.should be_a(Berkshelf::CachedCookbook)
146
151
  end
147
152
 
148
153
  it "returns an array containing the symbol :ok and the local_path" do
@@ -150,7 +155,7 @@ module Berkshelf
150
155
 
151
156
  result.should be_a(Array)
152
157
  result[0].should eql(:ok)
153
- result[1].should eql(subject.local_path)
158
+ result[1].should eql(subject.cached_cookbook)
154
159
  end
155
160
  end
156
161
 
@@ -168,26 +173,12 @@ module Berkshelf
168
173
  end
169
174
  end
170
175
 
171
- describe "#metadata" do
172
- it "should return the metadata of a CookbookSource that has been downloaded" do
173
- subject.download(tmp_path)
174
-
175
- subject.metadata.should be_a(Chef::Cookbook::Metadata)
176
- end
177
-
178
- it "should return nil if the CookbookSource has not been downloaded" do
179
- subject.metadata.should be_nil
180
- end
181
- end
182
-
183
176
  describe "#downloaded?" do
184
- let(:path) { fixtures_path.join("cookbooks", "example_cookbook").to_s }
185
- subject { CookbookSource.new("example_cookbook", :path => path) }
186
-
187
- it "returns true if the local_path has been set" do
188
- subject.stub(:local_path) { path }
177
+ subject{ CookbookSource.new("nginx", ">= 1.0.1") }
189
178
 
190
- subject.downloaded?.should be_true
179
+ it "delegates the message ':downloaded?' to the location" do
180
+ subject.location.should_receive(:downloaded?)
181
+ subject.downloaded?
191
182
  end
192
183
  end
193
184
  end
@@ -34,15 +34,42 @@ module Berkshelf
34
34
  end
35
35
  end
36
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)
37
+ describe "#satisfy" do
38
+ let(:name) { "nginx" }
39
+ let(:version) { DepSelector::Version.new("0.101.4") }
40
+ let(:constraint) { DepSelector::VersionConstraint.new("~> 0.101.2") }
41
+ let(:cached_cb) { double('cached-cb', name: name, version: version) }
42
+ let(:cached_two) { double('cached-two', name: "mysql", version: DepSelector::Version.new("1.2.6")) }
43
+
44
+ before(:each) do
45
+ subject.stub(:cookbooks).and_return([cached_cb, cached_two])
46
+ end
47
+
48
+ it "gets and returns the the CachedCookbook best matching the name and constraint" do
49
+ subject.should_receive(:cookbook).with(name, version).and_return(cached_cb)
40
50
 
41
- subject.downloaded?("nginx", "0.101.2").should be_true
51
+ subject.satisfy(name, constraint).should eql(cached_cb)
42
52
  end
43
53
 
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
54
+ context "when there are no cookbooks in the cookbook store" do
55
+ before(:each) { subject.stub(:cookbooks).and_return([]) }
56
+
57
+ it "returns nil" do
58
+ subject.satisfy(name, constraint).should be_nil
59
+ end
60
+ end
61
+
62
+ context "when there is no matching cookbook for the given name and constraint" do
63
+ let(:version) { DepSelector::Version.new("1.0.0") }
64
+ let(:constraint) { DepSelector::VersionConstraint.new("= 0.0.1") }
65
+
66
+ before(:each) do
67
+ subject.stub(:cookbooks).and_return([ double('badcache', name: 'none', version: version) ])
68
+ end
69
+
70
+ it "returns nil if there is no matching cookbook for the name and constraint" do
71
+ subject.satisfy(name, constraint).should be_nil
72
+ end
46
73
  end
47
74
  end
48
75
 
@@ -59,13 +86,26 @@ module Berkshelf
59
86
  end
60
87
 
61
88
  describe "#cookbooks" do
62
- it "returns a list of CachedCookbooks" do
89
+ before(:each) do
63
90
  CookbookSource.new("nginx", "0.101.2").download(subject.storage_path)
91
+ CookbookSource.new("mysql", "1.2.6").download(subject.storage_path)
92
+ end
64
93
 
94
+ it "returns a list of CachedCookbooks" do
65
95
  subject.cookbooks.each do |cb|
66
96
  cb.should be_a(CachedCookbook)
67
97
  end
68
98
  end
99
+
100
+ it "return an instance of CachedCookbook for every downloaded cookbook" do
101
+ subject.cookbooks.should have(2).items
102
+ end
103
+
104
+ context "given a value for the filter parameter" do
105
+ it "returns only the CachedCookbooks whose name match the filter" do
106
+ subject.cookbooks("mysql").should have(1).item
107
+ end
108
+ end
69
109
  end
70
110
  end
71
111
  end
@@ -5,24 +5,21 @@ module Berkshelf
5
5
  describe "ClassMethods" do
6
6
  subject { Git }
7
7
 
8
- describe "#find_git" do
8
+ describe "::find_git" do
9
9
  it "should find git" do
10
10
  subject.find_git.should_not be_nil
11
11
  end
12
12
 
13
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
14
+ ENV.should_receive(:[]).with("PATH").and_return(String.new)
15
+
16
+ lambda {
17
+ subject.find_git
18
+ }.should raise_error(GitNotFound)
22
19
  end
23
20
  end
24
21
 
25
- describe "#clone" do
22
+ describe "::clone" do
26
23
  let(:target) { tmp_path.join("nginx") }
27
24
 
28
25
  it "clones the repository to the target path" do
@@ -33,7 +30,7 @@ module Berkshelf
33
30
  end
34
31
  end
35
32
 
36
- describe "#checkout" do
33
+ describe "::checkout" do
37
34
  let(:repo_path) { tmp_path.join("nginx") }
38
35
  let(:repo) { subject.clone("git://github.com/opscode-cookbooks/nginx.git", repo_path) }
39
36
  let(:tag) { "0.101.2" }
@@ -47,7 +44,7 @@ module Berkshelf
47
44
  end
48
45
  end
49
46
 
50
- describe "#rev_parse" do
47
+ describe "::rev_parse" do
51
48
  let(:repo_path) { tmp_path.join("nginx") }
52
49
  before(:each) do
53
50
  subject.clone("git://github.com/opscode-cookbooks/nginx.git", repo_path)
@@ -58,6 +55,94 @@ module Berkshelf
58
55
  subject.rev_parse(repo_path).should eql("0e4887d9eef8cb83972f974a85890983c8204c3b")
59
56
  end
60
57
  end
58
+
59
+ let(:readonly_uri) { "git://github.com/reset/thor-foodcritic.git" }
60
+ let(:https_uri) { "https://github.com/reset/solve.git" }
61
+ let(:ssh_uri) { "git@github.com:reset/solve.git" }
62
+ let(:http_uri) { "http://github.com/reset/solve.git" }
63
+ let(:invalid_uri) { "/something/on/disk" }
64
+
65
+ describe "::validate_uri" do
66
+ context "given a valid Git read-only URI" do
67
+ it "returns true" do
68
+ subject.validate_uri(readonly_uri)
69
+ end
70
+ end
71
+
72
+ context "given a valid Git HTTPS URI" do
73
+ it "returns true" do
74
+ subject.validate_uri(https_uri)
75
+ end
76
+ end
77
+
78
+ context "given a valid Git SSH URI" do
79
+ it "returns true" do
80
+ subject.validate_uri(ssh_uri)
81
+ end
82
+ end
83
+
84
+ context "given an invalid URI" do
85
+ it "returns false" do
86
+ subject.validate_uri(invalid_uri)
87
+ end
88
+ end
89
+
90
+ context "given a HTTP URI" do
91
+ it "returns false" do
92
+ subject.validate_uri(http_uri)
93
+ end
94
+ end
95
+
96
+ context "given an integer" do
97
+ it "returns false" do
98
+ subject.validate_uri(123)
99
+ end
100
+ end
101
+ end
102
+
103
+ describe "::validate_uri!" do
104
+ context "given a valid Git read-only URI" do
105
+ it "returns true" do
106
+ subject.validate_uri!(readonly_uri)
107
+ end
108
+ end
109
+
110
+ context "given a valid Git HTTPS URI" do
111
+ it "returns true" do
112
+ subject.validate_uri!(https_uri)
113
+ end
114
+ end
115
+
116
+ context "given a valid Git SSH URI" do
117
+ it "returns true" do
118
+ subject.validate_uri!(ssh_uri)
119
+ end
120
+ end
121
+
122
+ context "given an invalid URI" do
123
+ it "raises InvalidGitURI" do
124
+ lambda {
125
+ subject.validate_uri!(invalid_uri)
126
+ }.should raise_error(InvalidGitURI)
127
+ end
128
+ end
129
+
130
+ context "given a HTTP URI" do
131
+ it "raises InvalidGitURI" do
132
+ lambda {
133
+ subject.validate_uri!(http_uri)
134
+ }.should raise_error(InvalidGitURI)
135
+ end
136
+ end
137
+
138
+ context "given an integer" do
139
+ it "raises InvalidGitURI" do
140
+ lambda {
141
+ subject.validate_uri!(123)
142
+ }.should raise_error(InvalidGitURI)
143
+ end
144
+ end
145
+ end
61
146
  end
62
147
  end
63
148
  end