berkshelf 6.0.1 → 6.1.0

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -3
  3. data/Gemfile +1 -28
  4. data/Gemfile.lock +27 -94
  5. data/Rakefile +10 -0
  6. data/appveyor.yml +2 -2
  7. data/berkshelf.gemspec +2 -1
  8. data/features/commands/install.feature +21 -0
  9. data/features/step_definitions/berksfile_steps.rb +1 -1
  10. data/features/step_definitions/environment_steps.rb +1 -1
  11. data/features/step_definitions/json_steps.rb +2 -2
  12. data/features/support/env.rb +0 -2
  13. data/lib/berkshelf/berksfile.rb +1 -1
  14. data/lib/berkshelf/chef_repo_universe.rb +45 -0
  15. data/lib/berkshelf/cli.rb +1 -2
  16. data/lib/berkshelf/community_rest.rb +6 -37
  17. data/lib/berkshelf/config.rb +3 -0
  18. data/lib/berkshelf/downloader.rb +2 -2
  19. data/lib/berkshelf/formatters/human.rb +3 -1
  20. data/lib/berkshelf/installer.rb +14 -6
  21. data/lib/berkshelf/locations/git.rb +0 -2
  22. data/lib/berkshelf/mixin/git.rb +4 -3
  23. data/lib/berkshelf/shell_out.rb +17 -0
  24. data/lib/berkshelf/source.rb +30 -17
  25. data/lib/berkshelf/source_uri.rb +1 -1
  26. data/lib/berkshelf/ssl_policies.rb +1 -1
  27. data/lib/berkshelf/streaming_file_adapter.rb +22 -0
  28. data/lib/berkshelf/version.rb +1 -1
  29. data/lib/berkshelf/visualizer.rb +3 -3
  30. data/spec/fixtures/complex-cookbook-path/cookbooks/app/metadata.rb +2 -0
  31. data/spec/fixtures/complex-cookbook-path/cookbooks/jenkins-config/metadata.rb +4 -0
  32. data/spec/fixtures/complex-cookbook-path/cookbooks/jenkins/metadata.rb +2 -0
  33. data/spec/support/git.rb +18 -17
  34. data/spec/support/kitchen.rb +0 -14
  35. data/spec/unit/berkshelf/chef_repo_universe_spec.rb +37 -0
  36. data/spec/unit/berkshelf/config_spec.rb +46 -0
  37. data/spec/unit/berkshelf/cookbook_generator_spec.rb +0 -8
  38. data/spec/unit/berkshelf/downloader_spec.rb +12 -9
  39. data/spec/unit/berkshelf/init_generator_spec.rb +0 -1
  40. data/spec/unit/berkshelf/locations/git_spec.rb +2 -2
  41. data/spec/unit/berkshelf/resolver/graph_spec.rb +3 -2
  42. data/spec/unit/berkshelf/source_spec.rb +55 -44
  43. data/spec/unit/berkshelf/ssl_policies_spec.rb +3 -2
  44. data/spec/unit/berkshelf/uploader_spec.rb +1 -0
  45. data/spec/unit/berkshelf/visualizer_spec.rb +1 -1
  46. metadata +30 -7
  47. data/Guardfile +0 -18
  48. data/lib/berkshelf/commands/test_command.rb +0 -13
@@ -7,5 +7,5 @@ Given /^the environment variable (.+) is "(.+)"$/ do |variable, value|
7
7
  end
8
8
 
9
9
  Given /^the environment variable (.+) is \$TEST_BERKSHELF_ARTIFACTORY_API_KEY$/ do |variable|
10
- set_environment_variable(variable, ENV['TEST_BERKSHELF_ARTIFACTORY_API_KEY'])
10
+ set_environment_variable(variable, ENV["TEST_BERKSHELF_ARTIFACTORY_API_KEY"])
11
11
  end
@@ -16,8 +16,8 @@ end
16
16
 
17
17
  # Pending Ridley allowing newer Faraday and Celluloid.
18
18
  def clean_json_output(output)
19
- output.gsub(/^.+warning: constant ::Fixnum is deprecated$/, '') \
20
- .gsub(/^.*forwarding to private method Celluloid::PoolManager#url_prefix$/, '')
19
+ output.gsub(/^.+warning: constant ::Fixnum is deprecated$/, "") \
20
+ .gsub(/^.*forwarding to private method Celluloid::PoolManager#url_prefix$/, "")
21
21
  end
22
22
 
23
23
  Then /^the output should contain JSON:$/ do |data|
@@ -12,7 +12,6 @@ require "berkshelf/api/cucumber" unless windows?
12
12
  Dir["spec/support/**/*.rb"].each { |f| require File.expand_path(f) }
13
13
 
14
14
  World(Berkshelf::RSpec::PathHelpers)
15
- World(Berkshelf::RSpec::Kitchen)
16
15
 
17
16
  CHEF_SERVER_PORT = 26310
18
17
  BERKS_API_PORT = 26210
@@ -34,7 +33,6 @@ Before do
34
33
  aruba.config.main_class = Berkshelf::Cli::Runner
35
34
  @dirs = ["spec/tmp/aruba"] # set aruba's temporary directory
36
35
 
37
- stub_kitchen!
38
36
  clean_tmp_path
39
37
  Berkshelf.initialize_filesystem
40
38
  Berkshelf::CookbookStore.instance.initialize_filesystem
@@ -198,7 +198,7 @@ module Berkshelf
198
198
  #
199
199
  # @return [Array<Source>]
200
200
  def source(api_url, **options)
201
- source = Source.new(api_url, **options)
201
+ source = Source.new(self, api_url, **options)
202
202
  @sources[source.uri.to_s] = source
203
203
  end
204
204
  expose :source
@@ -0,0 +1,45 @@
1
+ require "berkshelf/api_client/remote_cookbook"
2
+ require "ridley/chef/cookbook"
3
+
4
+ module Berkshelf
5
+ # Shim to look like a Berkshelf::APIClient but for a chef repo folder.
6
+ #
7
+ # @since 6.1
8
+ class ChefRepoUniverse
9
+ def initialize(uri, **options)
10
+ @uri = uri
11
+ @path = options[:path]
12
+ @options = options
13
+ end
14
+
15
+ def universe
16
+ Dir.entries(cookbooks_path).sort.each_with_object([]) do |entry, cookbooks|
17
+ next if entry[0] == "." # Skip hidden folders.
18
+ entry_path = "#{cookbooks_path}/#{entry}"
19
+ next unless File.directory?(entry_path) # Skip non-dirs.
20
+ cookbook = begin
21
+ Ridley::Chef::Cookbook.from_path(entry_path)
22
+ rescue IOError
23
+ next # It wasn't a cookbook.
24
+ end
25
+ cookbooks << Berkshelf::APIClient::RemoteCookbook.new(
26
+ cookbook.cookbook_name,
27
+ cookbook.version,
28
+ location_type: "file_store",
29
+ location_path: entry_path,
30
+ dependencies: cookbook.metadata.dependencies
31
+ )
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def cookbooks_path
38
+ if File.exist?("#{@path}/cookbooks")
39
+ "#{@path}/cookbooks"
40
+ else
41
+ @path
42
+ end
43
+ end
44
+ end
45
+ end
@@ -3,7 +3,6 @@ require_relative "config"
3
3
  require_relative "init_generator"
4
4
  require_relative "cookbook_generator"
5
5
  require_relative "commands/shelf"
6
- require_relative "commands/test_command"
7
6
 
8
7
  module Berkshelf
9
8
  class Cli < Thor
@@ -262,7 +261,7 @@ module Berkshelf
262
261
  banner: "URL"
263
262
  desc "search NAME", "Search the remote source for cookbooks matching the partial name"
264
263
  def search(name)
265
- source = Source.new(options[:source])
264
+ source = Source.new(nil, options[:source])
266
265
  cookbooks = source.search(name)
267
266
  Berkshelf.formatter.search(cookbooks)
268
267
  end
@@ -1,4 +1,4 @@
1
- require "open-uri"
1
+ require "berkshelf/streaming_file_adapter"
2
2
  require "retryable"
3
3
  require "mixlib/archive"
4
4
 
@@ -80,7 +80,7 @@ module Berkshelf
80
80
  interval: @retry_interval,
81
81
  exceptions: [Faraday::Error::TimeoutError]
82
82
 
83
- b.adapter :httpclient
83
+ b.use Berkshelf::StreamingFileAdapter
84
84
  end
85
85
 
86
86
  super(api_uri, options)
@@ -116,7 +116,7 @@ module Berkshelf
116
116
  response = get("cookbooks/#{name}/versions/#{self.class.uri_escape_version(version)}")
117
117
 
118
118
  # Artifactory responds with a 200 and blank body for unknown cookbooks.
119
- if response.status == 200 && response.body.to_s == ''
119
+ if response.status == 200 && response.body.to_s == ""
120
120
  response.env.status = 404
121
121
  end
122
122
 
@@ -137,7 +137,7 @@ module Berkshelf
137
137
  response = get("cookbooks/#{name}")
138
138
 
139
139
  # Artifactory responds with a 200 and blank body for unknown cookbooks.
140
- if response.status == 200 && response.body.to_s == ''
140
+ if response.status == 200 && response.body.to_s == ""
141
141
  response.env.status = 404
142
142
  end
143
143
 
@@ -189,44 +189,13 @@ module Berkshelf
189
189
  local = Tempfile.new("community-rest-stream")
190
190
  local.binmode
191
191
 
192
- Retryable.retryable(tries: retries, on: OpenURI::HTTPError, sleep: retry_interval) do
193
- open(target, "rb", open_uri_options(target)) do |remote|
194
- local.write(remote.read)
195
- end
192
+ Retryable.retryable(tries: retries, on: Faraday::Error::ConnectionFailed, sleep: retry_interval) do
193
+ get(target, nil, streaming_file: local)
196
194
  end
197
195
 
198
196
  local
199
197
  ensure
200
198
  local.close(false) unless local.nil?
201
199
  end
202
-
203
- private
204
-
205
- def open_uri_options(target)
206
- options = {}
207
- options.merge!(headers)
208
- options.merge!(open_uri_proxy_options(target))
209
- options.merge!(ssl_verify_mode: ssl_verify_mode)
210
- end
211
-
212
- def open_uri_proxy_options(target)
213
- proxy_uri = URI.parse(target).find_proxy()
214
- return {} if proxy_uri.nil?
215
-
216
- proxy = Faraday::ProxyOptions.from(proxy_uri)
217
- if proxy && proxy[:user] && proxy[:password]
218
- { proxy_http_basic_authentication: [ proxy[:uri], proxy[:user], proxy[:password] ] }
219
- else
220
- { proxy: proxy[:uri] }
221
- end
222
- end
223
-
224
- def ssl_verify_mode
225
- if Berkshelf::Config.instance.ssl.verify.nil? || Berkshelf::Config.instance.ssl.verify
226
- OpenSSL::SSL::VERIFY_PEER
227
- else
228
- OpenSSL::SSL::VERIFY_NONE
229
- end
230
- end
231
200
  end
232
201
  end
@@ -110,6 +110,9 @@ module Berkshelf
110
110
  attribute "chef.trusted_certs_dir",
111
111
  type: String,
112
112
  default: Berkshelf.chef_config.trusted_certs_dir
113
+ attribute "chef.artifactory_api_key",
114
+ type: String,
115
+ default: Berkshelf.chef_config.artifactory_api_key
113
116
  attribute "cookbook.copyright",
114
117
  type: String,
115
118
  default: Berkshelf.chef_config.cookbook_copyright
@@ -63,9 +63,9 @@ module Berkshelf
63
63
 
64
64
  case remote_cookbook.location_type
65
65
  when :opscode, :supermarket
66
- options = {}
66
+ options = { ssl: source.options[:ssl] }
67
67
  if source.type == :artifactory
68
- options[:headers] = {'X-Jfrog-Art-Api' => source.options[:api_key]}
68
+ options[:headers] = { "X-Jfrog-Art-Api" => source.options[:api_key] }
69
69
  end
70
70
  CommunityREST.new(remote_cookbook.location_path, options).download(name, version)
71
71
  when :chef_server
@@ -19,7 +19,9 @@ module Berkshelf
19
19
  def install(source, cookbook)
20
20
  message = "Installing #{cookbook.name} (#{cookbook.version})"
21
21
 
22
- unless source.default?
22
+ if source.type == :chef_repo
23
+ message << " from #{cookbook.location_path}"
24
+ elsif !source.default?
23
25
  message << " from #{source}"
24
26
  message << " ([#{cookbook.location_type}] #{cookbook.location_path})"
25
27
  end
@@ -1,4 +1,6 @@
1
1
  require "berkshelf/api-client"
2
+ require "concurrent/executors"
3
+ require "concurrent/future"
2
4
 
3
5
  module Berkshelf
4
6
  class Installer
@@ -10,14 +12,15 @@ module Berkshelf
10
12
  def initialize(berksfile)
11
13
  @berksfile = berksfile
12
14
  @lockfile = berksfile.lockfile
13
- @worker = Worker.pool(size: [(Celluloid.cores.to_i - 1), 2].max, args: [berksfile])
15
+ @pool = Concurrent::FixedThreadPool.new([Concurrent.processor_count - 1, 2].max)
16
+ @worker = Worker.new(berksfile)
14
17
  end
15
18
 
16
19
  def build_universe
17
20
  berksfile.sources.collect do |source|
18
21
  Thread.new do
19
22
  begin
20
- Berkshelf.formatter.msg("Fetching cookbook index from #{source.uri}...")
23
+ Berkshelf.formatter.msg("Fetching cookbook index from #{source}...")
21
24
  source.build_universe
22
25
  rescue Berkshelf::APIClientError => ex
23
26
  Berkshelf.formatter.warn "Error retrieving universe from source: #{source}"
@@ -61,10 +64,9 @@ module Berkshelf
61
64
  private
62
65
 
63
66
  attr_reader :worker
67
+ attr_reader :pool
64
68
 
65
69
  class Worker
66
- include Celluloid
67
-
68
70
  attr_reader :berksfile
69
71
  attr_reader :downloader
70
72
 
@@ -132,7 +134,10 @@ module Berkshelf
132
134
  build_universe
133
135
  end
134
136
 
135
- cookbooks = dependencies.sort.map { |dependency| worker.future.install(dependency) }.map(&:value)
137
+ futures = dependencies.sort.map { |dependency| Concurrent::Future.execute(executor: pool) { worker.install(dependency) } }
138
+ cookbooks = futures.map(&:value)
139
+ rejects = futures.select(&:rejected?)
140
+ raise rejects.first.reason unless rejects.empty?
136
141
 
137
142
  [dependencies, cookbooks]
138
143
  end
@@ -173,7 +178,10 @@ module Berkshelf
173
178
 
174
179
  Berkshelf.log.debug " Starting resolution..."
175
180
 
176
- cookbooks = resolver.resolve.sort.map { |dependency| worker.future.install(dependency) }.map(&:value)
181
+ futures = resolver.resolve.sort.map { |dependency| Concurrent::Future.execute(executor: pool) { worker.install(dependency) } }
182
+ cookbooks = futures.map(&:value)
183
+ rejects = futures.select(&:rejected?)
184
+ raise rejects.first.reason unless rejects.empty?
177
185
 
178
186
  [dependencies, cookbooks]
179
187
  end
@@ -1,5 +1,3 @@
1
- require "buff/shell_out"
2
-
3
1
  module Berkshelf
4
2
  class GitLocation < BaseLocation
5
3
  include Mixin::Git
@@ -1,8 +1,9 @@
1
- require "buff/shell_out"
1
+ require "berkshelf/shell_out"
2
2
 
3
3
  module Berkshelf
4
4
  module Mixin
5
5
  module Git
6
+ include Berkshelf::ShellOut
6
7
  # Perform a git command.
7
8
  #
8
9
  # @param [String] command
@@ -17,9 +18,9 @@ module Berkshelf
17
18
  raise GitNotInstalled.new
18
19
  end
19
20
 
20
- response = Buff::ShellOut.shell_out(%{git #{command}})
21
+ response = shell_out(%{git #{command}})
21
22
 
22
- if error && !response.success?
23
+ if response.error?
23
24
  raise GitCommandError.new(command, cache_path, response.stderr)
24
25
  end
25
26
 
@@ -0,0 +1,17 @@
1
+ require "mixlib/shellout"
2
+
3
+ module Berkshelf
4
+ module ShellOut
5
+ def shell_out(*args, **options)
6
+ cmd = Mixlib::ShellOut.new(*args, **options)
7
+ cmd.run_command
8
+ cmd
9
+ end
10
+
11
+ def shell_out!(*args)
12
+ cmd = shell_out(*args)
13
+ cmd.error!
14
+ cmd
15
+ end
16
+ end
17
+ end
@@ -1,4 +1,5 @@
1
1
  require "berkshelf/api-client"
2
+ require "berkshelf/chef_repo_universe"
2
3
  require "berkshelf/ssl_policies"
3
4
  require "openssl"
4
5
 
@@ -10,9 +11,10 @@ module Berkshelf
10
11
  attr_accessor :uri_string
11
12
  attr_accessor :options
12
13
 
14
+ # @param [Berkshelf::Berksfile] berksfile
13
15
  # @param [String, Berkshelf::SourceURI] source
14
- def initialize(source, **options)
15
- @options = {timeout: api_timeout, open_timeout: [(api_timeout / 10), 3].max, ssl: {}}
16
+ def initialize(berksfile, source, **options)
17
+ @options = { timeout: api_timeout, open_timeout: [(api_timeout / 10), 3].max, ssl: {} }
16
18
  @options.update(options)
17
19
  case source
18
20
  when String
@@ -35,7 +37,13 @@ module Berkshelf
35
37
  @options[:client_name] ||= Berkshelf::Config.instance.chef.node_name
36
38
  @options[:client_key] ||= Berkshelf::Config.instance.chef.client_key
37
39
  when :artifactory
38
- @options[:api_key] ||= Berkshelf::Config.instance.chef.artifactory_api_key || ENV['ARTIFACTORY_API_KEY']
40
+ @options[:api_key] ||= Berkshelf::Config.instance.chef.artifactory_api_key || ENV["ARTIFACTORY_API_KEY"]
41
+ when :chef_repo
42
+ @options[:path] = uri_string
43
+ # If given a relative path, expand it against the Berksfile's folder.
44
+ @options[:path] = File.expand_path(@options[:path], File.dirname(berksfile ? berksfile.filepath : Dir.pwd))
45
+ # Lie because this won't actually parse as a URI.
46
+ @uri_string = "file://#{@options[:path]}"
39
47
  end
40
48
  # Set some default SSL options.
41
49
  Berkshelf::Config.instance.ssl.each do |key, value|
@@ -50,17 +58,19 @@ module Berkshelf
50
58
  end
51
59
 
52
60
  def api_client
53
- @api_client ||= case type
54
- when :chef_server
55
- APIClient.chef_server(server_url: uri.to_s, **options)
56
- when :artifactory
57
- # Don't accidentally mutate the options.
58
- client_options = options.dup
59
- api_key = client_options.delete(:api_key)
60
- APIClient.new(uri, headers: {'X-Jfrog-Art-Api' => api_key}, **client_options)
61
- else
62
- APIClient.new(uri, **options)
63
- end
61
+ @api_client ||= case type
62
+ when :chef_server
63
+ APIClient.chef_server(server_url: uri.to_s, **options)
64
+ when :artifactory
65
+ # Don't accidentally mutate the options.
66
+ client_options = options.dup
67
+ api_key = client_options.delete(:api_key)
68
+ APIClient.new(uri, headers: { "X-Jfrog-Art-Api" => api_key }, **client_options)
69
+ when :chef_repo
70
+ ChefRepoUniverse.new(uri_string, **options)
71
+ else
72
+ APIClient.new(uri, **options)
73
+ end
64
74
  end
65
75
 
66
76
  def uri
@@ -133,15 +143,18 @@ module Berkshelf
133
143
  end
134
144
 
135
145
  def to_s
136
- if type == :supermarket
137
- "#{uri}"
146
+ case type
147
+ when :supermarket
148
+ uri.to_s
149
+ when :chef_repo
150
+ options[:path]
138
151
  else
139
152
  "#{type}: #{uri}"
140
153
  end
141
154
  end
142
155
 
143
156
  def inspect
144
- "#<#{self.class.name} #{type}: #{uri.to_s.inspect}, #{options.map {|k, v| "#{k}: #{v.inspect}" }.join(', ')}>"
157
+ "#<#{self.class.name} #{type}: #{uri.to_s.inspect}, #{options.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')}>"
145
158
  end
146
159
 
147
160
  def hash
@@ -22,7 +22,7 @@ module Berkshelf
22
22
  end
23
23
  end
24
24
 
25
- VALID_SCHEMES = %w{http https}.freeze
25
+ VALID_SCHEMES = %w{http https file}.freeze
26
26
 
27
27
  # @raise [Berkshelf::InvalidSourceURI]
28
28
  def validate
@@ -22,7 +22,7 @@ module Berkshelf
22
22
  end
23
23
 
24
24
  def trusted_certs_dir
25
- config_dir = Berkshelf.config.chef.trusted_certs_dir.to_s.tr('\\', '/')
25
+ config_dir = Berkshelf.config.chef.trusted_certs_dir.to_s.tr('\\', "/")
26
26
  if config_dir.empty? || !::File.exist?(config_dir)
27
27
  File.join(ENV["HOME"], ".chef", "trusted_certs")
28
28
  else
@@ -0,0 +1,22 @@
1
+ require "faraday/adapter/net_http"
2
+
3
+ module Berkshelf
4
+ class StreamingFileAdapter < Faraday::Adapter::NetHttp
5
+ def call(env)
6
+ env[:streaming_file] = env[:request_headers].delete(:streaming_file) if env[:request_headers] && env[:request_headers][:streaming_file]
7
+ super
8
+ end
9
+
10
+ def perform_request(http, env)
11
+ if env[:streaming_file]
12
+ http.request(create_request(env)) do |response|
13
+ response.read_body do |chunk|
14
+ env[:streaming_file].write(chunk) if response.code == "200"
15
+ end
16
+ end
17
+ else
18
+ super
19
+ end
20
+ end
21
+ end
22
+ end