berkshelf 2.0.3 → 2.0.4

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 (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