berkshelf 2.0.3 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG.md +7 -0
  2. data/Gemfile +2 -0
  3. data/berkshelf.gemspec +5 -4
  4. data/features/berksfile.feature +56 -0
  5. data/features/install_command.feature +99 -13
  6. data/features/json_formatter.feature +1 -1
  7. data/features/lockfile.feature +50 -23
  8. data/features/step_definitions/filesystem_steps.rb +14 -1
  9. data/features/step_definitions/json_steps.rb +1 -1
  10. data/features/update_command.feature +2 -2
  11. data/features/upload_command.feature +0 -1
  12. data/lib/berkshelf.rb +1 -15
  13. data/lib/berkshelf/berksfile.rb +27 -21
  14. data/lib/berkshelf/cli.rb +2 -3
  15. data/lib/berkshelf/commands/test_command.rb +4 -2
  16. data/lib/berkshelf/community_rest.rb +6 -0
  17. data/lib/berkshelf/cookbook_source.rb +15 -37
  18. data/lib/berkshelf/core_ext/rbzip2.rb +8 -0
  19. data/lib/berkshelf/downloader.rb +56 -47
  20. data/lib/berkshelf/errors.rb +9 -2
  21. data/lib/berkshelf/formatters/human_readable.rb +10 -3
  22. data/lib/berkshelf/formatters/json.rb +7 -3
  23. data/lib/berkshelf/git.rb +2 -1
  24. data/lib/berkshelf/init_generator.rb +18 -12
  25. data/lib/berkshelf/location.rb +4 -14
  26. data/lib/berkshelf/locations/chef_api_location.rb +0 -1
  27. data/lib/berkshelf/locations/git_location.rb +1 -2
  28. data/lib/berkshelf/locations/path_location.rb +35 -11
  29. data/lib/berkshelf/locations/site_location.rb +0 -1
  30. data/lib/berkshelf/resolver.rb +18 -14
  31. data/lib/berkshelf/version.rb +1 -1
  32. data/spec/fixtures/cookbooks/example_cookbook/Berksfile +1 -0
  33. data/spec/unit/berkshelf/berksfile_spec.rb +30 -2
  34. data/spec/unit/berkshelf/community_rest_spec.rb +49 -11
  35. data/spec/unit/berkshelf/cookbook_source_spec.rb +11 -7
  36. data/spec/unit/berkshelf/downloader_spec.rb +1 -1
  37. data/spec/unit/berkshelf/location_spec.rb +0 -6
  38. data/spec/unit/berkshelf/locations/chef_api_location_spec.rb +0 -4
  39. data/spec/unit/berkshelf/locations/git_location_spec.rb +0 -5
  40. data/spec/unit/berkshelf/locations/path_location_spec.rb +0 -41
  41. data/spec/unit/berkshelf_spec.rb +0 -25
  42. metadata +37 -16
@@ -123,7 +123,9 @@ module Berkshelf
123
123
  class BerksfileReadError < BerkshelfError
124
124
  # @param [#status_code] original_error
125
125
  def initialize(original_error)
126
- @original_error = original_error
126
+ @original_error = original_error
127
+ @error_message = original_error.to_s
128
+ @error_backtrace = original_error.backtrace
127
129
  end
128
130
 
129
131
  status_code(113)
@@ -132,11 +134,16 @@ module Berkshelf
132
134
  @original_error.respond_to?(:status_code) ? @original_error.status_code : 113
133
135
  end
134
136
 
137
+ alias_method :original_backtrace, :backtrace
138
+ def backtrace
139
+ Array(@error_backtrace) + Array(original_backtrace)
140
+ end
141
+
135
142
  def to_s
136
143
  [
137
144
  "An error occurred while reading the Berksfile:",
138
145
  "",
139
- " " + @original_error.to_s.split("\n").map(&:strip).join("\n "),
146
+ " #{@error_message}",
140
147
  ].join("\n")
141
148
  end
142
149
  end
@@ -18,9 +18,16 @@ module Berkshelf
18
18
  #
19
19
  # @param [String] cookbook
20
20
  # @param [String] version
21
- # @param [String] path
22
- def use(cookbook, version, path = nil)
23
- Berkshelf.ui.info "Using #{cookbook} (#{version})#{' at '+path if path}"
21
+ # @param [~Location] location
22
+ def use(cookbook, version, location = nil)
23
+ message = "Using #{cookbook} (#{version})"
24
+
25
+ if location && location.is_a?(PathLocation)
26
+ message << ' from metadata' if location.metadata?
27
+ message << " at '#{location.relative_path}'" unless location.relative_path == '.'
28
+ end
29
+
30
+ Berkshelf.ui.info message
24
31
  end
25
32
 
26
33
  # Output a Cookbook upload message using {Berkshelf.ui}
@@ -41,11 +41,15 @@ module Berkshelf
41
41
  #
42
42
  # @param [String] cookbook
43
43
  # @param [String] version
44
- # @param [String] path
45
- def use(cookbook, version, path = nil)
44
+ # @param [~Location] location
45
+ def use(cookbook, version, location = nil)
46
46
  cookbooks[cookbook] ||= {}
47
47
  cookbooks[cookbook][:version] = version
48
- cookbooks[cookbook][:location] = path if path
48
+
49
+ if location && location.is_a?(PathLocation)
50
+ cookbooks[cookbook][:metadata] = true if location.metadata?
51
+ cookbooks[cookbook][:location] = location.relative_path
52
+ end
49
53
  end
50
54
 
51
55
  # Add a Cookbook upload entry to delayed output
data/lib/berkshelf/git.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'uri'
2
+ require 'buff/shell_out'
2
3
 
3
4
  module Berkshelf
4
5
  class Git
@@ -9,7 +10,7 @@ module Berkshelf
9
10
  HAS_SPACE_RE = %r{\s}.freeze
10
11
 
11
12
  class << self
12
- include Ridley::Mixin::ShellOut
13
+ include Buff::ShellOut
13
14
 
14
15
  # @overload git(commands)
15
16
  # Shellout to the Git executable on your system with the given commands.
@@ -1,4 +1,6 @@
1
- require 'kitchen/generator/init'
1
+ begin
2
+ require 'kitchen/generator/init'
3
+ rescue LoadError; end
2
4
 
3
5
  module Berkshelf
4
6
  class InitGenerator < BaseGenerator
@@ -49,10 +51,12 @@ module Berkshelf
49
51
  class_option :cookbook_name,
50
52
  type: :string
51
53
 
52
- class_option :skip_test_kitchen,
53
- type: :boolean,
54
- default: false,
55
- desc: 'Skip adding a testing environment to your cookbook'
54
+ if defined?(Kitchen::Generator::Init)
55
+ class_option :skip_test_kitchen,
56
+ type: :boolean,
57
+ default: false,
58
+ desc: 'Skip adding a testing environment to your cookbook'
59
+ end
56
60
 
57
61
  def generate
58
62
  validate_configuration
@@ -89,13 +93,15 @@ module Berkshelf
89
93
  template 'Gemfile.erb', target.join('Gemfile')
90
94
  end
91
95
 
92
- unless options[:skip_test_kitchen]
93
- # Temporarily use Dir.chdir to ensure the destionation_root of test kitchen's generator
94
- # is where we expect until this bug can be addressed:
95
- # https://github.com/opscode/test-kitchen/pull/140
96
- Dir.chdir target do
97
- # Kitchen::Generator::Init.new([], {}, destination_root: target).invoke_all
98
- Kitchen::Generator::Init.new([], {}).invoke_all
96
+ if defined?(Kitchen::Generator::Init)
97
+ unless options[:skip_test_kitchen]
98
+ # Temporarily use Dir.chdir to ensure the destionation_root of test kitchen's generator
99
+ # is where we expect until this bug can be addressed:
100
+ # https://github.com/opscode/test-kitchen/pull/140
101
+ Dir.chdir target do
102
+ # Kitchen::Generator::Init.new([], {}, destination_root: target).invoke_all
103
+ Kitchen::Generator::Init.new([], {}).invoke_all
104
+ end
99
105
  end
100
106
  end
101
107
 
@@ -134,11 +134,6 @@ module Berkshelf
134
134
  raise AbstractFunction
135
135
  end
136
136
 
137
- # @return [Boolean]
138
- def downloaded?
139
- @downloaded_status
140
- end
141
-
142
137
  # Ensure the retrieved CachedCookbook is valid
143
138
  #
144
139
  # @param [CachedCookbook] cached_cookbook
@@ -170,15 +165,10 @@ module Berkshelf
170
165
  def to_json(options = {})
171
166
  JSON.pretty_generate(to_hash, options)
172
167
  end
173
-
174
- private
175
-
176
- def set_downloaded_status(state)
177
- @downloaded_status = state
178
- end
179
168
  end
180
169
  end
181
170
 
182
- Dir["#{File.dirname(__FILE__)}/locations/*.rb"].sort.each do |path|
183
- require_relative "locations/#{File.basename(path, '.rb')}"
184
- end
171
+ require_relative 'locations/chef_api_location'
172
+ require_relative 'locations/git_location'
173
+ require_relative 'locations/github_location'
174
+ require_relative 'locations/site_location'
@@ -156,7 +156,6 @@ module Berkshelf
156
156
  cached = CachedCookbook.from_store_path(berks_path)
157
157
  validate_cached(cached)
158
158
 
159
- set_downloaded_status(true)
160
159
  cached
161
160
  end
162
161
 
@@ -54,7 +54,7 @@ module Berkshelf
54
54
  # @return [Berkshelf::CachedCookbook]
55
55
  def download(destination)
56
56
  if cached?(destination)
57
- @ref = Berkshelf::Git.rev_parse(revision_path(destination))
57
+ @ref ||= Berkshelf::Git.rev_parse(revision_path(destination))
58
58
  return local_revision(destination)
59
59
  end
60
60
 
@@ -77,7 +77,6 @@ module Berkshelf
77
77
  cached = CachedCookbook.from_store_path(cb_path)
78
78
  validate_cached(cached)
79
79
 
80
- set_downloaded_status(true)
81
80
  cached
82
81
  end
83
82
 
@@ -22,6 +22,7 @@ module Berkshelf
22
22
  include Location
23
23
 
24
24
  set_location_key :path
25
+ set_valid_options :path, :metadata
25
26
 
26
27
  attr_accessor :path
27
28
  attr_reader :name
@@ -30,26 +31,49 @@ module Berkshelf
30
31
  # @param [Solve::Constraint] version_constraint
31
32
  # @param [Hash] options
32
33
  #
33
- # @option options [String] :path
34
+ # @option options [#to_s] :path
34
35
  # a filepath to the cookbook on your local disk
36
+ # @option options [Boolean] :metadata
37
+ # true if this is a metadata source
35
38
  def initialize(name, version_constraint, options = {})
36
39
  @name = name
37
40
  @version_constraint = version_constraint
38
- @path = options[:path]
39
- set_downloaded_status(true)
41
+ @path = options[:path].to_s
42
+ @metadata = options[:metadata]
40
43
  end
41
44
 
42
- # @param [#to_s] destination
45
+ # The cookbook associated with this path location.
43
46
  #
44
47
  # @return [Berkshelf::CachedCookbook]
45
- def download(destination)
46
- cached = CachedCookbook.from_path(path, name: name)
47
- validate_cached(cached)
48
+ # the cached cookbook for this location
49
+ def cookbook
50
+ @cookbook ||= CachedCookbook.from_path(path, name: name)
51
+ end
52
+
53
+ # Returns true if the location is a metadata location. By default, no
54
+ # locations are the metadata location.
55
+ #
56
+ # @return [Boolean]
57
+ def metadata?
58
+ !!@metadata
59
+ end
60
+
61
+ # Return this PathLocation's path relative to the given target.
62
+ #
63
+ # @param [#to_s] target
64
+ # the path to a file or directory to be relative to
65
+ #
66
+ # @return [String]
67
+ # the relative path relative to the target
68
+ def relative_path(target = '.')
69
+ my_path = Pathname.new(path).expand_path
70
+ target_path = Pathname.new(target.to_s).expand_path
71
+ target_path = target_path.dirname if target_path.file?
72
+
73
+ new_path = my_path.relative_path_from(target_path).to_s
48
74
 
49
- set_downloaded_status(true)
50
- cached
51
- rescue IOError
52
- raise Berkshelf::CookbookNotFound
75
+ return new_path if new_path.index('.') == 0
76
+ "./#{new_path}"
53
77
  end
54
78
 
55
79
  def to_hash
@@ -47,7 +47,6 @@ module Berkshelf
47
47
  cached = CachedCookbook.from_store_path(berks_path)
48
48
  validate_cached(cached)
49
49
 
50
- set_downloaded_status(true)
51
50
  cached
52
51
  end
53
52
 
@@ -85,7 +85,7 @@ module Berkshelf
85
85
  # @return [Array<Berkshelf::CookbookSource>]
86
86
  # an array of CookbookSources that are currently added to this resolver
87
87
  def sources
88
- @sources.collect { |name, source| source }
88
+ @sources.values
89
89
  end
90
90
 
91
91
  # Finds a solution for the currently added sources and their dependencies and
@@ -153,24 +153,28 @@ module Berkshelf
153
153
  #
154
154
  # @return [Boolean]
155
155
  def use_source(source)
156
+ name = source.name
157
+ constraint = source.version_constraint
158
+ location = source.location
159
+
156
160
  if source.downloaded?
157
161
  cached = source.cached_cookbook
158
- source.location.validate_cached(cached)
162
+ location.validate_cached(cached)
163
+ Berkshelf.formatter.use(name, cached.version, location)
164
+ true
165
+ elsif location.is_a?(GitLocation)
166
+ false
159
167
  else
160
- if source.location.is_a?(GitLocation)
161
- return false
168
+ cached = downloader.cookbook_store.satisfy(name, constraint)
169
+
170
+ if cached
171
+ get_source(source).cached_cookbook = cached
172
+ Berkshelf.formatter.use(name, cached.version)
173
+ true
174
+ else
175
+ false
162
176
  end
163
-
164
- cached = downloader.cookbook_store.satisfy(source.name, source.version_constraint)
165
- return false if cached.nil?
166
-
167
- get_source(source).cached_cookbook = cached
168
177
  end
169
-
170
- path = source.location.is_a?(PathLocation) ? source.location.to_s : nil
171
- Berkshelf.formatter.use(cached.cookbook_name, cached.version, path)
172
-
173
- true
174
178
  end
175
179
  end
176
180
  end
@@ -1,3 +1,3 @@
1
1
  module Berkshelf
2
- VERSION = "2.0.3"
2
+ VERSION = "2.0.4"
3
3
  end
@@ -0,0 +1 @@
1
+ site :opscode
@@ -129,8 +129,8 @@ describe Berkshelf::Berksfile do
129
129
 
130
130
  before { Dir.chdir(path) }
131
131
 
132
- it 'sends the add_source message with an explicit version constraint and the path to the cookbook' do
133
- subject.should_receive(:add_source).with('example_cookbook', '= 0.5.0', path: path.to_s)
132
+ it 'sends the add_source message with no version constraint, the path to the cookbook, and the metadata definition' do
133
+ subject.should_receive(:add_source).with('example_cookbook', nil, path: path.to_s, metadata: true)
134
134
  subject.metadata
135
135
  end
136
136
  end
@@ -572,4 +572,32 @@ describe Berkshelf::Berksfile do
572
572
  end
573
573
  end
574
574
  end
575
+
576
+ describe '#validate_files!' do
577
+ before { described_class.send(:public, :validate_files!) }
578
+ let(:cookbook) { double('cookbook', cookbook_name: 'cookbook', path: 'path') }
579
+
580
+ it 'raises an error when the cookbook has spaces in the files' do
581
+ Dir.stub(:glob).and_return(['/there are/spaces/in this/recipes/default.rb'])
582
+ expect {
583
+ subject.validate_files!(cookbook)
584
+ }.to raise_error(Berkshelf::InvalidCookbookFiles)
585
+ end
586
+
587
+ it 'does not raise an error when the cookbook is valid' do
588
+ Dir.stub(:glob).and_return(['/there-are/no-spaces/in-this/recipes/default.rb'])
589
+ expect {
590
+ subject.validate_files!(cookbook)
591
+ }.to_not raise_error(Berkshelf::InvalidCookbookFiles)
592
+ end
593
+
594
+ it 'does not raise an exception with spaces in the path' do
595
+ Dir.stub(:glob).and_return(['/there are/spaces/in this/recipes/default.rb'])
596
+ Pathname.any_instance.stub(:dirname).and_return('/there are/spaces/in this')
597
+
598
+ expect {
599
+ subject.validate_files!(cookbook)
600
+ }.to_not raise_error
601
+ end
602
+ end
575
603
  end
@@ -5,21 +5,59 @@ describe Berkshelf::CommunityREST, vcr: { record: :new_episodes, serialize_with:
5
5
  let(:target) { '/foo/bar' }
6
6
  let(:destination) { '/destination/bar' }
7
7
  let(:file) { double('file') }
8
- let(:gzip_reader) { double('gzip_reader') }
9
8
 
10
- before do
11
- File.stub(:open).with(target, 'rb').and_return(file)
12
- Zlib::GzipReader.stub(:new).with(file).and_return(gzip_reader)
13
- Archive::Tar::Minitar.stub(:unpack).with(gzip_reader, destination)
9
+ describe 'a tar.gz file' do
10
+ let(:gzip_reader) { double('gzip_reader') }
11
+
12
+ before do
13
+ File.stub(:open).with(target, 'rb').and_return(file)
14
+ Zlib::GzipReader.stub(:new).with(file).and_return(gzip_reader)
15
+ Archive::Tar::Minitar.stub(:unpack).with(gzip_reader, destination)
16
+ end
17
+
18
+ it 'unpacks the file' do
19
+ File.should_receive(:open).with(target, 'rb')
20
+ ::IO.should_receive(:binread).with(target, 2).and_return([0x1F, 0x8B].pack("C*"))
21
+ Zlib::GzipReader.should_receive(:new).with(file)
22
+ Archive::Tar::Minitar.should_receive(:unpack).with(gzip_reader, destination)
23
+
24
+ expect(Berkshelf::CommunityREST.unpack(target, destination)).to eq(destination)
25
+ end
26
+ end
27
+
28
+ describe 'a tar file' do
29
+ before do
30
+ File.stub(:open).with(target, 'rb').and_return(file)
31
+ Archive::Tar::Minitar.stub(:unpack).with(target, destination)
32
+ end
33
+
34
+ it 'unpacks the file' do
35
+ ::IO.should_receive(:binread).once
36
+ ::IO.should_receive(:binread).with(target, 8, 257).and_return("ustar\x0000")
37
+ Archive::Tar::Minitar.should_receive(:unpack).with(target, destination)
38
+
39
+ expect(Berkshelf::CommunityREST.unpack(target, destination)).to eq(destination)
40
+ end
14
41
  end
15
42
 
16
- it 'unpacks the tar' do
17
- File.should_receive(:open).with(target, 'rb')
18
- ::IO.should_receive(:binread).with(target, 2).and_return([0x1F, 0x8B].pack("C*"))
19
- Zlib::GzipReader.should_receive(:new).with(file)
20
- Archive::Tar::Minitar.should_receive(:unpack).with(gzip_reader, destination)
43
+ describe 'a tar.bz2 file' do
44
+ let(:bzip2_reader) { double('bzip2_reader') }
45
+
46
+ before do
47
+ File.stub(:open).with(target, 'rb').and_return(file)
48
+ RBzip2::Decompressor.stub(:new).with(file).and_return(bzip2_reader)
49
+ Archive::Tar::Minitar.stub(:unpack).with(bzip2_reader, destination)
50
+ end
51
+
52
+ it 'unpacks the file' do
53
+ File.should_receive(:open).with(target, 'rb')
54
+ ::IO.should_receive(:binread).twice
55
+ ::IO.should_receive(:binread).with(target, 3).and_return('BZh')
56
+ RBzip2::Decompressor.should_receive(:new).with(file)
57
+ Archive::Tar::Minitar.should_receive(:unpack).with(bzip2_reader, destination)
21
58
 
22
- expect(Berkshelf::CommunityREST.unpack(target, destination)).to eq(destination)
59
+ expect(Berkshelf::CommunityREST.unpack(target, destination)).to eq(destination)
60
+ end
23
61
  end
24
62
  end
25
63