kitchen-openstack 7.0.1 → 8.0.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.
- checksums.yaml +4 -4
- data/README.md +477 -81
- data/lib/kitchen/driver/openstack/clouds.rb +124 -23
- data/lib/kitchen/driver/openstack/config.rb +97 -30
- data/lib/kitchen/driver/openstack/helpers.rb +44 -5
- data/lib/kitchen/driver/openstack/networking.rb +127 -32
- data/lib/kitchen/driver/openstack/server_helper.rb +92 -20
- data/lib/kitchen/driver/openstack/volume.rb +121 -25
- data/lib/kitchen/driver/openstack.rb +99 -15
- data/lib/kitchen/driver/openstack_version.rb +7 -1
- metadata +2 -2
|
@@ -76,9 +76,12 @@ module Kitchen
|
|
|
76
76
|
private
|
|
77
77
|
|
|
78
78
|
# Merges external config sources into the driver config hash.
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
79
|
+
#
|
|
80
|
+
# Precedence, highest first: kitchen.yml, `OS_*` environment
|
|
81
|
+
# variables, then clouds.yaml. Only keys that are currently nil are
|
|
82
|
+
# written, so anything set explicitly in kitchen.yml always wins.
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
82
85
|
def apply_clouds_config
|
|
83
86
|
cc = load_clouds_config
|
|
84
87
|
env = load_env_vars
|
|
@@ -91,15 +94,23 @@ module Kitchen
|
|
|
91
94
|
config[key] = value if config[key].nil?
|
|
92
95
|
end
|
|
93
96
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
# `verify: false` in clouds.yaml is how operators opt out of TLS
|
|
98
|
+
# verification. openstacksdk documents no environment variable for it
|
|
99
|
+
# (though its env loader does sweep up any OS_* name into an implicit
|
|
100
|
+
# cloud), and ENV_VAR_MAP deliberately mirrors the documented set --
|
|
101
|
+
# so for this driver clouds.yaml is the only source.
|
|
102
|
+
return unless cc[:ssl_verify_peer] == false && !config[:disable_ssl_validation]
|
|
97
103
|
|
|
98
104
|
config[:disable_ssl_validation] = true
|
|
99
105
|
end
|
|
100
106
|
|
|
101
|
-
# Reads OS_
|
|
102
|
-
#
|
|
107
|
+
# Reads `OS_*` environment variables and maps them to Fog config keys.
|
|
108
|
+
#
|
|
109
|
+
# Empty variables are ignored, so `OS_REGION_NAME=""` does not shadow
|
|
110
|
+
# a region set in clouds.yaml.
|
|
111
|
+
#
|
|
112
|
+
# @return [Hash{Symbol => Object}] Fog config keys for the variables
|
|
113
|
+
# that are set
|
|
103
114
|
def load_env_vars
|
|
104
115
|
result = {}
|
|
105
116
|
ENV_VAR_MAP.each do |env_var, fog_key|
|
|
@@ -109,57 +120,136 @@ module Kitchen
|
|
|
109
120
|
result
|
|
110
121
|
end
|
|
111
122
|
|
|
112
|
-
# Resolves
|
|
123
|
+
# Resolves which named cloud to read out of clouds.yaml.
|
|
124
|
+
#
|
|
125
|
+
# @return [String, nil] the cloud name, or nil if none is configured
|
|
113
126
|
def cloud_name
|
|
114
127
|
config[:openstack_cloud] || ENV["OS_CLOUD"]
|
|
115
128
|
end
|
|
116
129
|
|
|
117
130
|
# Loads and merges clouds.yaml with secure.yaml, then translates the
|
|
118
131
|
# named cloud entry into Fog-compatible config keys.
|
|
119
|
-
#
|
|
132
|
+
#
|
|
133
|
+
# secure.yaml wins over clouds.yaml, matching openstacksdk.
|
|
134
|
+
#
|
|
135
|
+
# @return [Hash{Symbol => Object}] Fog config keys, or an empty hash
|
|
136
|
+
# when no cloud is configured or the named cloud is absent
|
|
120
137
|
def load_clouds_config
|
|
121
138
|
name = cloud_name
|
|
122
139
|
return {} unless name
|
|
123
140
|
|
|
124
|
-
clouds_data = load_yaml_file("clouds.yaml", "OS_CLIENT_CONFIG_FILE")
|
|
125
|
-
secure_data = load_yaml_file("secure.yaml", "OS_CLIENT_SECURE_FILE")
|
|
141
|
+
clouds_data, clouds_path = load_yaml_file("clouds.yaml", "OS_CLIENT_CONFIG_FILE")
|
|
142
|
+
secure_data, secure_path = load_yaml_file("secure.yaml", "OS_CLIENT_SECURE_FILE")
|
|
126
143
|
|
|
127
|
-
cloud = extract_cloud(clouds_data, name)
|
|
128
|
-
secure = extract_cloud(secure_data, name)
|
|
144
|
+
cloud = extract_cloud(clouds_data, name, clouds_path)
|
|
145
|
+
secure = extract_cloud(secure_data, name, secure_path)
|
|
129
146
|
|
|
130
147
|
cloud = deep_merge(cloud, secure)
|
|
131
148
|
translate_cloud_config(cloud)
|
|
132
149
|
end
|
|
133
150
|
|
|
134
|
-
#
|
|
151
|
+
# Loads the first of the standard OpenStack config locations that
|
|
152
|
+
# exists.
|
|
153
|
+
#
|
|
154
|
+
# @param filename [String] `"clouds.yaml"` or `"secure.yaml"`
|
|
155
|
+
# @param env_var [String] the environment variable that overrides the
|
|
156
|
+
# search path for this file
|
|
157
|
+
# @return [Array(Hash, String), Array(Hash, nil)] the parsed document
|
|
158
|
+
# and the path it came from; an empty hash and nil if no file was
|
|
159
|
+
# found
|
|
160
|
+
# @raise [Kitchen::ActionFailed] if the file exists but is not valid
|
|
161
|
+
# YAML, or does not parse to a mapping
|
|
135
162
|
def load_yaml_file(filename, env_var)
|
|
136
163
|
paths = clouds_yaml_search_paths(filename, env_var)
|
|
137
164
|
path = paths.find { |p| File.exist?(p) }
|
|
138
|
-
return {} unless path
|
|
165
|
+
return [{}, nil] unless path
|
|
139
166
|
|
|
140
167
|
debug "Loading #{filename} from #{path}"
|
|
141
|
-
|
|
168
|
+
data = parse_yaml(path)
|
|
169
|
+
|
|
170
|
+
# A clouds.yaml that parses to a list or a scalar is a mistake worth
|
|
171
|
+
# naming, rather than a NoMethodError three frames later.
|
|
172
|
+
raise ActionFailed, "#{path} must contain a YAML mapping" unless data.is_a?(Hash)
|
|
173
|
+
|
|
174
|
+
[data, path]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Parses one YAML document, turning a syntax error into a message that
|
|
178
|
+
# names the offending file.
|
|
179
|
+
#
|
|
180
|
+
# @param path [String] the file to parse
|
|
181
|
+
# @return [Object] the parsed document
|
|
182
|
+
# @raise [Kitchen::ActionFailed] if the file is not valid YAML
|
|
183
|
+
def parse_yaml(path)
|
|
184
|
+
YAML.safe_load_file(path, permitted_classes: [Date]) || {}
|
|
185
|
+
rescue Psych::Exception => e
|
|
186
|
+
raise ActionFailed, "Could not parse #{path}: #{e.message}"
|
|
142
187
|
end
|
|
143
188
|
|
|
189
|
+
# Standard OpenStack client config search locations, highest priority
|
|
190
|
+
# first.
|
|
191
|
+
#
|
|
192
|
+
# @param filename [String] the file being looked for
|
|
193
|
+
# @param env_var [String] the environment variable that overrides it
|
|
194
|
+
# @return [Array<String>] candidate paths
|
|
144
195
|
def clouds_yaml_search_paths(filename, env_var)
|
|
145
196
|
paths = []
|
|
146
197
|
paths << ENV[env_var] if ENV[env_var]
|
|
147
198
|
paths << config[:clouds_yaml_path] if config[:clouds_yaml_path] && filename == "clouds.yaml"
|
|
148
199
|
paths << File.join(Dir.pwd, filename)
|
|
149
|
-
|
|
200
|
+
home = user_home
|
|
201
|
+
paths << File.join(home, ".config", "openstack", filename) if home
|
|
150
202
|
paths << File.join("/etc/openstack", filename)
|
|
151
203
|
paths
|
|
152
204
|
end
|
|
153
205
|
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
# The user's home directory, or nil if there isn't one.
|
|
207
|
+
#
|
|
208
|
+
# `Dir.home` raises when HOME is unset and the uid has no passwd entry,
|
|
209
|
+
# which is the ordinary state inside a container running as an arbitrary
|
|
210
|
+
# uid. That must not take out the whole search -- /etc/openstack is
|
|
211
|
+
# still worth trying.
|
|
212
|
+
#
|
|
213
|
+
# @return [String, nil] the home directory, or nil if it cannot be
|
|
214
|
+
# determined
|
|
215
|
+
def user_home
|
|
216
|
+
Dir.home
|
|
217
|
+
rescue ArgumentError
|
|
218
|
+
nil
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Pulls one named cloud entry out of a parsed clouds/secure document.
|
|
222
|
+
#
|
|
223
|
+
# An absent entry is an empty hash: clouds.yaml and secure.yaml are
|
|
224
|
+
# merged, and it is normal for a cloud to appear in only one of them.
|
|
225
|
+
# An entry that is *present but not a mapping* is always a mistake, and
|
|
226
|
+
# is reported rather than silently discarded -- otherwise the driver
|
|
227
|
+
# carries on with nil credentials and the user sees an opaque Keystone
|
|
228
|
+
# auth failure that never mentions their config file.
|
|
229
|
+
#
|
|
230
|
+
# @param data [Hash] the parsed document
|
|
231
|
+
# @param name [String] the cloud name to extract
|
|
232
|
+
# @param source [String] the file the document came from, for errors
|
|
233
|
+
# @return [Hash] the cloud entry, or an empty hash if it is absent
|
|
234
|
+
# @raise [Kitchen::ActionFailed] if `clouds` or the named entry is
|
|
235
|
+
# present but is not a mapping
|
|
236
|
+
def extract_cloud(data, name, source)
|
|
237
|
+
clouds = data["clouds"]
|
|
238
|
+
return {} if clouds.nil?
|
|
239
|
+
raise ActionFailed, "The clouds section of #{source} must be a YAML mapping" unless clouds.is_a?(Hash)
|
|
240
|
+
|
|
156
241
|
cloud = clouds[name]
|
|
157
|
-
return {}
|
|
242
|
+
return {} if cloud.nil?
|
|
243
|
+
raise ActionFailed, "Cloud <#{name}> in #{source} must be a YAML mapping" unless cloud.is_a?(Hash)
|
|
158
244
|
|
|
159
245
|
cloud
|
|
160
246
|
end
|
|
161
247
|
|
|
162
|
-
#
|
|
248
|
+
# Recursively merges `override` onto `base` without mutating either.
|
|
249
|
+
#
|
|
250
|
+
# @param base [Hash] the lower-priority hash
|
|
251
|
+
# @param override [Hash] the higher-priority hash
|
|
252
|
+
# @return [Hash] a new merged hash
|
|
163
253
|
def deep_merge(base, override)
|
|
164
254
|
result = base.dup
|
|
165
255
|
override.each do |key, value|
|
|
@@ -172,7 +262,10 @@ module Kitchen
|
|
|
172
262
|
result
|
|
173
263
|
end
|
|
174
264
|
|
|
175
|
-
#
|
|
265
|
+
# Converts a clouds.yaml cloud entry into Fog-compatible config keys.
|
|
266
|
+
#
|
|
267
|
+
# @param cloud [Hash] a single cloud entry
|
|
268
|
+
# @return [Hash{Symbol => Object}] Fog config keys
|
|
176
269
|
def translate_cloud_config(cloud)
|
|
177
270
|
result = {}
|
|
178
271
|
|
|
@@ -196,6 +289,14 @@ module Kitchen
|
|
|
196
289
|
result
|
|
197
290
|
end
|
|
198
291
|
|
|
292
|
+
# Coerces values Fog insists on receiving as strings.
|
|
293
|
+
#
|
|
294
|
+
# YAML parses `identity_api_version: 3` and `project_id: 12345` as
|
|
295
|
+
# Integers, which Fog then fails on.
|
|
296
|
+
#
|
|
297
|
+
# @param fog_key [Symbol] the Fog config key
|
|
298
|
+
# @param value [Object] the raw value from YAML or the environment
|
|
299
|
+
# @return [Object] the value, stringified when the key requires it
|
|
199
300
|
def normalize_config_value(fog_key, value)
|
|
200
301
|
return value unless STRING_CONFIG_KEYS.include?(fog_key)
|
|
201
302
|
|
|
@@ -21,14 +21,66 @@
|
|
|
21
21
|
# See the License for the specific language governing permissions and
|
|
22
22
|
# limitations under the License.
|
|
23
23
|
|
|
24
|
+
require "etc" unless defined?(Etc)
|
|
25
|
+
require "socket" unless defined?(Socket)
|
|
26
|
+
|
|
24
27
|
module Kitchen
|
|
25
28
|
module Driver
|
|
26
29
|
class Openstack < Kitchen::Driver::Base
|
|
27
30
|
# Server naming and configuration helpers
|
|
28
31
|
module Config
|
|
29
|
-
#
|
|
32
|
+
# Longest server name OpenStack will accept without truncating.
|
|
33
|
+
#
|
|
34
|
+
# Every other length in this file is derived from it, so the limit is
|
|
35
|
+
# actually maintained by the code rather than merely documented here.
|
|
36
|
+
#
|
|
37
|
+
# @return [Integer]
|
|
38
|
+
MAX_SERVER_NAME_LENGTH = 63
|
|
39
|
+
|
|
40
|
+
# Number of random characters appended to a user-supplied prefix.
|
|
41
|
+
#
|
|
42
|
+
# @return [Integer]
|
|
43
|
+
PREFIX_SUFFIX_LENGTH = 8
|
|
44
|
+
|
|
45
|
+
# Longest user-supplied prefix that still leaves room for the
|
|
46
|
+
# separator and the random suffix.
|
|
47
|
+
#
|
|
48
|
+
# @return [Integer]
|
|
49
|
+
MAX_PREFIX_LENGTH = MAX_SERVER_NAME_LENGTH - PREFIX_SUFFIX_LENGTH - 1
|
|
50
|
+
|
|
51
|
+
# Character budget for the instance name in a fully generated name.
|
|
52
|
+
#
|
|
53
|
+
# @return [Integer]
|
|
54
|
+
NAME_INSTANCE_LENGTH = 15
|
|
55
|
+
|
|
56
|
+
# Character budget for the username in a fully generated name.
|
|
57
|
+
#
|
|
58
|
+
# @return [Integer]
|
|
59
|
+
NAME_USERNAME_LENGTH = 15
|
|
60
|
+
|
|
61
|
+
# Number of random characters in a fully generated name.
|
|
62
|
+
#
|
|
63
|
+
# @return [Integer]
|
|
64
|
+
NAME_RANDOM_LENGTH = 7
|
|
65
|
+
|
|
66
|
+
# Character budget for the hostname in a fully generated name.
|
|
67
|
+
#
|
|
68
|
+
# Whatever is left once the other three components and the three
|
|
69
|
+
# separators are accounted for.
|
|
70
|
+
#
|
|
71
|
+
# @return [Integer]
|
|
72
|
+
NAME_HOSTNAME_LENGTH =
|
|
73
|
+
MAX_SERVER_NAME_LENGTH - NAME_INSTANCE_LENGTH - NAME_USERNAME_LENGTH - NAME_RANDOM_LENGTH - 3
|
|
74
|
+
|
|
75
|
+
# Sets `config[:server_name]` unless the user already supplied one.
|
|
76
|
+
#
|
|
77
|
+
# Called at the top of {Kitchen::Driver::Openstack#create} rather than
|
|
78
|
+
# at config-finalize time so that the random suffix is generated once
|
|
79
|
+
# per converge instead of once per `kitchen` invocation.
|
|
80
|
+
#
|
|
81
|
+
# @return [String] the resolved server name
|
|
30
82
|
def config_server_name
|
|
31
|
-
return if config[:server_name]
|
|
83
|
+
return config[:server_name] if config[:server_name]
|
|
32
84
|
|
|
33
85
|
config[:server_name] = if config[:server_name_prefix]
|
|
34
86
|
server_name_prefix(config[:server_name_prefix])
|
|
@@ -39,46 +91,61 @@ module Kitchen
|
|
|
39
91
|
|
|
40
92
|
private
|
|
41
93
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# ================
|
|
49
|
-
# Total: 63
|
|
94
|
+
# Generates a unique server name of at most 63 characters.
|
|
95
|
+
#
|
|
96
|
+
# <instance>-<user>-<host>-<random>
|
|
97
|
+
# 15 15 23 7 + 3 separators = 63
|
|
98
|
+
#
|
|
99
|
+
# @return [String] the generated server name
|
|
50
100
|
def default_name
|
|
51
101
|
[
|
|
52
|
-
instance.name.gsub(/\W/, "")[0
|
|
53
|
-
|
|
54
|
-
Socket.gethostname.gsub(/\W/, "")[0
|
|
55
|
-
Array.new(
|
|
102
|
+
instance.name.gsub(/\W/, "")[0, NAME_INSTANCE_LENGTH],
|
|
103
|
+
current_username.gsub(/\W/, "")[0, NAME_USERNAME_LENGTH],
|
|
104
|
+
Socket.gethostname.gsub(/\W/, "")[0, NAME_HOSTNAME_LENGTH],
|
|
105
|
+
Array.new(NAME_RANDOM_LENGTH) { rand(36).to_s(36) }.join,
|
|
56
106
|
].join("-")
|
|
57
107
|
end
|
|
58
108
|
|
|
109
|
+
# Best-effort lookup of the name of the user running Test Kitchen.
|
|
110
|
+
#
|
|
111
|
+
# `Etc.getpwuid` raises `ArgumentError` rather than returning nil when
|
|
112
|
+
# the effective uid has no passwd entry, which is routine inside
|
|
113
|
+
# containers, so both failure modes fall through to `Etc.getlogin` and
|
|
114
|
+
# finally to a placeholder.
|
|
115
|
+
#
|
|
116
|
+
# @return [String] a username, or `"nologin"` if none can be determined
|
|
117
|
+
def current_username
|
|
118
|
+
(Etc.getpwuid&.name || Etc.getlogin || "nologin")
|
|
119
|
+
rescue ArgumentError
|
|
120
|
+
Etc.getlogin || "nologin"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Generates a unique server name from a user-supplied prefix.
|
|
124
|
+
#
|
|
125
|
+
# <prefix>-<random>
|
|
126
|
+
# max 54 8 + 1 separator = 63
|
|
127
|
+
#
|
|
128
|
+
# Falls back to {#default_name} when the prefix contains nothing usable
|
|
129
|
+
# once non-word characters are stripped.
|
|
130
|
+
#
|
|
131
|
+
# @param server_name_prefix [String] the configured prefix; never
|
|
132
|
+
# mutated, so the caller's config survives intact
|
|
133
|
+
# @return [String] the generated server name
|
|
59
134
|
def server_name_prefix(server_name_prefix)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# Separator: 1
|
|
65
|
-
# Random string: 8
|
|
66
|
-
# ===================
|
|
67
|
-
# Max: 63
|
|
68
|
-
#
|
|
69
|
-
if server_name_prefix.length > 54
|
|
70
|
-
warn "Server name prefix too long, truncated to 54 characters"
|
|
71
|
-
server_name_prefix = server_name_prefix[0..53]
|
|
135
|
+
prefix = server_name_prefix.to_s
|
|
136
|
+
if prefix.length > MAX_PREFIX_LENGTH
|
|
137
|
+
warn "Server name prefix too long, truncated to #{MAX_PREFIX_LENGTH} characters"
|
|
138
|
+
prefix = prefix[0, MAX_PREFIX_LENGTH]
|
|
72
139
|
end
|
|
73
140
|
|
|
74
|
-
|
|
141
|
+
prefix = prefix.gsub(/\W/, "")
|
|
75
142
|
|
|
76
|
-
if
|
|
143
|
+
if prefix.empty?
|
|
77
144
|
warn "Server name prefix empty or invalid; using fully generated name"
|
|
78
145
|
default_name
|
|
79
146
|
else
|
|
80
|
-
random_suffix = ("a".."z").to_a.sample(
|
|
81
|
-
|
|
147
|
+
random_suffix = ("a".."z").to_a.sample(PREFIX_SUFFIX_LENGTH).join
|
|
148
|
+
"#{prefix}-#{random_suffix}"
|
|
82
149
|
end
|
|
83
150
|
end
|
|
84
151
|
end
|
|
@@ -28,8 +28,20 @@ module Kitchen
|
|
|
28
28
|
class Openstack < Kitchen::Driver::Base
|
|
29
29
|
# Ohai hints, SSL handling, and server wait helpers
|
|
30
30
|
module Helpers
|
|
31
|
+
# Seconds between progress dots while counting down.
|
|
32
|
+
#
|
|
33
|
+
# @return [Integer]
|
|
34
|
+
COUNTDOWN_TICK = 10
|
|
35
|
+
|
|
31
36
|
private
|
|
32
37
|
|
|
38
|
+
# Drops an empty `openstack.json` ohai hint on the new instance so
|
|
39
|
+
# that Chef Infra's ohai run picks up OpenStack metadata.
|
|
40
|
+
#
|
|
41
|
+
# Does nothing on platforms that are neither Bourne-shell nor Windows.
|
|
42
|
+
#
|
|
43
|
+
# @param state [Hash] instance state, used to open a transport session
|
|
44
|
+
# @return [void]
|
|
33
45
|
def add_ohai_hint(state)
|
|
34
46
|
if bourne_shell?
|
|
35
47
|
info "Adding OpenStack hint for ohai"
|
|
@@ -45,18 +57,40 @@ module Kitchen
|
|
|
45
57
|
instance.transport.connection(state).execute(
|
|
46
58
|
"#{touch_cmd} #{touch_cmd_args}"
|
|
47
59
|
)
|
|
60
|
+
else
|
|
61
|
+
debug "Unknown platform shell; skipping the OpenStack ohai hint"
|
|
48
62
|
end
|
|
49
63
|
end
|
|
50
64
|
|
|
65
|
+
# The directory ohai reads hint files from.
|
|
66
|
+
#
|
|
67
|
+
# @return [String] the first configured ohai hints path
|
|
51
68
|
def hints_path
|
|
52
69
|
Ohai.config[:hints_path][0]
|
|
53
70
|
end
|
|
54
71
|
|
|
72
|
+
# Turns off TLS peer verification for every subsequent Excon request in
|
|
73
|
+
# this process.
|
|
74
|
+
#
|
|
75
|
+
# Called when the `disable_ssl_validation` config key is set, which the
|
|
76
|
+
# user sets directly in kitchen.yml or which is inferred from a
|
|
77
|
+
# `verify: false` entry in clouds.yaml.
|
|
78
|
+
#
|
|
79
|
+
# @return [void]
|
|
55
80
|
def disable_ssl_validation
|
|
56
81
|
require "excon" unless defined?(Excon)
|
|
57
82
|
Excon.defaults[:ssl_verify_peer] = false
|
|
58
83
|
end
|
|
59
84
|
|
|
85
|
+
# Blocks until the transport reports the instance is reachable,
|
|
86
|
+
# destroying the instance if it never comes up.
|
|
87
|
+
#
|
|
88
|
+
# A server we cannot reach is a server we cannot clean up later, so the
|
|
89
|
+
# failure path tears it down before re-raising rather than leaking it.
|
|
90
|
+
#
|
|
91
|
+
# @param state [Hash] instance state, containing `:hostname`
|
|
92
|
+
# @return [void]
|
|
93
|
+
# @raise [StandardError] whatever the transport raised, after cleanup
|
|
60
94
|
def wait_for_server(state)
|
|
61
95
|
if config[:server_wait]
|
|
62
96
|
info "Sleeping for #{config[:server_wait]} seconds to let your server start up..."
|
|
@@ -64,17 +98,22 @@ module Kitchen
|
|
|
64
98
|
end
|
|
65
99
|
info "Waiting for server to be ready..."
|
|
66
100
|
instance.transport.connection(state).wait_until_ready
|
|
67
|
-
rescue
|
|
68
|
-
error "Server #{state[:hostname]} (#{state[:server_id]}) not reachable. Destroying server..."
|
|
101
|
+
rescue => e
|
|
102
|
+
error "Server #{state[:hostname]} (#{state[:server_id]}) not reachable: #{e.message}. Destroying server..."
|
|
69
103
|
destroy(state)
|
|
70
104
|
raise
|
|
71
105
|
end
|
|
72
106
|
|
|
107
|
+
# Prints a progress dot every {COUNTDOWN_TICK} seconds for the given
|
|
108
|
+
# duration.
|
|
109
|
+
#
|
|
110
|
+
# @param seconds [Integer] how long to wait
|
|
111
|
+
# @return [void]
|
|
73
112
|
def countdown(seconds)
|
|
74
|
-
|
|
75
|
-
while Time.now <
|
|
113
|
+
finish_at = Time.now + seconds
|
|
114
|
+
while Time.now < finish_at
|
|
76
115
|
Kernel.print "."
|
|
77
|
-
sleep
|
|
116
|
+
sleep COUNTDOWN_TICK
|
|
78
117
|
end
|
|
79
118
|
end
|
|
80
119
|
end
|