berkshelf 6.0.1 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -3
- data/Gemfile +1 -28
- data/Gemfile.lock +27 -94
- data/Rakefile +10 -0
- data/appveyor.yml +2 -2
- data/berkshelf.gemspec +2 -1
- data/features/commands/install.feature +21 -0
- data/features/step_definitions/berksfile_steps.rb +1 -1
- data/features/step_definitions/environment_steps.rb +1 -1
- data/features/step_definitions/json_steps.rb +2 -2
- data/features/support/env.rb +0 -2
- data/lib/berkshelf/berksfile.rb +1 -1
- data/lib/berkshelf/chef_repo_universe.rb +45 -0
- data/lib/berkshelf/cli.rb +1 -2
- data/lib/berkshelf/community_rest.rb +6 -37
- data/lib/berkshelf/config.rb +3 -0
- data/lib/berkshelf/downloader.rb +2 -2
- data/lib/berkshelf/formatters/human.rb +3 -1
- data/lib/berkshelf/installer.rb +14 -6
- data/lib/berkshelf/locations/git.rb +0 -2
- data/lib/berkshelf/mixin/git.rb +4 -3
- data/lib/berkshelf/shell_out.rb +17 -0
- data/lib/berkshelf/source.rb +30 -17
- data/lib/berkshelf/source_uri.rb +1 -1
- data/lib/berkshelf/ssl_policies.rb +1 -1
- data/lib/berkshelf/streaming_file_adapter.rb +22 -0
- data/lib/berkshelf/version.rb +1 -1
- data/lib/berkshelf/visualizer.rb +3 -3
- data/spec/fixtures/complex-cookbook-path/cookbooks/app/metadata.rb +2 -0
- data/spec/fixtures/complex-cookbook-path/cookbooks/jenkins-config/metadata.rb +4 -0
- data/spec/fixtures/complex-cookbook-path/cookbooks/jenkins/metadata.rb +2 -0
- data/spec/support/git.rb +18 -17
- data/spec/support/kitchen.rb +0 -14
- data/spec/unit/berkshelf/chef_repo_universe_spec.rb +37 -0
- data/spec/unit/berkshelf/config_spec.rb +46 -0
- data/spec/unit/berkshelf/cookbook_generator_spec.rb +0 -8
- data/spec/unit/berkshelf/downloader_spec.rb +12 -9
- data/spec/unit/berkshelf/init_generator_spec.rb +0 -1
- data/spec/unit/berkshelf/locations/git_spec.rb +2 -2
- data/spec/unit/berkshelf/resolver/graph_spec.rb +3 -2
- data/spec/unit/berkshelf/source_spec.rb +55 -44
- data/spec/unit/berkshelf/ssl_policies_spec.rb +3 -2
- data/spec/unit/berkshelf/uploader_spec.rb +1 -0
- data/spec/unit/berkshelf/visualizer_spec.rb +1 -1
- metadata +30 -7
- data/Guardfile +0 -18
- 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[
|
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|
|
data/features/support/env.rb
CHANGED
@@ -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
|
data/lib/berkshelf/berksfile.rb
CHANGED
@@ -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
|
data/lib/berkshelf/cli.rb
CHANGED
@@ -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 "
|
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.
|
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:
|
193
|
-
|
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
|
data/lib/berkshelf/config.rb
CHANGED
@@ -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
|
data/lib/berkshelf/downloader.rb
CHANGED
@@ -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] = {
|
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
|
-
|
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
|
data/lib/berkshelf/installer.rb
CHANGED
@@ -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
|
-
@
|
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
|
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
|
-
|
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
|
-
|
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
|
data/lib/berkshelf/mixin/git.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
require "
|
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 =
|
21
|
+
response = shell_out(%{git #{command}})
|
21
22
|
|
22
|
-
if
|
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
|
data/lib/berkshelf/source.rb
CHANGED
@@ -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[
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
137
|
-
|
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
|
data/lib/berkshelf/source_uri.rb
CHANGED
@@ -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
|