berkshelf 6.0.1 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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