berkshelf 0.3.3 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
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