cookbook-omnifetch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ module CookbookOmnifetch
2
+ class GithubLocation < GitLocation
3
+ def initialize(dependency, options = {})
4
+ options[:git] = "git://github.com/#{options.delete(:github)}.git"
5
+ super
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,46 @@
1
+ require 'cookbook-omnifetch/exceptions'
2
+
3
+ module CookbookOmnifetch
4
+
5
+ class MissingConfiguration < OmnifetchError; end
6
+
7
+ class NullValue; end
8
+
9
+ class Integration
10
+
11
+ def self.configurables
12
+ @configurables ||= []
13
+ end
14
+
15
+ def self.configurable(name)
16
+ configurables << name
17
+
18
+ attr_writer name
19
+
20
+ define_method(name) do
21
+ value = instance_variable_get("@#{name}".to_sym)
22
+ case value
23
+ when NullValue
24
+ raise MissingConfiguration, "`#{name}` is not configured"
25
+ when Proc
26
+ value.call
27
+ else
28
+ value
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ configurable :cache_path
35
+ configurable :storage_path
36
+ configurable :shell_out_class
37
+ configurable :cached_cookbook_class
38
+
39
+ def initialize
40
+ self.class.configurables.each do |configurable|
41
+ instance_variable_set("@#{configurable}".to_sym, NullValue.new)
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,86 @@
1
+ require 'cookbook-omnifetch/base'
2
+
3
+ module CookbookOmnifetch
4
+ class PathLocation < BaseLocation
5
+ # Technically path locations are always installed, but this method
6
+ # intentionally returns +false+ to force validation of the cookbook at the
7
+ # path.
8
+ #
9
+ # @see BaseLocation#installed?
10
+ def installed?
11
+ false
12
+ end
13
+
14
+ # The installation for a path location is actually just a noop
15
+ #
16
+ # @see BaseLocation#install
17
+ def install
18
+ validate_cached!(expanded_path)
19
+ end
20
+
21
+ # @see BaseLocation#cached_cookbook
22
+ def cached_cookbook
23
+ @cached_cookbook ||= CookbookOmnifetch.cached_cookbook_class.from_path(expanded_path)
24
+ end
25
+
26
+ # Returns true if the location is a metadata location. By default, no
27
+ # locations are the metadata location.
28
+ #
29
+ # @return [Boolean]
30
+ def metadata?
31
+ !!options[:metadata]
32
+ end
33
+
34
+ def install_path
35
+ relative_path
36
+ end
37
+
38
+ # Return this PathLocation's path relative to the associated Berksfile. It
39
+ # is actually the path reative to the associated Berksfile's parent
40
+ # directory.
41
+ #
42
+ # @return [Pathname]
43
+ # the relative path relative to the target
44
+ def relative_path
45
+ # TODO: this requires Berkshelf::Dependency to provide a delegate (ish) method that does
46
+ #
47
+ # def relative_paths_root
48
+ # File.dirname(berksfile.filepath)
49
+ # end
50
+ @relative_path ||= expanded_path.relative_path_from(Pathname.new(dependency.relative_paths_root))
51
+ end
52
+
53
+ # The fully expanded path of this cookbook on disk, relative to the
54
+ # Berksfile.
55
+ #
56
+ # @return [Pathname]
57
+ def expanded_path
58
+ # TODO: this requires Berkshelf::Dependency to provide a delegate (ish) method that does
59
+ #
60
+ # def relative_paths_root
61
+ # File.dirname(berksfile.filepath)
62
+ # end
63
+ @expanded_path ||= Pathname.new File.expand_path(options[:path], dependency.relative_paths_root)
64
+ end
65
+
66
+ def ==(other)
67
+ other.is_a?(PathLocation) &&
68
+ other.metadata? == metadata? &&
69
+ other.relative_path == relative_path
70
+ end
71
+
72
+ def to_lock
73
+ out = " path: #{relative_path}\n"
74
+ out << " metadata: true\n" if metadata?
75
+ out
76
+ end
77
+
78
+ def to_s
79
+ "source at #{relative_path}"
80
+ end
81
+
82
+ def inspect
83
+ "#<CookbookOmnifetch::PathLocation metadata: #{metadata?}, path: #{relative_path}>"
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ module CookbookOmnifetch
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,12 @@
1
+ Description
2
+ ===========
3
+
4
+ Requirements
5
+ ============
6
+
7
+ Attributes
8
+ ==========
9
+
10
+ Usage
11
+ =====
12
+
@@ -0,0 +1,3 @@
1
+ name 'example_cookbook'
2
+ maintainer 'Berkshelf Core'
3
+ version '0.5.0'
@@ -0,0 +1,8 @@
1
+ #
2
+ # Cookbook Name:: example_cookbook
3
+ # Recipe:: default
4
+ #
5
+ # Copyright 2012, YOUR_COMPANY_NAME
6
+ #
7
+ # All rights reserved - Do Not Redistribute
8
+ #
@@ -0,0 +1,2 @@
1
+ .kitchen/
2
+ .kitchen.local.yml
@@ -0,0 +1,26 @@
1
+ ---
2
+ driver_plugin: vagrant
3
+
4
+ platforms:
5
+ - name: ubuntu-12.04
6
+ driver_config:
7
+ box: canonical-ubuntu-12.04
8
+ box_url: http://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box
9
+ require_chef_omnibus: true
10
+ - name: ubuntu-10.04
11
+ driver_config:
12
+ box: opscode-ubuntu-10.04
13
+ box_url: http://opscode-vm.s3.amazonaws.com/vagrant/opscode_ubuntu-10.04_chef-11.2.0.box
14
+ - name: centos-6.3
15
+ driver_config:
16
+ box: opscode-centos-6.3
17
+ box_url: http://opscode-vm.s3.amazonaws.com/vagrant/opscode_centos-6.3_chef-11.2.0.box
18
+ - name: centos-5.8
19
+ driver_config:
20
+ box: opscode-centos-5.8
21
+ box_url: http://opscode-vm.s3.amazonaws.com/vagrant/opscode_centos-5.8_chef-11.2.0.box
22
+
23
+ suites:
24
+ - name: default
25
+ run_list: []
26
+ attributes: {}
@@ -0,0 +1 @@
1
+ site :opscode
@@ -0,0 +1,3 @@
1
+ DEPENDENCIES
2
+
3
+ GRAPH
@@ -0,0 +1,12 @@
1
+ Description
2
+ ===========
3
+
4
+ Requirements
5
+ ============
6
+
7
+ Attributes
8
+ ==========
9
+
10
+ Usage
11
+ =====
12
+
@@ -0,0 +1,3 @@
1
+ name 'example_cookbook'
2
+ maintainer 'Berkshelf Core'
3
+ version '0.5.0'
@@ -0,0 +1,8 @@
1
+ #
2
+ # Cookbook Name:: example_cookbook
3
+ # Recipe:: default
4
+ #
5
+ # Copyright 2012, YOUR_COMPANY_NAME
6
+ #
7
+ # All rights reserved - Do Not Redistribute
8
+ #
@@ -0,0 +1,44 @@
1
+ require 'cookbook-omnifetch'
2
+
3
+ module Fixtures
4
+
5
+ def fixtures_path
6
+ spec_root.join('fixtures')
7
+ end
8
+
9
+ def spec_root
10
+ Pathname.new(File.expand_path(File.dirname(__FILE__)))
11
+ end
12
+
13
+ end
14
+
15
+ module MockShellOut; end
16
+ module MockCachedCookbook; end
17
+
18
+ RSpec.configure do |config|
19
+
20
+ config.raise_errors_for_deprecations!
21
+
22
+ config.include Fixtures
23
+
24
+ config.expect_with :rspec do |c|
25
+ c.syntax = [:expect]
26
+ end
27
+ config.mock_with :rspec do |c|
28
+ c.syntax = [:expect, :should]
29
+ end
30
+
31
+ config.filter_run :focus => true
32
+
33
+ config.run_all_when_everything_filtered = true
34
+
35
+ config.before(:suite) do
36
+ CookbookOmnifetch.configure do |c|
37
+ c.cache_path = File.expand_path('~/.berkshelf')
38
+ c.storage_path = Pathname.new(File.expand_path('~/.berkshelf/cookbooks'))
39
+ c.shell_out_class = MockShellOut
40
+ c.cached_cookbook_class = MockCachedCookbook
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'cookbook-omnifetch/artifactserver'
3
+
4
+ module CookbookOmnifetch
5
+ describe ArtifactserverLocation do
6
+
7
+ let(:cookbook_name) { "nginx" }
8
+
9
+ let(:cookbook_version) { "1.5.23" }
10
+
11
+ let(:constraint) { double("Constraint") }
12
+
13
+ let(:dependency) { double("Dependency", name: cookbook_name, constraint: constraint) }
14
+
15
+ let(:options) { {artifactserver: "https://cookbooks.opscode.com/api/v1", version: cookbook_version } }
16
+
17
+ subject(:public_repo_location) { ArtifactserverLocation.new(dependency, options) }
18
+
19
+ it "has a URI" do
20
+ expect(public_repo_location.uri).to eq("https://cookbooks.opscode.com/api/v1")
21
+ end
22
+
23
+ it "has a repo host" do
24
+ expect(public_repo_location.repo_host).to eq("cookbooks.opscode.com")
25
+ end
26
+
27
+ it "has an exact version" do
28
+ expect(public_repo_location.cookbook_version).to eq("1.5.23")
29
+ end
30
+
31
+ it "has a cache key containing the site URI and version" do
32
+ expect(public_repo_location.cache_key).to eq("nginx-1.5.23-cookbooks.opscode.com")
33
+ end
34
+
35
+ it "sets the install location as the cache path plus cache key" do
36
+ expected_install_path = Pathname.new('~/.berkshelf/cookbooks').expand_path.join("nginx-1.5.23-cookbooks.opscode.com")
37
+ expect(public_repo_location.install_path).to eq(expected_install_path)
38
+ end
39
+
40
+ it "considers the cookbook installed if it exists in the main cache" do
41
+ expect(public_repo_location.install_path).to receive(:exist?).and_return(true)
42
+ expect(public_repo_location.installed?).to be true
43
+ end
44
+
45
+ it "considers the cookbook not installed if it doesn't exist in the main cache" do
46
+ expect(public_repo_location.install_path).to receive(:exist?).and_return(false)
47
+ expect(public_repo_location.installed?).to be false
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'cookbook-omnifetch/base'
3
+
4
+ module CookbookOmnifetch
5
+ describe BaseLocation do
6
+ let(:constraint) { double('constraint') }
7
+ let(:dependency) { double('dependency', name: 'cookbook', version_constraint: constraint) }
8
+
9
+ subject { described_class.new(dependency) }
10
+
11
+ describe '#installed?' do
12
+ it 'is an abstract function' do
13
+ expect { subject.installed? }.to raise_error(AbstractFunction)
14
+ end
15
+ end
16
+
17
+ describe '#install' do
18
+ it 'is an abstract function' do
19
+ expect { subject.install }.to raise_error(AbstractFunction)
20
+ end
21
+ end
22
+
23
+ describe '#cached_cookbook' do
24
+ it 'is an abstract function' do
25
+ expect { subject.cached_cookbook }.to raise_error(AbstractFunction)
26
+ end
27
+ end
28
+
29
+ describe '#to_lock' do
30
+ it 'is an abstract function' do
31
+ expect { subject.to_lock }.to raise_error(AbstractFunction)
32
+ end
33
+ end
34
+
35
+ describe '#validate_cached!' do
36
+ context 'when the path is not a cookbook' do
37
+ before { CookbookOmnifetch.stub(:cookbook?).and_return(false) }
38
+
39
+ it 'raises an error' do
40
+ expect {
41
+ subject.validate_cached!('/foo/bar')
42
+ }.to raise_error(NotACookbook)
43
+ end
44
+ end
45
+
46
+ context 'when the path is a cookbook' do
47
+ let(:cookbook) do
48
+ double('cookbook',
49
+ cookbook_name: 'cookbook',
50
+ version: '0.1.0',
51
+ )
52
+ end
53
+
54
+ before do
55
+ CookbookOmnifetch.stub(:cookbook?).and_return(true)
56
+ MockCachedCookbook.stub(:from_path).and_return(cookbook)
57
+ end
58
+
59
+ it 'raises an error if the constraint does not satisfy' do
60
+ constraint.stub(:satisfies?).with('0.1.0').and_return(false)
61
+ expect {
62
+ subject.validate_cached!(cookbook)
63
+ }.to raise_error(CookbookValidationFailure)
64
+ end
65
+
66
+ it 'raises an error if the names do not match' do
67
+ constraint.stub(:satisfies?).with('0.1.0').and_return(true)
68
+ cookbook.stub(:cookbook_name).and_return('different_name')
69
+ expect {
70
+ subject.validate_cached!(cookbook)
71
+ }.to raise_error(MismatchedCookbookName)
72
+ end
73
+
74
+ it 'returns true when the validation succeeds' do
75
+ constraint.stub(:satisfies?).with('0.1.0').and_return(true)
76
+ expect(subject.validate_cached!(cookbook)).to be true
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,258 @@
1
+ require 'spec_helper'
2
+ require 'cookbook-omnifetch/git'
3
+
4
+ module CookbookOmnifetch
5
+ describe GitLocation do
6
+ let(:dependency) { double(name: 'bacon') }
7
+
8
+ subject do
9
+ described_class.new(dependency, git: 'https://repo.com', branch: 'ham',
10
+ tag: 'v1.2.3', ref: 'abc123', revision: 'defjkl123456', rel: 'hi')
11
+ end
12
+
13
+ describe '.initialize' do
14
+ it 'sets the uri' do
15
+ instance = described_class.new(dependency, git: 'https://repo.com')
16
+ expect(instance.uri).to eq('https://repo.com')
17
+ end
18
+
19
+ it 'sets the branch' do
20
+ instance = described_class.new(dependency,
21
+ git: 'https://repo.com', branch: 'magic_new_feature')
22
+ expect(instance.branch).to eq('magic_new_feature')
23
+ end
24
+
25
+ it 'sets the tag' do
26
+ instance = described_class.new(dependency,
27
+ git: 'https://repo.com', tag: 'v1.2.3')
28
+ expect(instance.tag).to eq('v1.2.3')
29
+ end
30
+
31
+ it 'adds the ref' do
32
+ instance = described_class.new(dependency,
33
+ git: 'https://repo.com', ref: 'abc123')
34
+ expect(instance.ref).to eq('abc123')
35
+ end
36
+
37
+ it 'sets the revision' do
38
+ instance = described_class.new(dependency,
39
+ git: 'https://repo.com', revision: 'abcde12345')
40
+ expect(instance.revision).to eq('abcde12345')
41
+ end
42
+
43
+ it 'sets the rel' do
44
+ instance = described_class.new(dependency,
45
+ git: 'https://repo.com', rel: 'internal/path')
46
+ expect(instance.rel).to eq('internal/path')
47
+ end
48
+
49
+ context 'rev_parse' do
50
+ def rev_parse(instance)
51
+ instance.instance_variable_get(:@rev_parse)
52
+ end
53
+
54
+ it 'uses the :ref option with priority' do
55
+ instance = described_class.new(dependency,
56
+ git: 'https://repo.com', ref: 'abc123', branch: 'magic_new_feature')
57
+ expect(rev_parse(instance)).to eq('abc123')
58
+ end
59
+
60
+ it 'uses the :branch option with priority' do
61
+ instance = described_class.new(dependency,
62
+ git: 'https://repo.com', branch: 'magic_new_feature', tag: 'v1.2.3')
63
+ expect(rev_parse(instance)).to eq('magic_new_feature')
64
+ end
65
+
66
+ it 'uses the :tag option' do
67
+ instance = described_class.new(dependency,
68
+ git: 'https://repo.com', tag: 'v1.2.3')
69
+ expect(rev_parse(instance)).to eq('v1.2.3')
70
+ end
71
+
72
+ it 'uses "master" when none is given' do
73
+ instance = described_class.new(dependency, git: 'https://repo.com')
74
+ expect(rev_parse(instance)).to eq('master')
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "#cache_key" do
80
+
81
+ it "concatenates the name and revision" do
82
+ subject.stub(:revision).and_return("abcdef123456")
83
+ expect(subject.cache_key).to eq("bacon-abcdef123456")
84
+ end
85
+ end
86
+
87
+ describe '#installed?' do
88
+ it 'returns false when there is no revision' do
89
+ subject.stub(:revision).and_return(nil)
90
+ expect(subject.installed?).to be false
91
+ end
92
+
93
+ it 'returns false when the install_path does not exist' do
94
+ subject.stub(:revision).and_return('abcd1234')
95
+ subject.stub(:install_path).and_return(double(exist?: false))
96
+ expect(subject.installed?).to be false
97
+ end
98
+
99
+ it 'returns true when the location is installed' do
100
+ subject.stub(:revision).and_return('abcd1234')
101
+ subject.stub(:install_path).and_return(double(exist?: true))
102
+ expect(subject.installed?).to be true
103
+ end
104
+ end
105
+
106
+ describe '#install' do
107
+ before do
108
+ File.stub(:chmod)
109
+ FileUtils.stub(:cp_r)
110
+ subject.stub(:validate_cached!)
111
+ subject.stub(:git)
112
+ end
113
+
114
+ context 'when the repository is cached' do
115
+ it 'pulls a new version' do
116
+ Dir.stub(:chdir) { |args, &b| b.call } # Force eval the chdir block
117
+
118
+ subject.stub(:cached?).and_return(true)
119
+ expect(subject).to receive(:git).with(
120
+ 'fetch --force --tags https://repo.com "refs/heads/*:refs/heads/*"'
121
+ )
122
+ subject.install
123
+ end
124
+ end
125
+
126
+ context 'when the revision is not cached' do
127
+ it 'clones the repository' do
128
+ Dir.stub(:chdir) { |args, &b| b.call } # Force eval the chdir block
129
+
130
+ cache_path = subject.send(:cache_path)
131
+ subject.stub(:cached?).and_return(false)
132
+ expect(subject).to receive(:git).with(
133
+ %|clone https://repo.com "#{cache_path}" --bare --no-hardlinks|
134
+ )
135
+ subject.install
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '#cached_cookbook' do
141
+ it 'returns nil if the cookbook is not installed' do
142
+ subject.stub(:installed?).and_return(false)
143
+ expect(subject.cached_cookbook).to be_nil
144
+ end
145
+
146
+ it 'returns the cookbook at the install_path' do
147
+ subject.stub(:installed?).and_return(true)
148
+ MockCachedCookbook.stub(:from_path)
149
+
150
+ expect(MockCachedCookbook).to receive(:from_path).once
151
+ subject.cached_cookbook
152
+ end
153
+ end
154
+
155
+ describe '#==' do
156
+ let(:other) { subject.dup }
157
+
158
+ it 'returns true when everything matches' do
159
+ expect(subject).to eq(other)
160
+ end
161
+
162
+ it 'returns false when the other location is not an GitLocation' do
163
+ other.stub(:is_a?).and_return(false)
164
+ expect(subject).to_not eq(other)
165
+ end
166
+
167
+ it 'returns false when the uri is different' do
168
+ other.stub(:uri).and_return('different')
169
+ expect(subject).to_not eq(other)
170
+ end
171
+
172
+ it 'returns false when the branch is different' do
173
+ other.stub(:branch).and_return('different')
174
+ expect(subject).to_not eq(other)
175
+ end
176
+
177
+ it 'returns false when the tag is different' do
178
+ other.stub(:tag).and_return('different')
179
+ expect(subject).to_not eq(other)
180
+ end
181
+
182
+ it 'returns false when the ref is different' do
183
+ other.stub(:ref).and_return('different')
184
+ expect(subject).to_not eq(other)
185
+ end
186
+
187
+ it 'returns false when the rel is different' do
188
+ other.stub(:rel).and_return('different')
189
+ expect(subject).to_not eq(other)
190
+ end
191
+ end
192
+
193
+ describe '#to_s' do
194
+ it 'prefers the tag' do
195
+ expect(subject.to_s).to eq('https://repo.com (at v1.2.3/hi)')
196
+ end
197
+
198
+ it 'prefers the branch' do
199
+ subject.stub(:tag).and_return(nil)
200
+ expect(subject.to_s).to eq('https://repo.com (at ham/hi)')
201
+ end
202
+
203
+ it 'falls back to the ref' do
204
+ subject.stub(:tag).and_return(nil)
205
+ subject.stub(:branch).and_return(nil)
206
+ expect(subject.to_s).to eq('https://repo.com (at abc123/hi)')
207
+ end
208
+
209
+ it 'does not use the rel if missing' do
210
+ subject.stub(:rel).and_return(nil)
211
+ expect(subject.to_s).to eq('https://repo.com (at v1.2.3)')
212
+ end
213
+ end
214
+
215
+ describe '#to_lock' do
216
+ it 'includes all the information' do
217
+ expect(subject.to_lock).to eq <<-EOH.gsub(/^ {8}/, '')
218
+ git: https://repo.com
219
+ revision: defjkl123456
220
+ ref: abc123
221
+ branch: ham
222
+ tag: v1.2.3
223
+ rel: hi
224
+ EOH
225
+ end
226
+
227
+ it 'does not include the branch if missing' do
228
+ subject.stub(:branch).and_return(nil)
229
+ expect(subject.to_lock).to_not include('branch')
230
+ end
231
+
232
+ it 'does not include the tag if missing' do
233
+ subject.stub(:tag).and_return(nil)
234
+ expect(subject.to_lock).to_not include('tag')
235
+ end
236
+
237
+ it 'does not include the rel if missing' do
238
+ subject.stub(:rel).and_return(nil)
239
+ expect(subject.to_lock).to_not include('rel')
240
+ end
241
+ end
242
+
243
+ describe '#git' do
244
+ before { described_class.send(:public, :git) }
245
+
246
+ it 'raises an error if Git is not installed' do
247
+ CookbookOmnifetch.stub(:which).and_return(false)
248
+ expect { subject.git('foo') }.to raise_error(GitNotInstalled)
249
+ end
250
+
251
+ it 'raises an error if the command fails' do
252
+ shell_out = double('shell_out', success?: false, stderr: nil)
253
+ MockShellOut.stub(:shell_out).and_return(shell_out)
254
+ expect { subject.git('foo') }.to raise_error(GitCommandError)
255
+ end
256
+ end
257
+ end
258
+ end