berkshelf 1.2.0.rc1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG.md +8 -0
  2. data/Gemfile +1 -1
  3. data/README.md +2 -0
  4. data/berkshelf.gemspec +4 -3
  5. data/features/step_definitions/filesystem_steps.rb +0 -1
  6. data/generator_files/Gemfile.erb +0 -3
  7. data/generator_files/Vagrantfile.erb +13 -1
  8. data/lib/berkshelf.rb +0 -1
  9. data/lib/berkshelf/berksfile.rb +11 -4
  10. data/lib/berkshelf/cached_cookbook.rb +2 -257
  11. data/lib/berkshelf/chef.rb +0 -1
  12. data/lib/berkshelf/chef/config.rb +3 -0
  13. data/lib/berkshelf/chef/cookbook.rb +0 -2
  14. data/lib/berkshelf/community_rest.rb +31 -6
  15. data/lib/berkshelf/cookbook_source.rb +5 -1
  16. data/lib/berkshelf/errors.rb +24 -0
  17. data/lib/berkshelf/git.rb +49 -1
  18. data/lib/berkshelf/init_generator.rb +1 -1
  19. data/lib/berkshelf/locations/chef_api_location.rb +6 -3
  20. data/lib/berkshelf/locations/path_location.rb +2 -0
  21. data/lib/berkshelf/version.rb +1 -1
  22. data/spec/spec_helper.rb +9 -2
  23. data/spec/support/chef_api.rb +1 -10
  24. data/spec/unit/berkshelf/cached_cookbook_spec.rb +37 -458
  25. data/spec/unit/berkshelf/git_spec.rb +119 -9
  26. data/spec/unit/berkshelf/init_generator_spec.rb +0 -1
  27. metadata +30 -24
  28. data/lib/berkshelf/chef/cookbook/metadata.rb +0 -556
  29. data/lib/berkshelf/chef/cookbook/syntax_check.rb +0 -158
  30. data/lib/berkshelf/chef/digester.rb +0 -67
  31. data/lib/berkshelf/mixin/checksum.rb +0 -16
  32. data/lib/berkshelf/mixin/params_validate.rb +0 -218
  33. data/lib/berkshelf/mixin/shell_out.rb +0 -23
  34. data/lib/berkshelf/uploader.rb +0 -80
  35. data/spec/unit/berkshelf/uploader_spec.rb +0 -27
  36. data/spec/unit/chef/cookbook/metadata_spec.rb +0 -5
  37. data/spec/unit/chef/digester_spec.rb +0 -41
@@ -1,3 +1,11 @@
1
+ # 1.2.0
2
+ - Remove Vagrant as a gem dependency
3
+ - Remove Chef as a gem dependency
4
+ - Add retries to downloads/uploads
5
+ - Speed optimizations to resolver
6
+ - Speed optimizations to downloading cookbooks
7
+ - Speed optimizations to uploading cookbooks
8
+
1
9
  # 1.1.0
2
10
  ## new/improved commands
3
11
  - `berks show` command: display the file path for the given cookbook's current version resolved by your Berksfile
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  Berkshelf
2
2
  =========
3
+ [![Gem Version](https://badge.fury.io/rb/berkshelf.png)](http://badge.fury.io/rb/berkshelf)
3
4
  [![Build Status](https://travis-ci.org/RiotGames/berkshelf.png?branch=master)](https://travis-ci.org/RiotGames/berkshelf)
5
+ [![Dependency Status](https://gemnasium.com/RiotGames/berkshelf.png)](https://gemnasium.com/RiotGames/berkshelf)
4
6
  [![Code Climate](https://codeclimate.com/github/RiotGames/berkshelf.png)](https://codeclimate.com/github/RiotGames/berkshelf)
5
7
 
6
8
  Manage a Cookbook or an Application's Cookbook dependencies
@@ -32,14 +32,15 @@ Gem::Specification.new do |s|
32
32
  s.add_dependency 'mixlib-shellout'
33
33
  s.add_dependency 'mixlib-config'
34
34
  s.add_dependency 'faraday', '>= 0.8.5'
35
- s.add_dependency 'ridley', '>= 0.7.0.rc4'
36
- s.add_dependency 'chozo', '>= 0.5.0'
37
- s.add_dependency 'hashie'
35
+ s.add_dependency 'ridley', '>= 0.8.3'
36
+ s.add_dependency 'chozo', '>= 0.6.1'
37
+ s.add_dependency 'hashie', '>= 2.0.2'
38
38
  s.add_dependency 'minitar'
39
39
  s.add_dependency 'json', '>= 1.5.0'
40
40
  s.add_dependency 'multi_json', '~> 1.5'
41
41
  s.add_dependency 'solve', '>= 0.4.2'
42
42
  s.add_dependency 'thor', '~> 0.16.0'
43
+ s.add_dependency 'retryable'
43
44
 
44
45
  # Vagrant 1-0-stable compatability locks
45
46
  s.add_dependency 'moneta', '~> 0.6.0'
@@ -106,7 +106,6 @@ Then /^I should have a new cookbook skeleton "(.*?)"$/ do |name|
106
106
  end
107
107
  file "Gemfile" do
108
108
  contains "gem 'berkshelf'"
109
- contains "gem 'vagrant'"
110
109
  end
111
110
  file "metadata.rb"
112
111
  file "README.md"
@@ -7,6 +7,3 @@ gem 'thor-foodcritic'
7
7
  <% if options[:scmversion] -%>
8
8
  gem 'thor-scmversion'
9
9
  <% end -%>
10
- <% unless options[:skip_vagrant] -%>
11
- gem 'vagrant', '~> 1.0.5'
12
- <% end -%>
@@ -1,4 +1,16 @@
1
- require 'berkshelf/vagrant'
1
+ begin
2
+ require 'berkshelf/vagrant'
3
+ rescue LoadError
4
+ puts "[WARNING] Berkshelf not found in your Vagrant's RubyGems but your Vagrantfile is attempting"
5
+ puts "[WARNING] to require the Berkshelf Vagrant plugin! Install the Berkshelf Vagrant plugin or"
6
+ puts "[WARNING] remove the 'require \"berkshelf/vagrant\"' line from the top of your Vagrantfile."
7
+ puts ""
8
+ puts "If you installed Vagrant by RubyGems:"
9
+ puts " Install Berkshelf by running: \"gem install berkshelf\""
10
+ puts "If you installed Vagrant by one of the pre-packaged installers:"
11
+ puts " Install Berkshelf by running: \"vagrant gem install berkshelf\""
12
+ puts ""
13
+ end
2
14
 
3
15
  Vagrant::Config.run do |config|
4
16
  # All Vagrant configuration is done here. The most common configuration
@@ -48,7 +48,6 @@ module Berkshelf
48
48
  autoload :Mixin, 'berkshelf/mixin'
49
49
  autoload :Resolver, 'berkshelf/resolver'
50
50
  autoload :UI, 'berkshelf/ui'
51
- autoload :Uploader, 'berkshelf/uploader'
52
51
 
53
52
  require 'berkshelf/location'
54
53
 
@@ -183,7 +183,7 @@ module Berkshelf
183
183
  raise CookbookNotFound, "No 'metadata.rb' found at #{path}"
184
184
  end
185
185
 
186
- metadata = Berkshelf::Chef::Cookbook::Metadata.from_file(metadata_file.to_s)
186
+ metadata = Ridley::Chef::Cookbook::Metadata.from_file(metadata_file.to_s)
187
187
 
188
188
  name = if metadata.name.empty? || metadata.name.nil?
189
189
  File.basename(File.dirname(metadata_file))
@@ -478,12 +478,17 @@ module Berkshelf
478
478
  #
479
479
  # @raise [UploadFailure] if you are uploading cookbooks with an invalid or not-specified client key
480
480
  def upload(options = {})
481
- uploader = Uploader.new(options)
481
+ conn = Ridley.new(options)
482
482
  solution = resolve(options)
483
+
483
484
  solution.each do |cb|
484
- Berkshelf.formatter.upload cb.cookbook_name, cb.version, options[:server_url]
485
- uploader.upload(cb, options)
485
+ upload_opts = options.dup
486
+ upload_opts[:name] = cb.cookbook_name
487
+
488
+ Berkshelf.formatter.upload cb.cookbook_name, cb.version, upload_opts[:server_url]
489
+ conn.cookbook.upload(cb.path, upload_opts)
486
490
  end
491
+
487
492
  if options[:skip_dependencies]
488
493
  missing_cookbooks = options.fetch(:cookbooks, nil) - solution.map(&:cookbook_name)
489
494
  unless missing_cookbooks.empty?
@@ -496,6 +501,8 @@ module Berkshelf
496
501
  msg = "Could not upload cookbooks: Missing Chef client key: '#{Berkshelf::Config.instance.chef.client_key}'."
497
502
  msg << " Generate or update your Berkshelf configuration that contains a valid path to a Chef client key."
498
503
  raise UploadFailure, msg
504
+ ensure
505
+ conn.terminate if conn && conn.alive?
499
506
  end
500
507
 
501
508
  # Finds a solution for the Berksfile and returns an array of CachedCookbooks.
@@ -1,29 +1,7 @@
1
1
  module Berkshelf
2
2
  # @author Jamie Winsor <reset@riotgames.com>
3
- class CachedCookbook
3
+ class CachedCookbook < Ridley::Chef::Cookbook
4
4
  class << self
5
- include Berkshelf::Mixin::Checksum
6
-
7
- # Creates a new instance of Berkshelf::CachedCookbook from a path on disk that
8
- # contains a Cookbook. The name of the Cookbook will be determined first by the
9
- # name attribute of the metadata.rb file if it is present. If the name attribute
10
- # has not been set the Cookbook name will be determined by the basename of the
11
- # given filepath.
12
- #
13
- # @param [#to_s] path
14
- # a path on disk to the location of a Cookbook
15
- #
16
- # @return [Berkshelf::CachedCookbook]
17
- def from_path(path)
18
- path = Pathname.new(path)
19
- metadata = Berkshelf::Chef::Cookbook::Metadata.from_file(path.join('metadata.rb'))
20
-
21
- name = metadata.name.empty? ? File.basename(path) : metadata.name
22
- metadata.name(name) if metadata.name.empty?
23
-
24
- new(name, path, metadata)
25
- end
26
-
27
5
  # @param [#to_s] path
28
6
  # a path on disk to the location of a Cookbook downloaded by the Downloader
29
7
  #
@@ -35,248 +13,15 @@ module Berkshelf
35
13
  cached_name = File.basename(path.to_s).slice(DIRNAME_REGEXP, 1)
36
14
  return nil if cached_name.nil?
37
15
 
38
- metadata = Berkshelf::Chef::Cookbook::Metadata.from_file(path.join('metadata.rb'))
39
- metadata.name(cached_name) if metadata.name.empty?
40
-
41
- new(cached_name, path, metadata)
42
- end
43
-
44
- # @param [String] filepath
45
- # a path on disk to the location of a file to checksum
46
- #
47
- # @return [String]
48
- # a checksum that can be used to uniquely identify the file understood
49
- # by a Chef Server.
50
- def checksum(filepath)
51
- Berkshelf::Chef::Digester.md5_checksum_for_file(filepath)
16
+ from_path(path, name: cached_name)
52
17
  end
53
18
  end
54
19
 
55
20
  DIRNAME_REGEXP = /^(.+)-(.+)$/
56
- CHEF_TYPE = "cookbook_version".freeze
57
- CHEF_JSON_CLASS = "Chef::CookbookVersion".freeze
58
-
59
- extend Forwardable
60
-
61
- attr_reader :cookbook_name
62
- attr_reader :path
63
- attr_reader :metadata
64
-
65
- # @return [Hashie::Mash]
66
- # a Hashie::Mash containing Cookbook file category names as keys and an Array of Hashes
67
- # containing metadata about the files belonging to that category. This is used
68
- # to communicate what a Cookbook looks like when uploading to a Chef Server.
69
- #
70
- # example:
71
- # {
72
- # :recipes => [
73
- # {
74
- # name: "default.rb",
75
- # path: "recipes/default.rb",
76
- # checksum: "fb1f925dcd5fc4ebf682c4442a21c619",
77
- # specificity: "default"
78
- # }
79
- # ]
80
- # ...
81
- # ...
82
- # }
83
- attr_reader :manifest
84
-
85
- def_delegator :@metadata, :version
86
-
87
- def initialize(name, path, metadata)
88
- @cookbook_name = name
89
- @path = Pathname.new(path)
90
- @metadata = metadata
91
- @files = Array.new
92
- @manifest = Hashie::Mash.new(
93
- recipes: Array.new,
94
- definitions: Array.new,
95
- libraries: Array.new,
96
- attributes: Array.new,
97
- files: Array.new,
98
- templates: Array.new,
99
- resources: Array.new,
100
- providers: Array.new,
101
- root_files: Array.new
102
- )
103
-
104
- load_files
105
- end
106
-
107
- # @return [String]
108
- # the name of the cookbook and the version number separated by a dash (-).
109
- #
110
- # example:
111
- # "nginx-0.101.2"
112
- def name
113
- "#{cookbook_name}-#{version}"
114
- end
115
21
 
116
22
  # @return [Hash]
117
23
  def dependencies
118
24
  metadata.recommendations.merge(metadata.dependencies)
119
25
  end
120
-
121
- # @return [Hash]
122
- # an hash containing the checksums and expanded file paths of all of the
123
- # files found in the instance of CachedCookbook
124
- #
125
- # example:
126
- # {
127
- # "da97c94bb6acb2b7900cbf951654fea3" => "/Users/reset/.berkshelf/nginx-0.101.2/README.md"
128
- # }
129
- def checksums
130
- {}.tap do |checksums|
131
- files.each do |file|
132
- checksums[self.class.checksum(file)] = file
133
- end
134
- end
135
- end
136
-
137
- # @param [Symbol] category
138
- # the category of file to generate metadata about
139
- # @param [String] target
140
- # the filepath to the file to get metadata information about
141
- #
142
- # @return [Hash]
143
- # a Hash containing a name, path, checksum, and specificity key representing the
144
- # metadata about a file contained in a Cookbook. This metadata is used when
145
- # uploading a Cookbook's files to a Chef Server.
146
- #
147
- # example:
148
- # {
149
- # name: "default.rb",
150
- # path: "recipes/default.rb",
151
- # checksum: "fb1f925dcd5fc4ebf682c4442a21c619",
152
- # specificity: "default"
153
- # }
154
- def file_metadata(category, target)
155
- target = Pathname.new(target)
156
-
157
- {
158
- name: target.basename.to_s,
159
- path: target.relative_path_from(path).to_s,
160
- checksum: self.class.checksum(target),
161
- specificity: file_specificity(category, target)
162
- }
163
- end
164
-
165
- # Validates that this instance of CachedCookbook points to a valid location on disk that
166
- # contains a cookbook which passes a Ruby and template syntax check. Raises an error if
167
- # these assertions are not true.
168
- #
169
- # @return [Boolean]
170
- # returns true if Cookbook is valid
171
- def validate!
172
- raise CookbookNotFound, "No Cookbook found at: #{path}" unless path.exist?
173
-
174
- unless quietly { syntax_checker.validate_ruby_files }
175
- raise CookbookSyntaxError, "Invalid ruby files in cookbook: #{name} (#{version})."
176
- end
177
- unless quietly { syntax_checker.validate_templates }
178
- raise CookbookSyntaxError, "Invalid template files in cookbook: #{name} (#{version})."
179
- end
180
-
181
- true
182
- end
183
-
184
- def to_hash
185
- result = manifest.dup
186
- result['chef_type'] = 'cookbook_version'
187
- result['name'] = name
188
- result['cookbook_name'] = cookbook_name
189
- result['version'] = version
190
- result['metadata'] = metadata
191
- result['chef_type']
192
- result
193
- end
194
-
195
- def to_json(*a)
196
- result = self.to_hash
197
- result['json_class'] = chef_json_class
198
- result['frozen?'] = false
199
- result.to_json(*a)
200
- end
201
-
202
- def to_s
203
- "#{cookbook_name} (#{version}) '#{path}'"
204
- end
205
-
206
- def <=>(other_cookbook)
207
- [self.cookbook_name, self.version] <=> [other_cookbook.cookbook_name, other_cookbook.version]
208
- end
209
-
210
- private
211
-
212
- attr_reader :files
213
-
214
- def chef_type
215
- CHEF_TYPE
216
- end
217
-
218
- def chef_json_class
219
- CHEF_JSON_CLASS
220
- end
221
-
222
- def syntax_checker
223
- @syntax_checker ||= Berkshelf::Chef::Cookbook::SyntaxCheck.new(path.to_s)
224
- end
225
-
226
- def load_files
227
- load_shallow(:recipes, 'recipes', '*.rb')
228
- load_shallow(:definitions, 'definitions', '*.rb')
229
- load_shallow(:libraries, 'libraries', '*.rb')
230
- load_shallow(:attributes, 'attributes', '*.rb')
231
- load_recursively(:files, "files", "*")
232
- load_recursively(:templates, "templates", "*")
233
- load_recursively(:resources, "resources", "*.rb")
234
- load_recursively(:providers, "providers", "*.rb")
235
- load_root
236
- end
237
-
238
- def load_root
239
- [].tap do |files|
240
- Dir.glob(path.join('*'), File::FNM_DOTMATCH).each do |file|
241
- next if File.directory?(file)
242
- @files << file
243
- @manifest[:root_files] << file_metadata(:root_files, file)
244
- end
245
- end
246
- end
247
-
248
- def load_recursively(category, category_dir, glob)
249
- [].tap do |files|
250
- file_spec = path.join(category_dir, '**', glob)
251
- Dir.glob(file_spec, File::FNM_DOTMATCH).each do |file|
252
- next if File.directory?(file)
253
- @files << file
254
- @manifest[category] << file_metadata(category, file)
255
- end
256
- end
257
- end
258
-
259
- def load_shallow(category, *path_glob)
260
- [].tap do |files|
261
- Dir[path.join(*path_glob)].each do |file|
262
- @files << file
263
- @manifest[category] << file_metadata(category, file)
264
- end
265
- end
266
- end
267
-
268
- # @param [Symbol] category
269
- # @param [Pathname] target
270
- #
271
- # @return [String]
272
- def file_specificity(category, target)
273
- case category
274
- when :files, :templates
275
- relpath = target.relative_path_from(path).to_s
276
- relpath.slice(/(.+)\/(.+)\/.+/, 2)
277
- else
278
- 'default'
279
- end
280
- end
281
26
  end
282
27
  end
@@ -6,6 +6,5 @@ module Berkshelf
6
6
  module Chef
7
7
  autoload :Config, 'berkshelf/chef/config'
8
8
  autoload :Cookbook, 'berkshelf/chef/cookbook'
9
- autoload :Digester, 'berkshelf/chef/digester'
10
9
  end
11
10
  end
@@ -1,4 +1,5 @@
1
1
  require 'socket'
2
+ require 'tmpdir'
2
3
  require 'berkshelf/mixin'
3
4
  require 'mixlib/config'
4
5
 
@@ -81,6 +82,8 @@ module Berkshelf::Chef
81
82
  cookbook_email "YOUR_EMAIL"
82
83
  cookbook_license "reserved"
83
84
 
85
+ knife Hash.new
86
+
84
87
  # history: prior to Chef 11, the cache implementation was based on
85
88
  # moneta and configured via cache_options[:path]. Knife configs
86
89
  # generated with Chef 11 will have `syntax_check_cache_path`, but older
@@ -2,7 +2,5 @@ module Berkshelf::Chef
2
2
  # @author Jamie Winsor <reset@riotgames.com>
3
3
  module Cookbook
4
4
  autoload :Chefignore, 'berkshelf/chef/cookbook/chefignore'
5
- autoload :Metadata, 'berkshelf/chef/cookbook/metadata'
6
- autoload :SyntaxCheck, 'berkshelf/chef/cookbook/syntax_check'
7
5
  end
8
6
  end
@@ -1,4 +1,5 @@
1
1
  require 'open-uri'
2
+ require 'retryable'
2
3
 
3
4
  module Berkshelf
4
5
  # @author Jamie Winsor <reset@riotgames.com>
@@ -32,13 +33,35 @@ module Berkshelf
32
33
 
33
34
  V1_API = 'http://cookbooks.opscode.com/api/v1/cookbooks'.freeze
34
35
 
36
+ # @return [String]
35
37
  attr_reader :api_uri
36
-
37
- def initialize(uri = V1_API)
38
- @api_uri = Addressable::URI.parse(uri)
38
+ # @return [Integer]
39
+ # how many retries to attempt on HTTP requests
40
+ attr_reader :retries
41
+ # @return [Float]
42
+ # time to wait between retries
43
+ attr_reader :retry_interval
44
+
45
+ # @param [String] uri (CommunityREST::V1_API)
46
+ # location of community site to connect to
47
+ #
48
+ # @option options [Integer] :retries (5)
49
+ # retry requests on 5XX failures
50
+ # @option options [Float] :retry_interval (0.5)
51
+ # how often we should pause between retries
52
+ def initialize(uri = V1_API, options = {})
53
+ options = options.reverse_merge(retries: 5, retry_interval: 0.5)
54
+ @api_uri = Addressable::URI.parse(uri)
55
+ @retries = options[:retries]
56
+ @retry_interval = options[:retry_interval]
39
57
 
40
58
  builder = Faraday::Builder.new do |b|
41
59
  b.response :json
60
+ b.request :retry,
61
+ max: @retries,
62
+ interval: @retry_interval,
63
+ exceptions: [Faraday::Error::TimeoutError]
64
+
42
65
  b.adapter :net_http
43
66
  end
44
67
 
@@ -123,10 +146,12 @@ module Berkshelf
123
146
  local = Tempfile.new('community-rest-stream')
124
147
  local.binmode
125
148
 
126
- open(target, 'rb', headers) do |remote|
127
- local.write(remote.read)
149
+ retryable(tries: retries, on: OpenURI::HTTPError, sleep: retry_interval) do
150
+ open(target, 'rb', headers) do |remote|
151
+ local.write(remote.read)
152
+ end
128
153
  end
129
-
154
+
130
155
  local
131
156
  ensure
132
157
  local.close(false) unless local.nil?