beaker 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.simplecov +14 -0
- data/DOCUMENTING.md +167 -0
- data/Gemfile +3 -0
- data/LICENSE +17 -0
- data/README.md +332 -0
- data/Rakefile +121 -0
- data/beaker.gemspec +42 -0
- data/beaker.rb +10 -0
- data/bin/beaker +9 -0
- data/lib/beaker.rb +36 -0
- data/lib/beaker/answers.rb +29 -0
- data/lib/beaker/answers/version28.rb +104 -0
- data/lib/beaker/answers/version30.rb +194 -0
- data/lib/beaker/cli.rb +113 -0
- data/lib/beaker/command.rb +241 -0
- data/lib/beaker/command_factory.rb +21 -0
- data/lib/beaker/dsl.rb +85 -0
- data/lib/beaker/dsl/assertions.rb +87 -0
- data/lib/beaker/dsl/helpers.rb +625 -0
- data/lib/beaker/dsl/install_utils.rb +299 -0
- data/lib/beaker/dsl/outcomes.rb +99 -0
- data/lib/beaker/dsl/roles.rb +97 -0
- data/lib/beaker/dsl/structure.rb +63 -0
- data/lib/beaker/dsl/wrappers.rb +100 -0
- data/lib/beaker/host.rb +193 -0
- data/lib/beaker/host/aix.rb +15 -0
- data/lib/beaker/host/aix/file.rb +16 -0
- data/lib/beaker/host/aix/group.rb +35 -0
- data/lib/beaker/host/aix/user.rb +32 -0
- data/lib/beaker/host/unix.rb +54 -0
- data/lib/beaker/host/unix/exec.rb +15 -0
- data/lib/beaker/host/unix/file.rb +16 -0
- data/lib/beaker/host/unix/group.rb +40 -0
- data/lib/beaker/host/unix/pkg.rb +22 -0
- data/lib/beaker/host/unix/user.rb +32 -0
- data/lib/beaker/host/windows.rb +44 -0
- data/lib/beaker/host/windows/exec.rb +18 -0
- data/lib/beaker/host/windows/file.rb +15 -0
- data/lib/beaker/host/windows/group.rb +36 -0
- data/lib/beaker/host/windows/pkg.rb +26 -0
- data/lib/beaker/host/windows/user.rb +32 -0
- data/lib/beaker/hypervisor.rb +37 -0
- data/lib/beaker/hypervisor/aixer.rb +52 -0
- data/lib/beaker/hypervisor/blimper.rb +123 -0
- data/lib/beaker/hypervisor/fusion.rb +56 -0
- data/lib/beaker/hypervisor/solaris.rb +65 -0
- data/lib/beaker/hypervisor/vagrant.rb +118 -0
- data/lib/beaker/hypervisor/vcloud.rb +175 -0
- data/lib/beaker/hypervisor/vsphere.rb +80 -0
- data/lib/beaker/hypervisor/vsphere_helper.rb +200 -0
- data/lib/beaker/logger.rb +167 -0
- data/lib/beaker/network_manager.rb +73 -0
- data/lib/beaker/options_parsing.rb +323 -0
- data/lib/beaker/result.rb +55 -0
- data/lib/beaker/shared.rb +15 -0
- data/lib/beaker/shared/error_handler.rb +17 -0
- data/lib/beaker/shared/host_handler.rb +46 -0
- data/lib/beaker/shared/repetition.rb +28 -0
- data/lib/beaker/ssh_connection.rb +198 -0
- data/lib/beaker/test_case.rb +225 -0
- data/lib/beaker/test_config.rb +148 -0
- data/lib/beaker/test_suite.rb +288 -0
- data/lib/beaker/utils.rb +7 -0
- data/lib/beaker/utils/ntp_control.rb +42 -0
- data/lib/beaker/utils/repo_control.rb +92 -0
- data/lib/beaker/utils/setup_helper.rb +77 -0
- data/lib/beaker/utils/validator.rb +27 -0
- data/spec/beaker/command_spec.rb +94 -0
- data/spec/beaker/dsl/assertions_spec.rb +104 -0
- data/spec/beaker/dsl/helpers_spec.rb +230 -0
- data/spec/beaker/dsl/install_utils_spec.rb +70 -0
- data/spec/beaker/dsl/outcomes_spec.rb +43 -0
- data/spec/beaker/dsl/roles_spec.rb +86 -0
- data/spec/beaker/dsl/structure_spec.rb +60 -0
- data/spec/beaker/dsl/wrappers_spec.rb +52 -0
- data/spec/beaker/host_spec.rb +95 -0
- data/spec/beaker/logger_spec.rb +117 -0
- data/spec/beaker/options_parsing_spec.rb +37 -0
- data/spec/beaker/puppet_command_spec.rb +128 -0
- data/spec/beaker/ssh_connection_spec.rb +39 -0
- data/spec/beaker/test_case_spec.rb +6 -0
- data/spec/beaker/test_suite_spec.rb +44 -0
- data/spec/mocks_and_helpers.rb +34 -0
- data/spec/spec_helper.rb +15 -0
- metadata +359 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Beaker
|
2
|
+
module Shared
|
3
|
+
module Repetition
|
4
|
+
|
5
|
+
def repeat_for seconds, &block
|
6
|
+
timeout = Time.now + seconds
|
7
|
+
done = false
|
8
|
+
until done or timeout < Time.now do
|
9
|
+
done = block.call
|
10
|
+
end
|
11
|
+
return done
|
12
|
+
end
|
13
|
+
|
14
|
+
def repeat_fibonacci_style_for attempts, &block
|
15
|
+
done = false
|
16
|
+
attempt = 1
|
17
|
+
last_wait, wait = 0, 1
|
18
|
+
while not done and attempt <= attempts do
|
19
|
+
done = block.call
|
20
|
+
sleep wait
|
21
|
+
last_wait, wait = wait, last_wait + wait
|
22
|
+
end
|
23
|
+
return done
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'timeout'
|
3
|
+
require 'net/scp'
|
4
|
+
|
5
|
+
module Beaker
|
6
|
+
class SshConnection
|
7
|
+
|
8
|
+
RETRYABLE_EXCEPTIONS = [
|
9
|
+
SocketError,
|
10
|
+
Timeout::Error,
|
11
|
+
Errno::ETIMEDOUT,
|
12
|
+
Errno::EHOSTDOWN,
|
13
|
+
Errno::EHOSTUNREACH,
|
14
|
+
Errno::ECONNREFUSED,
|
15
|
+
Errno::ECONNRESET,
|
16
|
+
Errno::ENETUNREACH,
|
17
|
+
Net::SSH::Disconnect
|
18
|
+
]
|
19
|
+
|
20
|
+
def initialize hostname, user = nil, options = {}
|
21
|
+
@hostname = hostname
|
22
|
+
@user = user
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.connect hostname, user = 'root', options = {}
|
27
|
+
connection = new hostname, user, options
|
28
|
+
connection.connect
|
29
|
+
connection
|
30
|
+
end
|
31
|
+
|
32
|
+
def connect
|
33
|
+
try = 1
|
34
|
+
last_wait = 0
|
35
|
+
wait = 1
|
36
|
+
@ssh ||= begin
|
37
|
+
Net::SSH.start(@hostname, @user, @options)
|
38
|
+
rescue *RETRYABLE_EXCEPTIONS => e
|
39
|
+
if try <= 11
|
40
|
+
puts "Try #{try} -- Host #{@hostname} unreachable: #{e.message}"
|
41
|
+
puts "Trying again in #{wait} seconds"
|
42
|
+
sleep wait
|
43
|
+
(last_wait, wait) = wait, last_wait + wait
|
44
|
+
try += 1
|
45
|
+
retry
|
46
|
+
else
|
47
|
+
# why is the logger not passed into this class?
|
48
|
+
puts "Failed to connect to #{@hostname}"
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
rescue Net::SSH::AuthenticationFailed => e
|
52
|
+
raise "Unable to authenticate to #{@hostname} with user #{e.message.inspect}"
|
53
|
+
end
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def close
|
58
|
+
begin
|
59
|
+
@ssh.close if @ssh
|
60
|
+
rescue
|
61
|
+
@ssh.shutdown!
|
62
|
+
end
|
63
|
+
@ssh = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def try_to_execute command, options = {}, stdout_callback = nil,
|
67
|
+
stderr_callback = stdout_callback
|
68
|
+
|
69
|
+
result = Result.new(@hostname, command)
|
70
|
+
# why are we getting to this point on a dry run anyways?
|
71
|
+
# also... the host creates connections through the class method,
|
72
|
+
# which automatically connects, so you can't do a dry run unless you also
|
73
|
+
# can connect to your hosts?
|
74
|
+
return result if options[:dry_run]
|
75
|
+
|
76
|
+
@ssh.open_channel do |channel|
|
77
|
+
request_terminal_for( channel, command ) if options[:pty]
|
78
|
+
|
79
|
+
channel.exec(command) do |terminal, success|
|
80
|
+
abort "FAILED: to execute command on a new channel on #{@hostname}" unless success
|
81
|
+
register_stdout_for terminal, result, stdout_callback
|
82
|
+
register_stderr_for terminal, result, stderr_callback
|
83
|
+
register_exit_code_for terminal, result
|
84
|
+
|
85
|
+
process_stdin_for( terminal, options[:stdin] ) if options[:stdin]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Process SSH activity until we stop doing that - which is when our
|
90
|
+
# channel is finished with...
|
91
|
+
@ssh.loop
|
92
|
+
|
93
|
+
result.finalize!
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
def execute command, options = {}, stdout_callback = nil,
|
98
|
+
stderr_callback = stdout_callback
|
99
|
+
attempt = true
|
100
|
+
begin
|
101
|
+
result = try_to_execute(command, options, stdout_callback, stderr_callback)
|
102
|
+
rescue *RETRYABLE_EXCEPTIONS => e
|
103
|
+
if attempt
|
104
|
+
attempt = false
|
105
|
+
puts "Command execution failed, attempting to reconnect to #{@hostname}"
|
106
|
+
close
|
107
|
+
connect
|
108
|
+
retry
|
109
|
+
else
|
110
|
+
raise
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
def request_terminal_for channel, command
|
118
|
+
channel.request_pty do |ch, success|
|
119
|
+
if success
|
120
|
+
puts "Allocated a PTY on #{@hostname} for #{command.inspect}"
|
121
|
+
else
|
122
|
+
abort "FAILED: could not allocate a pty when requested on " +
|
123
|
+
"#{@hostname} for #{command.inspect}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def register_stdout_for channel, output, callback = nil
|
129
|
+
channel.on_data do |ch, data|
|
130
|
+
callback[data] if callback
|
131
|
+
output.stdout << data
|
132
|
+
output.output << data
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def register_stderr_for channel, output, callback = nil
|
137
|
+
channel.on_extended_data do |ch, type, data|
|
138
|
+
if type == 1
|
139
|
+
callback[data] if callback
|
140
|
+
output.stderr << data
|
141
|
+
output.output << data
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def register_exit_code_for channel, output
|
147
|
+
channel.on_request("exit-status") do |ch, data|
|
148
|
+
output.exit_code = data.read_long
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def process_stdin_for channel, stdin
|
153
|
+
# queue stdin data, force it to packets, and signal eof: this
|
154
|
+
# triggers action in many remote commands, notably including
|
155
|
+
# 'puppet apply'. It must be sent at some point before the rest
|
156
|
+
# of the action.
|
157
|
+
channel.send_data stdin.to_s
|
158
|
+
channel.process
|
159
|
+
channel.eof!
|
160
|
+
end
|
161
|
+
|
162
|
+
def scp_to source, target, options = {}, dry_run = false
|
163
|
+
return if dry_run
|
164
|
+
|
165
|
+
options[:recursive]=File.directory?(source) if options[:recursive].nil?
|
166
|
+
|
167
|
+
@ssh.scp.upload! source, target, options
|
168
|
+
|
169
|
+
result = Result.new(@hostname, [source, target])
|
170
|
+
|
171
|
+
# Setting these values allows reporting via result.log(test_name)
|
172
|
+
result.stdout = "SCP'ed file #{source} to #{@hostname}:#{target}"
|
173
|
+
|
174
|
+
# Net::Scp always returns 0, so just set the return code to 0.
|
175
|
+
result.exit_code = 0
|
176
|
+
|
177
|
+
return result
|
178
|
+
end
|
179
|
+
|
180
|
+
def scp_from source, target, options = {}, dry_run = false
|
181
|
+
return if dry_run
|
182
|
+
|
183
|
+
options[:recursive] = true if options[:recursive].nil?
|
184
|
+
|
185
|
+
@ssh.scp.download! source, target, options
|
186
|
+
|
187
|
+
result = Result.new(@hostname, [source, target])
|
188
|
+
|
189
|
+
# Setting these values allows reporting via result.log(test_name)
|
190
|
+
result.stdout = "SCP'ed file #{@hostname}:#{source} to #{target}"
|
191
|
+
|
192
|
+
# Net::Scp always returns 0, so just set the return code to 0.
|
193
|
+
result.exit_code = 0
|
194
|
+
|
195
|
+
result
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
%w( host answers dsl ).each do |lib|
|
2
|
+
begin
|
3
|
+
require "beaker/#{lib}"
|
4
|
+
rescue LoadError
|
5
|
+
require File.expand_path(File.join(File.dirname(__FILE__), lib))
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'tempfile'
|
10
|
+
require 'benchmark'
|
11
|
+
require 'stringio'
|
12
|
+
require 'rbconfig'
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
module Beaker
|
16
|
+
# This class represents a single test case. A test case is necessarily
|
17
|
+
# contained all in one file though may have multiple dependent examples.
|
18
|
+
# They are executed in order (save for any teardown procs registered
|
19
|
+
# through {Beaker::DSL::Structure#teardown}) and once completed
|
20
|
+
# the status of the TestCase is saved. Instance readers/accessors provide
|
21
|
+
# the test case access to various details of the environment and suite
|
22
|
+
# the test case is running within.
|
23
|
+
#
|
24
|
+
# See {Beaker::DSL} for more information about writing tests
|
25
|
+
# using the DSL.
|
26
|
+
class TestCase
|
27
|
+
include Beaker::DSL
|
28
|
+
|
29
|
+
rb_config_class = defined?(RbConfig) ? RbConfig : Config
|
30
|
+
if rb_config_class::CONFIG['MAJOR'].to_i == 1 &&
|
31
|
+
rb_config_class::CONFIG['MINOR'].to_i == 8 then
|
32
|
+
Test::Unit.run = true
|
33
|
+
# The Exception raised by Ruby's STDLIB's test framework (Ruby 1.8)
|
34
|
+
TEST_EXCEPTION_CLASS = Test::Unit::AssertionFailedError
|
35
|
+
else
|
36
|
+
# The Exception raised by Ruby's STDLIB's test framework (Ruby 1.9)
|
37
|
+
TEST_EXCEPTION_CLASS = ::MiniTest::Assertion
|
38
|
+
end
|
39
|
+
|
40
|
+
# Necessary for implementing {Beaker::DSL::Helpers#confine}.
|
41
|
+
# Assumed to be an array of valid {Beaker::Host} objects for
|
42
|
+
# this test case.
|
43
|
+
attr_accessor :hosts
|
44
|
+
|
45
|
+
# Necessary for many methods in {Beaker::DSL}. Assumed to be
|
46
|
+
# an instance of {Beaker::Logger}.
|
47
|
+
attr_accessor :logger
|
48
|
+
|
49
|
+
# A Hash of 'product name' => 'version installed', only set when
|
50
|
+
# products are installed via git or PE install steps. See the 'git' or
|
51
|
+
# 'pe' directories within 'ROOT/setup' for examples.
|
52
|
+
attr_reader :version
|
53
|
+
|
54
|
+
# A Hash of values taken from host config file.
|
55
|
+
attr_reader :config
|
56
|
+
|
57
|
+
# Parsed command line options.
|
58
|
+
attr_reader :options
|
59
|
+
|
60
|
+
# The path to the file which contains this test case.
|
61
|
+
attr_reader :path
|
62
|
+
|
63
|
+
# I don't know why this is here
|
64
|
+
attr_reader :fail_flag
|
65
|
+
|
66
|
+
# The user that is running this tests home directory, needed by 'net/ssh'.
|
67
|
+
attr_reader :usr_home
|
68
|
+
|
69
|
+
# A Symbol denoting the status of this test (:fail, :pending,
|
70
|
+
# :skipped, :pass).
|
71
|
+
attr_reader :test_status
|
72
|
+
|
73
|
+
# The exception that may have stopped this test's execution.
|
74
|
+
attr_reader :exception
|
75
|
+
|
76
|
+
# @deprecated
|
77
|
+
# The amount of time taken to execute the test. Unused, probably soon
|
78
|
+
# to be removed or refactored.
|
79
|
+
attr_reader :runtime
|
80
|
+
|
81
|
+
# An Array of Procs to be called after test execution has stopped
|
82
|
+
# (whether by exception or not).
|
83
|
+
attr_reader :teardown_procs
|
84
|
+
|
85
|
+
# @deprecated
|
86
|
+
# Legacy accessor from when test files would only contain one remote
|
87
|
+
# action. Contains the Result of the last call to utilize
|
88
|
+
# {Beaker::DSL::Helpers#on}. Do not use as it is not safe
|
89
|
+
# in test files that use multiple calls to
|
90
|
+
# {Beaker::DSL::Helpers#on}.
|
91
|
+
attr_accessor :result
|
92
|
+
|
93
|
+
# @param [Hosts,Array<Host>] these_hosts The hosts to execute this test
|
94
|
+
# against/on.
|
95
|
+
# @param [Logger] logger A logger that implements
|
96
|
+
# {Beaker::Logger}'s interface.
|
97
|
+
# @param [Hash{String=>String}] config Clusterfck of various config opts.
|
98
|
+
# @param [Hash{Symbol=>String}] options Parsed command line options.
|
99
|
+
# @param [String] path The local path to a test file to be executed.
|
100
|
+
def initialize(these_hosts, logger, config, options={}, path=nil)
|
101
|
+
@config = config['CONFIG']
|
102
|
+
@hosts = these_hosts
|
103
|
+
@logger = logger
|
104
|
+
@options = options
|
105
|
+
@path = path
|
106
|
+
@usr_home = ENV['HOME']
|
107
|
+
@test_status = :pass
|
108
|
+
@exception = nil
|
109
|
+
@runtime = nil
|
110
|
+
@teardown_procs = []
|
111
|
+
|
112
|
+
|
113
|
+
#
|
114
|
+
# We put this on each wrapper (rather than the class) so that methods
|
115
|
+
# defined in the tests don't leak out to other tests.
|
116
|
+
class << self
|
117
|
+
def run_test
|
118
|
+
@runtime = Benchmark.realtime do
|
119
|
+
begin
|
120
|
+
test = File.read(path)
|
121
|
+
eval test,nil,path,1
|
122
|
+
rescue FailTest, TEST_EXCEPTION_CLASS => e
|
123
|
+
@test_status = :fail
|
124
|
+
@exception = e
|
125
|
+
rescue PendingTest
|
126
|
+
@test_status = :pending
|
127
|
+
rescue SkipTest
|
128
|
+
@test_status = :skip
|
129
|
+
rescue StandardError, ScriptError => e
|
130
|
+
log_and_fail_test(e)
|
131
|
+
ensure
|
132
|
+
@teardown_procs.each do |teardown|
|
133
|
+
begin
|
134
|
+
teardown.call
|
135
|
+
rescue StandardError => e
|
136
|
+
log_and_fail_test(e)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
return self
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Log an error and mark the test as failed, passing through an
|
147
|
+
# exception so it can be displayed at the end of the total run.
|
148
|
+
#
|
149
|
+
# We break out the complete exception backtrace and log each line
|
150
|
+
# individually as well.
|
151
|
+
#
|
152
|
+
# @param exception [Exception] exception to fail with
|
153
|
+
def log_and_fail_test(exception)
|
154
|
+
logger.error(exception.inspect)
|
155
|
+
bt = exception.backtrace
|
156
|
+
logger.pretty_backtrace(bt).each_line do |line|
|
157
|
+
logger.error(line)
|
158
|
+
end
|
159
|
+
@test_status = :error
|
160
|
+
@exception = exception
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# The TestCase as a hash
|
166
|
+
# @api public
|
167
|
+
# @note The visibility and semantics of this method are valid, but the
|
168
|
+
# structure of the Hash it returns may change without notice
|
169
|
+
#
|
170
|
+
# @return [Hash] A Hash representation of this test.
|
171
|
+
def to_hash
|
172
|
+
hash = {}
|
173
|
+
hash['HOSTS'] = {}
|
174
|
+
hash['CONFIG'] = @config
|
175
|
+
@hosts.each do |host|
|
176
|
+
hash['HOSTS'][host.name] = host.overrides
|
177
|
+
end
|
178
|
+
hash
|
179
|
+
end
|
180
|
+
|
181
|
+
# @deprecated
|
182
|
+
# An proxy for the last {Beaker::Result#stdout} returned by
|
183
|
+
# a method that makes remote calls. Use the {Beaker::Result}
|
184
|
+
# object returned by the method directly instead. For Usage see
|
185
|
+
# {Beaker::Result}.
|
186
|
+
def stdout
|
187
|
+
return nil if @result.nil?
|
188
|
+
@result.stdout
|
189
|
+
end
|
190
|
+
|
191
|
+
# @deprecated
|
192
|
+
# An proxy for the last {Beaker::Result#stderr} returned by
|
193
|
+
# a method that makes remote calls. Use the {Beaker::Result}
|
194
|
+
# object returned by the method directly instead. For Usage see
|
195
|
+
# {Beaker::Result}.
|
196
|
+
def stderr
|
197
|
+
return nil if @result.nil?
|
198
|
+
@result.stderr
|
199
|
+
end
|
200
|
+
|
201
|
+
# @deprecated
|
202
|
+
# An proxy for the last {Beaker::Result#exit_code} returned by
|
203
|
+
# a method that makes remote calls. Use the {Beaker::Result}
|
204
|
+
# object returned by the method directly instead. For Usage see
|
205
|
+
# {Beaker::Result}.
|
206
|
+
def exit_code
|
207
|
+
return nil if @result.nil?
|
208
|
+
@result.exit_code
|
209
|
+
end
|
210
|
+
|
211
|
+
# This method retrieves the forge hostname from either:
|
212
|
+
# * The environment variable 'forge_host'
|
213
|
+
# * The parameter 'forge_host' from the CONFIG hash in a node definition
|
214
|
+
#
|
215
|
+
# If none of these are available, it falls back to the static
|
216
|
+
# 'vulcan-acceptance.delivery.puppetlabs.net' hostname
|
217
|
+
#
|
218
|
+
# @return [String] hostname of test forge
|
219
|
+
def forge
|
220
|
+
ENV['forge_host'] ||
|
221
|
+
@config['forge_host'] ||
|
222
|
+
'vulcan-acceptance.delivery.puppetlabs.net'
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|