test-kitchen 1.7.0 → 1.7.1.dev
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/.cane +8 -8
- data/.gitattributes +3 -0
- data/.github/ISSUE_TEMPLATE.md +55 -55
- data/.gitignore +28 -28
- data/.kitchen.ci.yml +23 -23
- data/.kitchen.proxy.yml +27 -27
- data/.rubocop.yml +3 -3
- data/.travis.yml +70 -70
- data/.yardopts +3 -3
- data/Berksfile +3 -3
- data/CHANGELOG.md +1090 -1083
- data/CONTRIBUTING.md +14 -14
- data/Gemfile +19 -19
- data/Gemfile.proxy_tests +4 -4
- data/Guardfile +42 -42
- data/LICENSE +15 -15
- data/MAINTAINERS.md +23 -23
- data/README.md +135 -135
- data/Rakefile +61 -61
- data/appveyor.yml +44 -44
- data/features/kitchen_action_commands.feature +164 -164
- data/features/kitchen_command.feature +16 -16
- data/features/kitchen_console_command.feature +34 -34
- data/features/kitchen_defaults.feature +38 -38
- data/features/kitchen_diagnose_command.feature +96 -96
- data/features/kitchen_driver_create_command.feature +64 -64
- data/features/kitchen_driver_discover_command.feature +25 -25
- data/features/kitchen_help_command.feature +16 -16
- data/features/kitchen_init_command.feature +274 -274
- data/features/kitchen_list_command.feature +104 -104
- data/features/kitchen_login_command.feature +62 -62
- data/features/kitchen_sink_command.feature +30 -30
- data/features/kitchen_test_command.feature +88 -88
- data/features/step_definitions/gem_steps.rb +36 -36
- data/features/step_definitions/git_steps.rb +5 -5
- data/features/step_definitions/output_steps.rb +5 -5
- data/features/support/env.rb +75 -75
- data/lib/kitchen.rb +150 -150
- data/lib/kitchen/base64_stream.rb +55 -55
- data/lib/kitchen/cli.rb +419 -419
- data/lib/kitchen/collection.rb +55 -55
- data/lib/kitchen/color.rb +65 -65
- data/lib/kitchen/command.rb +185 -185
- data/lib/kitchen/command/action.rb +45 -45
- data/lib/kitchen/command/console.rb +58 -58
- data/lib/kitchen/command/diagnose.rb +92 -92
- data/lib/kitchen/command/driver_discover.rb +105 -105
- data/lib/kitchen/command/exec.rb +41 -41
- data/lib/kitchen/command/list.rb +119 -119
- data/lib/kitchen/command/login.rb +43 -43
- data/lib/kitchen/command/sink.rb +54 -54
- data/lib/kitchen/command/test.rb +51 -51
- data/lib/kitchen/config.rb +322 -322
- data/lib/kitchen/configurable.rb +529 -529
- data/lib/kitchen/data_munger.rb +959 -959
- data/lib/kitchen/diagnostic.rb +141 -141
- data/lib/kitchen/driver.rb +56 -56
- data/lib/kitchen/driver/base.rb +134 -134
- data/lib/kitchen/driver/dummy.rb +108 -108
- data/lib/kitchen/driver/proxy.rb +72 -72
- data/lib/kitchen/driver/ssh_base.rb +357 -357
- data/lib/kitchen/errors.rb +229 -229
- data/lib/kitchen/generator/driver_create.rb +177 -177
- data/lib/kitchen/generator/init.rb +296 -296
- data/lib/kitchen/instance.rb +662 -662
- data/lib/kitchen/lazy_hash.rb +142 -142
- data/lib/kitchen/loader/yaml.rb +349 -349
- data/lib/kitchen/logger.rb +423 -423
- data/lib/kitchen/logging.rb +56 -56
- data/lib/kitchen/login_command.rb +52 -52
- data/lib/kitchen/metadata_chopper.rb +52 -52
- data/lib/kitchen/platform.rb +67 -67
- data/lib/kitchen/provisioner.rb +54 -54
- data/lib/kitchen/provisioner/base.rb +236 -236
- data/lib/kitchen/provisioner/chef/berkshelf.rb +114 -114
- data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -322
- data/lib/kitchen/provisioner/chef/librarian.rb +112 -112
- data/lib/kitchen/provisioner/chef_apply.rb +124 -124
- data/lib/kitchen/provisioner/chef_base.rb +341 -341
- data/lib/kitchen/provisioner/chef_solo.rb +88 -88
- data/lib/kitchen/provisioner/chef_zero.rb +245 -245
- data/lib/kitchen/provisioner/dummy.rb +79 -79
- data/lib/kitchen/provisioner/shell.rb +138 -138
- data/lib/kitchen/rake_tasks.rb +63 -63
- data/lib/kitchen/shell_out.rb +93 -93
- data/lib/kitchen/ssh.rb +276 -276
- data/lib/kitchen/state_file.rb +120 -120
- data/lib/kitchen/suite.rb +51 -51
- data/lib/kitchen/thor_tasks.rb +66 -66
- data/lib/kitchen/transport.rb +54 -54
- data/lib/kitchen/transport/base.rb +176 -176
- data/lib/kitchen/transport/dummy.rb +79 -79
- data/lib/kitchen/transport/ssh.rb +364 -364
- data/lib/kitchen/transport/winrm.rb +486 -486
- data/lib/kitchen/util.rb +147 -147
- data/lib/kitchen/verifier.rb +55 -55
- data/lib/kitchen/verifier/base.rb +235 -235
- data/lib/kitchen/verifier/busser.rb +277 -277
- data/lib/kitchen/verifier/dummy.rb +79 -79
- data/lib/kitchen/verifier/shell.rb +101 -101
- data/lib/kitchen/version.rb +21 -21
- data/lib/vendor/hash_recursive_merge.rb +82 -82
- data/spec/kitchen/base64_stream_spec.rb +77 -77
- data/spec/kitchen/cli_spec.rb +56 -56
- data/spec/kitchen/collection_spec.rb +80 -80
- data/spec/kitchen/color_spec.rb +54 -54
- data/spec/kitchen/config_spec.rb +408 -408
- data/spec/kitchen/configurable_spec.rb +1095 -1095
- data/spec/kitchen/data_munger_spec.rb +2694 -2694
- data/spec/kitchen/diagnostic_spec.rb +129 -129
- data/spec/kitchen/driver/base_spec.rb +121 -121
- data/spec/kitchen/driver/dummy_spec.rb +199 -199
- data/spec/kitchen/driver/proxy_spec.rb +138 -138
- data/spec/kitchen/driver/ssh_base_spec.rb +1115 -1115
- data/spec/kitchen/driver_spec.rb +112 -112
- data/spec/kitchen/errors_spec.rb +309 -309
- data/spec/kitchen/instance_spec.rb +1419 -1419
- data/spec/kitchen/lazy_hash_spec.rb +117 -117
- data/spec/kitchen/loader/yaml_spec.rb +774 -774
- data/spec/kitchen/logger_spec.rb +429 -429
- data/spec/kitchen/logging_spec.rb +59 -59
- data/spec/kitchen/login_command_spec.rb +68 -68
- data/spec/kitchen/metadata_chopper_spec.rb +82 -82
- data/spec/kitchen/platform_spec.rb +89 -89
- data/spec/kitchen/provisioner/base_spec.rb +386 -386
- data/spec/kitchen/provisioner/chef_apply_spec.rb +136 -136
- data/spec/kitchen/provisioner/chef_base_spec.rb +1161 -1161
- data/spec/kitchen/provisioner/chef_solo_spec.rb +557 -557
- data/spec/kitchen/provisioner/chef_zero_spec.rb +1001 -1001
- data/spec/kitchen/provisioner/dummy_spec.rb +99 -99
- data/spec/kitchen/provisioner/shell_spec.rb +566 -566
- data/spec/kitchen/provisioner_spec.rb +107 -107
- data/spec/kitchen/shell_out_spec.rb +150 -150
- data/spec/kitchen/ssh_spec.rb +693 -693
- data/spec/kitchen/state_file_spec.rb +129 -129
- data/spec/kitchen/suite_spec.rb +62 -62
- data/spec/kitchen/transport/base_spec.rb +89 -89
- data/spec/kitchen/transport/ssh_spec.rb +1255 -1255
- data/spec/kitchen/transport/winrm_spec.rb +1143 -1143
- data/spec/kitchen/transport_spec.rb +112 -112
- data/spec/kitchen/util_spec.rb +165 -165
- data/spec/kitchen/verifier/base_spec.rb +362 -362
- data/spec/kitchen/verifier/busser_spec.rb +610 -610
- data/spec/kitchen/verifier/dummy_spec.rb +99 -99
- data/spec/kitchen/verifier/shell_spec.rb +160 -160
- data/spec/kitchen/verifier_spec.rb +120 -120
- data/spec/kitchen_spec.rb +114 -114
- data/spec/spec_helper.rb +85 -85
- data/spec/support/powershell_max_size_spec.rb +40 -40
- data/support/busser_install_command.ps1 +14 -14
- data/support/busser_install_command.sh +14 -14
- data/support/chef-client-zero.rb +77 -77
- data/support/chef_base_init_command.ps1 +18 -18
- data/support/chef_base_init_command.sh +2 -2
- data/support/chef_base_install_command.ps1 +85 -85
- data/support/chef_base_install_command.sh +229 -229
- data/support/chef_zero_prepare_command_legacy.ps1 +9 -9
- data/support/chef_zero_prepare_command_legacy.sh +10 -10
- data/support/download_helpers.sh +109 -109
- data/support/dummy-validation.pem +27 -27
- data/templates/driver/CHANGELOG.md.erb +3 -3
- data/templates/driver/Gemfile.erb +3 -3
- data/templates/driver/README.md.erb +64 -64
- data/templates/driver/Rakefile.erb +21 -21
- data/templates/driver/driver.rb.erb +23 -23
- data/templates/driver/gemspec.erb +29 -29
- data/templates/driver/gitignore.erb +17 -17
- data/templates/driver/license_apachev2.erb +15 -15
- data/templates/driver/license_lgplv3.erb +16 -16
- data/templates/driver/license_mit.erb +22 -22
- data/templates/driver/license_reserved.erb +5 -5
- data/templates/driver/tailor.erb +4 -4
- data/templates/driver/travis.yml.erb +11 -11
- data/templates/driver/version.rb.erb +12 -12
- data/templates/init/chefignore.erb +1 -1
- data/templates/init/kitchen.yml.erb +18 -18
- data/test-kitchen.gemspec +62 -62
- data/test/integration/default/default_spec.rb +3 -3
- data/testing_windows.md +37 -37
- metadata +5 -4
data/lib/kitchen/configurable.rb
CHANGED
|
@@ -1,529 +1,529 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2014, Fletcher Nichol
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
# you may not use this file except in compliance with the License.
|
|
9
|
-
# You may obtain a copy of the License at
|
|
10
|
-
#
|
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
#
|
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
# See the License for the specific language governing permissions and
|
|
17
|
-
# limitations under the License.
|
|
18
|
-
|
|
19
|
-
require "thor/util"
|
|
20
|
-
|
|
21
|
-
require "kitchen/lazy_hash"
|
|
22
|
-
|
|
23
|
-
module Kitchen
|
|
24
|
-
|
|
25
|
-
# A mixin for providing configuration-related behavior such as default
|
|
26
|
-
# config (static, computed, inherited), required config, local path
|
|
27
|
-
# expansion, etc.
|
|
28
|
-
#
|
|
29
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
30
|
-
module Configurable
|
|
31
|
-
|
|
32
|
-
def self.included(base)
|
|
33
|
-
base.extend(ClassMethods)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# @return [Kitchen::Instance] the associated instance
|
|
37
|
-
attr_reader :instance
|
|
38
|
-
|
|
39
|
-
# A lifecycle method that should be invoked when the object is about ready
|
|
40
|
-
# to be used. A reference to an Instance is required as configuration
|
|
41
|
-
# dependant data may be access through an Instance. This also acts as a
|
|
42
|
-
# hook point where the object may wish to perform other last minute
|
|
43
|
-
# checks, validations, or configuration expansions.
|
|
44
|
-
#
|
|
45
|
-
# @param instance [Instance] an associated instance
|
|
46
|
-
# @return [self] itself, for use in chaining
|
|
47
|
-
# @raise [ClientError] if instance parameter is nil
|
|
48
|
-
def finalize_config!(instance)
|
|
49
|
-
if instance.nil?
|
|
50
|
-
raise ClientError, "Instance must be provided to #{self}"
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
@instance = instance
|
|
54
|
-
expand_paths!
|
|
55
|
-
validate_config!
|
|
56
|
-
load_needed_dependencies!
|
|
57
|
-
|
|
58
|
-
self
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Provides hash-like access to configuration keys.
|
|
62
|
-
#
|
|
63
|
-
# @param attr [Object] configuration key
|
|
64
|
-
# @return [Object] value at configuration key
|
|
65
|
-
def [](attr)
|
|
66
|
-
config[attr]
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# @return [TrueClass,FalseClass] true if `:shell_type` is `"bourne"` (or
|
|
70
|
-
# unset, for backwards compatability)
|
|
71
|
-
def bourne_shell?
|
|
72
|
-
["bourne", nil].include?(instance.platform.shell_type)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Find an appropriate path to a file or directory, based on graceful
|
|
76
|
-
# fallback rules or returns nil if path cannot be determined.
|
|
77
|
-
#
|
|
78
|
-
# Given an instance with suite named `"server"`, a `test_base_path` of
|
|
79
|
-
# `"/a/b"`, and a path segement of `"roles"` then following will be tried
|
|
80
|
-
# in order (first match that exists wins):
|
|
81
|
-
#
|
|
82
|
-
# 1. /a/b/server/roles
|
|
83
|
-
# 2. /a/b/roles
|
|
84
|
-
# 3. $PWD/roles
|
|
85
|
-
#
|
|
86
|
-
# @param path [String] the base path segment to search for
|
|
87
|
-
# @param opts [Hash] options
|
|
88
|
-
# @option opts [Symbol] :type either `:file` or `:directory` (default)
|
|
89
|
-
# @option opts [Symbol] :base_path a custom base path to search under,
|
|
90
|
-
# default uses value from `config[:test_base_path]`
|
|
91
|
-
# @return [String] path to the existing file or directory, or nil if file
|
|
92
|
-
# or directory was not found
|
|
93
|
-
# @raise [UserError] if `config[:test_base_path]` is used and is not set
|
|
94
|
-
def calculate_path(path, opts = {})
|
|
95
|
-
type = opts.fetch(:type, :directory)
|
|
96
|
-
base = opts.fetch(:base_path) do
|
|
97
|
-
config.fetch(:test_base_path) do |key|
|
|
98
|
-
raise UserError, "#{key} is not found in #{self}"
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
[
|
|
103
|
-
File.join(base, instance.suite.name, path),
|
|
104
|
-
File.join(base, path),
|
|
105
|
-
File.join(Dir.pwd, path)
|
|
106
|
-
].find do |candidate|
|
|
107
|
-
type == :directory ? File.directory?(candidate) : File.file?(candidate)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# Returns an array of configuration keys.
|
|
112
|
-
#
|
|
113
|
-
# @return [Array] array of configuration keys
|
|
114
|
-
def config_keys
|
|
115
|
-
config.keys
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Returns a Hash of configuration and other useful diagnostic information.
|
|
119
|
-
#
|
|
120
|
-
# @return [Hash] a diagnostic hash
|
|
121
|
-
def diagnose
|
|
122
|
-
result = Hash.new
|
|
123
|
-
config_keys.sort.each { |k| result[k] = config[k] }
|
|
124
|
-
result
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Returns a Hash of configuration and other useful diagnostic information
|
|
128
|
-
# associated with the plugin itself (such as loaded version, class name,
|
|
129
|
-
# etc.).
|
|
130
|
-
#
|
|
131
|
-
# @return [Hash] a diagnostic hash
|
|
132
|
-
def diagnose_plugin
|
|
133
|
-
result = Hash.new
|
|
134
|
-
result[:name] = name
|
|
135
|
-
result.merge!(self.class.diagnose)
|
|
136
|
-
result
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Returns the name of this plugin, suitable for display in a CLI.
|
|
140
|
-
#
|
|
141
|
-
# @return [String] name of this plugin
|
|
142
|
-
def name
|
|
143
|
-
self.class.name.split("::").last
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# @return [TrueClass,FalseClass] true if `:shell_type` is `"powershell"`
|
|
147
|
-
def powershell_shell?
|
|
148
|
-
["powershell"].include?(instance.platform.shell_type)
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# Builds a file path based on the `:os_type` (`"windows"` or `"unix"`).
|
|
152
|
-
#
|
|
153
|
-
# @return [String] joined path for instance's os_type
|
|
154
|
-
def remote_path_join(*parts)
|
|
155
|
-
path = File.join(*parts)
|
|
156
|
-
windows_os? ? path.gsub("/", "\\") : path.gsub("\\", "/")
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# @return [TrueClass,FalseClass] true if `:os_type` is `"unix"` (or
|
|
160
|
-
# unset, for backwards compatibility)
|
|
161
|
-
def unix_os?
|
|
162
|
-
["unix", nil].include?(instance.platform.os_type)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Performs whatever tests that may be required to ensure that this plugin
|
|
166
|
-
# will be able to function in the current environment. This may involve
|
|
167
|
-
# checking for the presence of certain directories, software installed,
|
|
168
|
-
# etc.
|
|
169
|
-
#
|
|
170
|
-
# @raise [UserError] if the plugin will not be able to perform or if a
|
|
171
|
-
# documented dependency is missing from the system
|
|
172
|
-
def verify_dependencies
|
|
173
|
-
# this method may be left unimplemented if that is applicable
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
# @return [TrueClass,FalseClass] true if `:os_type` is `"windows"`
|
|
177
|
-
def windows_os?
|
|
178
|
-
["windows"].include?(instance.platform.os_type)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
private
|
|
182
|
-
|
|
183
|
-
# @return [LzayHash] a configuration hash
|
|
184
|
-
# @api private
|
|
185
|
-
attr_reader :config
|
|
186
|
-
|
|
187
|
-
# Initializes an internal configuration hash. The hash may contain
|
|
188
|
-
# callable blocks as values that are meant to be called lazily. This
|
|
189
|
-
# method is intended to be included in an object's .initialize method.
|
|
190
|
-
#
|
|
191
|
-
# @param config [Hash] initial provided configuration
|
|
192
|
-
# @api private
|
|
193
|
-
def init_config(config)
|
|
194
|
-
@config = LazyHash.new(config, self)
|
|
195
|
-
self.class.defaults.each do |attr, value|
|
|
196
|
-
@config[attr] = value unless @config.key?(attr)
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Expands file paths for certain configuration values. A configuration
|
|
201
|
-
# value is marked for file expansion with a expand_path_for declaration
|
|
202
|
-
# in the included class.
|
|
203
|
-
#
|
|
204
|
-
# @api private
|
|
205
|
-
def expand_paths!
|
|
206
|
-
root_path = config[:kitchen_root] || Dir.pwd
|
|
207
|
-
expanded_paths = LazyHash.new(self.class.expanded_paths, self).to_hash
|
|
208
|
-
|
|
209
|
-
expanded_paths.each do |key, should_expand|
|
|
210
|
-
next if !should_expand || config[key].nil? || config[key] == false
|
|
211
|
-
|
|
212
|
-
config[key] = if config[key].is_a?(Array)
|
|
213
|
-
config[key].map { |path| File.expand_path(path, root_path) }
|
|
214
|
-
else
|
|
215
|
-
File.expand_path(config[key], root_path)
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Loads any required third party Ruby libraries or runs any shell out
|
|
221
|
-
# commands to prepare the plugin. This method will be called in the
|
|
222
|
-
# context of the main thread of execution and so does not necessarily
|
|
223
|
-
# have to be thread safe.
|
|
224
|
-
#
|
|
225
|
-
# **Note:** any subclasses overriding this method would be well advised
|
|
226
|
-
# to call super when overriding this method, for example:
|
|
227
|
-
#
|
|
228
|
-
# @example overriding `#load_needed_dependencies!`
|
|
229
|
-
#
|
|
230
|
-
# class MyProvisioner < Kitchen::Provisioner::Base
|
|
231
|
-
# def load_needed_dependencies!
|
|
232
|
-
# super
|
|
233
|
-
# # any further work
|
|
234
|
-
# end
|
|
235
|
-
# end
|
|
236
|
-
#
|
|
237
|
-
# @raise [ClientError] if any library loading fails or any of the
|
|
238
|
-
# dependency requirements cannot be satisfied
|
|
239
|
-
# @api private
|
|
240
|
-
def load_needed_dependencies!
|
|
241
|
-
# this method may be left unimplemented if that is applicable
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# @return [Logger] the instance's logger or Test Kitchen's common logger
|
|
245
|
-
# otherwise
|
|
246
|
-
# @api private
|
|
247
|
-
def logger
|
|
248
|
-
instance ? instance.logger : Kitchen.logger
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
# Builds a shell environment variable assignment string for the
|
|
252
|
-
# required shell type.
|
|
253
|
-
#
|
|
254
|
-
# @param name [String] variable name
|
|
255
|
-
# @param value [String] variable value
|
|
256
|
-
# @return [String] shell variable assignment
|
|
257
|
-
# @api private
|
|
258
|
-
def shell_env_var(name, value)
|
|
259
|
-
if powershell_shell?
|
|
260
|
-
shell_var("env:#{name}", value)
|
|
261
|
-
else
|
|
262
|
-
"#{shell_var(name, value)}; export #{name}"
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# Builds a shell variable assignment string for the required shell type.
|
|
267
|
-
#
|
|
268
|
-
# @param name [String] variable name
|
|
269
|
-
# @param value [String] variable value
|
|
270
|
-
# @return [String] shell variable assignment
|
|
271
|
-
# @api private
|
|
272
|
-
def shell_var(name, value)
|
|
273
|
-
if powershell_shell?
|
|
274
|
-
%{$#{name} = "#{value}"}
|
|
275
|
-
else
|
|
276
|
-
%{#{name}="#{value}"}
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# Runs all validations set up for the included class. Each validation is
|
|
281
|
-
# for a specific configuration attribute and has an associated callable
|
|
282
|
-
# block. Each validation block is called with the attribute, its value,
|
|
283
|
-
# and the included object for context.
|
|
284
|
-
#
|
|
285
|
-
# @api private
|
|
286
|
-
def validate_config!
|
|
287
|
-
self.class.validations.each do |attr, block|
|
|
288
|
-
block.call(attr, config[attr], self)
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
# Wraps a body of shell code with common context appropriate for the type
|
|
293
|
-
# of shell.
|
|
294
|
-
#
|
|
295
|
-
# @param code [String] the shell code to be wrapped
|
|
296
|
-
# @return [String] wrapped shell code
|
|
297
|
-
# @api private
|
|
298
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
299
|
-
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
300
|
-
def wrap_shell_code(code)
|
|
301
|
-
env = []
|
|
302
|
-
if config[:http_proxy] && !config[:http_proxy].empty?
|
|
303
|
-
env << shell_env_var("http_proxy", config[:http_proxy])
|
|
304
|
-
env << shell_env_var("HTTP_PROXY", config[:http_proxy])
|
|
305
|
-
else
|
|
306
|
-
export_proxy(env, "http")
|
|
307
|
-
end
|
|
308
|
-
if config[:https_proxy] && !config[:https_proxy].empty?
|
|
309
|
-
env << shell_env_var("https_proxy", config[:https_proxy])
|
|
310
|
-
env << shell_env_var("HTTPS_PROXY", config[:https_proxy])
|
|
311
|
-
else
|
|
312
|
-
export_proxy(env, "https")
|
|
313
|
-
end
|
|
314
|
-
if config[:ftp_proxy] && !config[:ftp_proxy].empty?
|
|
315
|
-
env << shell_env_var("ftp_proxy", config[:ftp_proxy])
|
|
316
|
-
env << shell_env_var("FTP_PROXY", config[:ftp_proxy])
|
|
317
|
-
else
|
|
318
|
-
export_proxy(env, "ftp")
|
|
319
|
-
end
|
|
320
|
-
# if http_proxy was set from environment variable or https_proxy was set
|
|
321
|
-
# from environment variable, or ftp_proxy was set from environment
|
|
322
|
-
# variable, include no_proxy environment variable, if set.
|
|
323
|
-
if (!config[:http_proxy] && (ENV["http_proxy"] || ENV["HTTP_PROXY"])) ||
|
|
324
|
-
(!config[:https_proxy] && (ENV["https_proxy"] || ENV["HTTPS_PROXY"])) ||
|
|
325
|
-
(!config[:ftp_proxy] && (ENV["ftp_proxy"] || ENV["FTP_PROXY"]))
|
|
326
|
-
env << shell_env_var("no_proxy", ENV["no_proxy"]) if ENV["no_proxy"]
|
|
327
|
-
env << shell_env_var("NO_PROXY", ENV["NO_PROXY"]) if ENV["NO_PROXY"]
|
|
328
|
-
end
|
|
329
|
-
if powershell_shell?
|
|
330
|
-
env.join("\n").concat("\n").concat(code)
|
|
331
|
-
else
|
|
332
|
-
Util.wrap_command(env.join("\n").concat("\n").concat(code))
|
|
333
|
-
end
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
# Helper method to export
|
|
337
|
-
#
|
|
338
|
-
# @param env [Array] the environment to modify
|
|
339
|
-
# @param code [String] the type of proxy to export, one of 'http', 'https' or 'ftp'
|
|
340
|
-
# @api private
|
|
341
|
-
def export_proxy(env, type)
|
|
342
|
-
env << shell_env_var("#{type}_proxy", ENV["#{type}_proxy"]) if ENV["#{type}_proxy"]
|
|
343
|
-
env << shell_env_var("#{type.upcase}_PROXY", ENV["#{type.upcase}_PROXY"]) if
|
|
344
|
-
ENV["#{type.upcase}_PROXY"]
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
# Class methods which will be mixed in on inclusion of Configurable module.
|
|
348
|
-
module ClassMethods
|
|
349
|
-
|
|
350
|
-
# Sets the loaded version of this plugin, usually corresponding to the
|
|
351
|
-
# RubyGems version of the plugin's library. If the plugin does not set
|
|
352
|
-
# this value, then `nil` will be used and reported.
|
|
353
|
-
#
|
|
354
|
-
# @example setting a version used by RubyGems
|
|
355
|
-
#
|
|
356
|
-
# require "kitchen/driver/vagrant_version"
|
|
357
|
-
#
|
|
358
|
-
# module Kitchen
|
|
359
|
-
# module Driver
|
|
360
|
-
# class Vagrant < Kitchen::Driver::Base
|
|
361
|
-
#
|
|
362
|
-
# plugin_version Kitchen::Driver::VAGRANT_VERSION
|
|
363
|
-
#
|
|
364
|
-
# end
|
|
365
|
-
# end
|
|
366
|
-
# end
|
|
367
|
-
#
|
|
368
|
-
# @param version [String] a version string
|
|
369
|
-
def plugin_version(version) # rubocop:disable Style/TrivialAccessors
|
|
370
|
-
@plugin_version = version
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
# Returns a Hash of configuration and other useful diagnostic
|
|
374
|
-
# information.
|
|
375
|
-
#
|
|
376
|
-
# @return [Hash] a diagnostic hash
|
|
377
|
-
def diagnose
|
|
378
|
-
{
|
|
379
|
-
:class => name,
|
|
380
|
-
:version => @plugin_version,
|
|
381
|
-
:api_version => @api_version
|
|
382
|
-
}
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
# Sets a sane default value for a configuration attribute. These values
|
|
386
|
-
# can be overridden by provided configuration or in a subclass with
|
|
387
|
-
# another default_config declaration.
|
|
388
|
-
#
|
|
389
|
-
# @example a nil default value
|
|
390
|
-
#
|
|
391
|
-
# default_config :i_am_nil
|
|
392
|
-
#
|
|
393
|
-
# @example a primitive default value
|
|
394
|
-
#
|
|
395
|
-
# default_config :use_sudo, true
|
|
396
|
-
#
|
|
397
|
-
# @example a block to compute a default value
|
|
398
|
-
#
|
|
399
|
-
# default_config :box_name do |subject|
|
|
400
|
-
# subject.instance.platform.name
|
|
401
|
-
# end
|
|
402
|
-
#
|
|
403
|
-
# @param attr [String] configuration attribute name
|
|
404
|
-
# @param value [Object, nil] static default value for attribute
|
|
405
|
-
# @yieldparam object [Object] a reference to the instantiated object
|
|
406
|
-
# @yieldreturn [Object, nil] dynamically computed value for the attribute
|
|
407
|
-
def default_config(attr, value = nil, &block)
|
|
408
|
-
defaults[attr] = block_given? ? block : value
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
# Ensures that an attribute which is a path will be fully expanded at
|
|
412
|
-
# the right time. This helps make the configuration unambiguous and much
|
|
413
|
-
# easier to debug and diagnose.
|
|
414
|
-
#
|
|
415
|
-
# Note that the file path expansion is only intended for paths on the
|
|
416
|
-
# local workstation invking the Test Kitchen code.
|
|
417
|
-
#
|
|
418
|
-
# @example the default usage
|
|
419
|
-
#
|
|
420
|
-
# expand_path_for :data_path
|
|
421
|
-
#
|
|
422
|
-
# @example disabling path expansion with a falsey value
|
|
423
|
-
#
|
|
424
|
-
# expand_path_for :relative_path, false
|
|
425
|
-
#
|
|
426
|
-
# @example using a block to determine whether or not to expand
|
|
427
|
-
#
|
|
428
|
-
# expand_path_for :relative_or_not_path do |subject|
|
|
429
|
-
# subject.instance.name =~ /default/
|
|
430
|
-
# end
|
|
431
|
-
#
|
|
432
|
-
# @param attr [String] configuration attribute name
|
|
433
|
-
# @param value [Object, nil] whether or not to exand the file path
|
|
434
|
-
# @yieldparam object [Object] a reference to the instantiated object
|
|
435
|
-
# @yieldreturn [Object, nil] dynamically compute whether or not to
|
|
436
|
-
# perform the file expansion
|
|
437
|
-
def expand_path_for(attr, value = true, &block)
|
|
438
|
-
expanded_paths[attr] = block_given? ? block : value
|
|
439
|
-
end
|
|
440
|
-
|
|
441
|
-
# Ensures that an attribute must have a non-nil, non-empty String value.
|
|
442
|
-
# The default behavior will be to raise a user error and thereby halting
|
|
443
|
-
# further configuration processing. Good use cases for require_config
|
|
444
|
-
# might be cloud provider credential keys and other similar data.
|
|
445
|
-
#
|
|
446
|
-
# @example a value that must not be nil or an empty String
|
|
447
|
-
#
|
|
448
|
-
# required_config :cloud_api_token
|
|
449
|
-
#
|
|
450
|
-
# @example using a block to use custom validation logic
|
|
451
|
-
#
|
|
452
|
-
# required_config :email do |attr, value, subject|
|
|
453
|
-
# raise UserError, "Must be an email address" unless value =~ /@/
|
|
454
|
-
# end
|
|
455
|
-
#
|
|
456
|
-
# @param attr [String] configuration attribute name
|
|
457
|
-
# @yieldparam attr [Symbol] the attribute name
|
|
458
|
-
# @yieldparam value [Object] the current value of the attribute
|
|
459
|
-
# @yieldparam object [Object] a reference to the instantiated object
|
|
460
|
-
def required_config(attr, &block)
|
|
461
|
-
if !block_given?
|
|
462
|
-
klass = self
|
|
463
|
-
block = lambda do |_, value, thing|
|
|
464
|
-
if value.nil? || value.to_s.empty?
|
|
465
|
-
attribute = "#{klass}#{thing.instance.to_str}#config[:#{attr}]"
|
|
466
|
-
raise UserError, "#{attribute} cannot be blank"
|
|
467
|
-
end
|
|
468
|
-
end
|
|
469
|
-
end
|
|
470
|
-
validations[attr] = block
|
|
471
|
-
end
|
|
472
|
-
|
|
473
|
-
# @return [Hash] a hash of attribute keys and default values which has
|
|
474
|
-
# been merged with any superclass defaults
|
|
475
|
-
# @api private
|
|
476
|
-
def defaults
|
|
477
|
-
@defaults ||= Hash.new.merge(super_defaults)
|
|
478
|
-
end
|
|
479
|
-
|
|
480
|
-
# @return [Hash] a hash of defaults from the included class' superclass
|
|
481
|
-
# if defined in the superclass, or an empty hash otherwise
|
|
482
|
-
# @api private
|
|
483
|
-
def super_defaults
|
|
484
|
-
if superclass.respond_to?(:defaults)
|
|
485
|
-
superclass.defaults
|
|
486
|
-
else
|
|
487
|
-
Hash.new
|
|
488
|
-
end
|
|
489
|
-
end
|
|
490
|
-
|
|
491
|
-
# @return [Hash] a hash of attribute keys and truthy/falsey values to
|
|
492
|
-
# determine if said attribute needs to be fully file path expanded,
|
|
493
|
-
# which has been merged with any superclass expanded paths
|
|
494
|
-
# @api private
|
|
495
|
-
def expanded_paths
|
|
496
|
-
@expanded_paths ||= Hash.new.merge(super_expanded_paths)
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
# @return [Hash] a hash of expanded paths from the included class'
|
|
500
|
-
# superclass if defined in the superclass, or an empty hash otherwise
|
|
501
|
-
# @api private
|
|
502
|
-
def super_expanded_paths
|
|
503
|
-
if superclass.respond_to?(:expanded_paths)
|
|
504
|
-
superclass.expanded_paths
|
|
505
|
-
else
|
|
506
|
-
Hash.new
|
|
507
|
-
end
|
|
508
|
-
end
|
|
509
|
-
|
|
510
|
-
# @return [Hash] a hash of attribute keys and valudation callable blocks
|
|
511
|
-
# which has been merged with any superclass valudations
|
|
512
|
-
# @api private
|
|
513
|
-
def validations
|
|
514
|
-
@validations ||= Hash.new.merge(super_validations)
|
|
515
|
-
end
|
|
516
|
-
|
|
517
|
-
# @return [Hash] a hash of validations from the included class'
|
|
518
|
-
# superclass if defined in the superclass, or an empty hash otherwise
|
|
519
|
-
# @api private
|
|
520
|
-
def super_validations
|
|
521
|
-
if superclass.respond_to?(:validations)
|
|
522
|
-
superclass.validations
|
|
523
|
-
else
|
|
524
|
-
Hash.new
|
|
525
|
-
end
|
|
526
|
-
end
|
|
527
|
-
end
|
|
528
|
-
end
|
|
529
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2014, Fletcher Nichol
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
|
|
19
|
+
require "thor/util"
|
|
20
|
+
|
|
21
|
+
require "kitchen/lazy_hash"
|
|
22
|
+
|
|
23
|
+
module Kitchen
|
|
24
|
+
|
|
25
|
+
# A mixin for providing configuration-related behavior such as default
|
|
26
|
+
# config (static, computed, inherited), required config, local path
|
|
27
|
+
# expansion, etc.
|
|
28
|
+
#
|
|
29
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
30
|
+
module Configurable
|
|
31
|
+
|
|
32
|
+
def self.included(base)
|
|
33
|
+
base.extend(ClassMethods)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Kitchen::Instance] the associated instance
|
|
37
|
+
attr_reader :instance
|
|
38
|
+
|
|
39
|
+
# A lifecycle method that should be invoked when the object is about ready
|
|
40
|
+
# to be used. A reference to an Instance is required as configuration
|
|
41
|
+
# dependant data may be access through an Instance. This also acts as a
|
|
42
|
+
# hook point where the object may wish to perform other last minute
|
|
43
|
+
# checks, validations, or configuration expansions.
|
|
44
|
+
#
|
|
45
|
+
# @param instance [Instance] an associated instance
|
|
46
|
+
# @return [self] itself, for use in chaining
|
|
47
|
+
# @raise [ClientError] if instance parameter is nil
|
|
48
|
+
def finalize_config!(instance)
|
|
49
|
+
if instance.nil?
|
|
50
|
+
raise ClientError, "Instance must be provided to #{self}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@instance = instance
|
|
54
|
+
expand_paths!
|
|
55
|
+
validate_config!
|
|
56
|
+
load_needed_dependencies!
|
|
57
|
+
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Provides hash-like access to configuration keys.
|
|
62
|
+
#
|
|
63
|
+
# @param attr [Object] configuration key
|
|
64
|
+
# @return [Object] value at configuration key
|
|
65
|
+
def [](attr)
|
|
66
|
+
config[attr]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [TrueClass,FalseClass] true if `:shell_type` is `"bourne"` (or
|
|
70
|
+
# unset, for backwards compatability)
|
|
71
|
+
def bourne_shell?
|
|
72
|
+
["bourne", nil].include?(instance.platform.shell_type)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Find an appropriate path to a file or directory, based on graceful
|
|
76
|
+
# fallback rules or returns nil if path cannot be determined.
|
|
77
|
+
#
|
|
78
|
+
# Given an instance with suite named `"server"`, a `test_base_path` of
|
|
79
|
+
# `"/a/b"`, and a path segement of `"roles"` then following will be tried
|
|
80
|
+
# in order (first match that exists wins):
|
|
81
|
+
#
|
|
82
|
+
# 1. /a/b/server/roles
|
|
83
|
+
# 2. /a/b/roles
|
|
84
|
+
# 3. $PWD/roles
|
|
85
|
+
#
|
|
86
|
+
# @param path [String] the base path segment to search for
|
|
87
|
+
# @param opts [Hash] options
|
|
88
|
+
# @option opts [Symbol] :type either `:file` or `:directory` (default)
|
|
89
|
+
# @option opts [Symbol] :base_path a custom base path to search under,
|
|
90
|
+
# default uses value from `config[:test_base_path]`
|
|
91
|
+
# @return [String] path to the existing file or directory, or nil if file
|
|
92
|
+
# or directory was not found
|
|
93
|
+
# @raise [UserError] if `config[:test_base_path]` is used and is not set
|
|
94
|
+
def calculate_path(path, opts = {})
|
|
95
|
+
type = opts.fetch(:type, :directory)
|
|
96
|
+
base = opts.fetch(:base_path) do
|
|
97
|
+
config.fetch(:test_base_path) do |key|
|
|
98
|
+
raise UserError, "#{key} is not found in #{self}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
[
|
|
103
|
+
File.join(base, instance.suite.name, path),
|
|
104
|
+
File.join(base, path),
|
|
105
|
+
File.join(Dir.pwd, path)
|
|
106
|
+
].find do |candidate|
|
|
107
|
+
type == :directory ? File.directory?(candidate) : File.file?(candidate)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns an array of configuration keys.
|
|
112
|
+
#
|
|
113
|
+
# @return [Array] array of configuration keys
|
|
114
|
+
def config_keys
|
|
115
|
+
config.keys
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns a Hash of configuration and other useful diagnostic information.
|
|
119
|
+
#
|
|
120
|
+
# @return [Hash] a diagnostic hash
|
|
121
|
+
def diagnose
|
|
122
|
+
result = Hash.new
|
|
123
|
+
config_keys.sort.each { |k| result[k] = config[k] }
|
|
124
|
+
result
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns a Hash of configuration and other useful diagnostic information
|
|
128
|
+
# associated with the plugin itself (such as loaded version, class name,
|
|
129
|
+
# etc.).
|
|
130
|
+
#
|
|
131
|
+
# @return [Hash] a diagnostic hash
|
|
132
|
+
def diagnose_plugin
|
|
133
|
+
result = Hash.new
|
|
134
|
+
result[:name] = name
|
|
135
|
+
result.merge!(self.class.diagnose)
|
|
136
|
+
result
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Returns the name of this plugin, suitable for display in a CLI.
|
|
140
|
+
#
|
|
141
|
+
# @return [String] name of this plugin
|
|
142
|
+
def name
|
|
143
|
+
self.class.name.split("::").last
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @return [TrueClass,FalseClass] true if `:shell_type` is `"powershell"`
|
|
147
|
+
def powershell_shell?
|
|
148
|
+
["powershell"].include?(instance.platform.shell_type)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Builds a file path based on the `:os_type` (`"windows"` or `"unix"`).
|
|
152
|
+
#
|
|
153
|
+
# @return [String] joined path for instance's os_type
|
|
154
|
+
def remote_path_join(*parts)
|
|
155
|
+
path = File.join(*parts)
|
|
156
|
+
windows_os? ? path.gsub("/", "\\") : path.gsub("\\", "/")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @return [TrueClass,FalseClass] true if `:os_type` is `"unix"` (or
|
|
160
|
+
# unset, for backwards compatibility)
|
|
161
|
+
def unix_os?
|
|
162
|
+
["unix", nil].include?(instance.platform.os_type)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Performs whatever tests that may be required to ensure that this plugin
|
|
166
|
+
# will be able to function in the current environment. This may involve
|
|
167
|
+
# checking for the presence of certain directories, software installed,
|
|
168
|
+
# etc.
|
|
169
|
+
#
|
|
170
|
+
# @raise [UserError] if the plugin will not be able to perform or if a
|
|
171
|
+
# documented dependency is missing from the system
|
|
172
|
+
def verify_dependencies
|
|
173
|
+
# this method may be left unimplemented if that is applicable
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# @return [TrueClass,FalseClass] true if `:os_type` is `"windows"`
|
|
177
|
+
def windows_os?
|
|
178
|
+
["windows"].include?(instance.platform.os_type)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
# @return [LzayHash] a configuration hash
|
|
184
|
+
# @api private
|
|
185
|
+
attr_reader :config
|
|
186
|
+
|
|
187
|
+
# Initializes an internal configuration hash. The hash may contain
|
|
188
|
+
# callable blocks as values that are meant to be called lazily. This
|
|
189
|
+
# method is intended to be included in an object's .initialize method.
|
|
190
|
+
#
|
|
191
|
+
# @param config [Hash] initial provided configuration
|
|
192
|
+
# @api private
|
|
193
|
+
def init_config(config)
|
|
194
|
+
@config = LazyHash.new(config, self)
|
|
195
|
+
self.class.defaults.each do |attr, value|
|
|
196
|
+
@config[attr] = value unless @config.key?(attr)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Expands file paths for certain configuration values. A configuration
|
|
201
|
+
# value is marked for file expansion with a expand_path_for declaration
|
|
202
|
+
# in the included class.
|
|
203
|
+
#
|
|
204
|
+
# @api private
|
|
205
|
+
def expand_paths!
|
|
206
|
+
root_path = config[:kitchen_root] || Dir.pwd
|
|
207
|
+
expanded_paths = LazyHash.new(self.class.expanded_paths, self).to_hash
|
|
208
|
+
|
|
209
|
+
expanded_paths.each do |key, should_expand|
|
|
210
|
+
next if !should_expand || config[key].nil? || config[key] == false
|
|
211
|
+
|
|
212
|
+
config[key] = if config[key].is_a?(Array)
|
|
213
|
+
config[key].map { |path| File.expand_path(path, root_path) }
|
|
214
|
+
else
|
|
215
|
+
File.expand_path(config[key], root_path)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Loads any required third party Ruby libraries or runs any shell out
|
|
221
|
+
# commands to prepare the plugin. This method will be called in the
|
|
222
|
+
# context of the main thread of execution and so does not necessarily
|
|
223
|
+
# have to be thread safe.
|
|
224
|
+
#
|
|
225
|
+
# **Note:** any subclasses overriding this method would be well advised
|
|
226
|
+
# to call super when overriding this method, for example:
|
|
227
|
+
#
|
|
228
|
+
# @example overriding `#load_needed_dependencies!`
|
|
229
|
+
#
|
|
230
|
+
# class MyProvisioner < Kitchen::Provisioner::Base
|
|
231
|
+
# def load_needed_dependencies!
|
|
232
|
+
# super
|
|
233
|
+
# # any further work
|
|
234
|
+
# end
|
|
235
|
+
# end
|
|
236
|
+
#
|
|
237
|
+
# @raise [ClientError] if any library loading fails or any of the
|
|
238
|
+
# dependency requirements cannot be satisfied
|
|
239
|
+
# @api private
|
|
240
|
+
def load_needed_dependencies!
|
|
241
|
+
# this method may be left unimplemented if that is applicable
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# @return [Logger] the instance's logger or Test Kitchen's common logger
|
|
245
|
+
# otherwise
|
|
246
|
+
# @api private
|
|
247
|
+
def logger
|
|
248
|
+
instance ? instance.logger : Kitchen.logger
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Builds a shell environment variable assignment string for the
|
|
252
|
+
# required shell type.
|
|
253
|
+
#
|
|
254
|
+
# @param name [String] variable name
|
|
255
|
+
# @param value [String] variable value
|
|
256
|
+
# @return [String] shell variable assignment
|
|
257
|
+
# @api private
|
|
258
|
+
def shell_env_var(name, value)
|
|
259
|
+
if powershell_shell?
|
|
260
|
+
shell_var("env:#{name}", value)
|
|
261
|
+
else
|
|
262
|
+
"#{shell_var(name, value)}; export #{name}"
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Builds a shell variable assignment string for the required shell type.
|
|
267
|
+
#
|
|
268
|
+
# @param name [String] variable name
|
|
269
|
+
# @param value [String] variable value
|
|
270
|
+
# @return [String] shell variable assignment
|
|
271
|
+
# @api private
|
|
272
|
+
def shell_var(name, value)
|
|
273
|
+
if powershell_shell?
|
|
274
|
+
%{$#{name} = "#{value}"}
|
|
275
|
+
else
|
|
276
|
+
%{#{name}="#{value}"}
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Runs all validations set up for the included class. Each validation is
|
|
281
|
+
# for a specific configuration attribute and has an associated callable
|
|
282
|
+
# block. Each validation block is called with the attribute, its value,
|
|
283
|
+
# and the included object for context.
|
|
284
|
+
#
|
|
285
|
+
# @api private
|
|
286
|
+
def validate_config!
|
|
287
|
+
self.class.validations.each do |attr, block|
|
|
288
|
+
block.call(attr, config[attr], self)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Wraps a body of shell code with common context appropriate for the type
|
|
293
|
+
# of shell.
|
|
294
|
+
#
|
|
295
|
+
# @param code [String] the shell code to be wrapped
|
|
296
|
+
# @return [String] wrapped shell code
|
|
297
|
+
# @api private
|
|
298
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
299
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
300
|
+
def wrap_shell_code(code)
|
|
301
|
+
env = []
|
|
302
|
+
if config[:http_proxy] && !config[:http_proxy].empty?
|
|
303
|
+
env << shell_env_var("http_proxy", config[:http_proxy])
|
|
304
|
+
env << shell_env_var("HTTP_PROXY", config[:http_proxy])
|
|
305
|
+
else
|
|
306
|
+
export_proxy(env, "http")
|
|
307
|
+
end
|
|
308
|
+
if config[:https_proxy] && !config[:https_proxy].empty?
|
|
309
|
+
env << shell_env_var("https_proxy", config[:https_proxy])
|
|
310
|
+
env << shell_env_var("HTTPS_PROXY", config[:https_proxy])
|
|
311
|
+
else
|
|
312
|
+
export_proxy(env, "https")
|
|
313
|
+
end
|
|
314
|
+
if config[:ftp_proxy] && !config[:ftp_proxy].empty?
|
|
315
|
+
env << shell_env_var("ftp_proxy", config[:ftp_proxy])
|
|
316
|
+
env << shell_env_var("FTP_PROXY", config[:ftp_proxy])
|
|
317
|
+
else
|
|
318
|
+
export_proxy(env, "ftp")
|
|
319
|
+
end
|
|
320
|
+
# if http_proxy was set from environment variable or https_proxy was set
|
|
321
|
+
# from environment variable, or ftp_proxy was set from environment
|
|
322
|
+
# variable, include no_proxy environment variable, if set.
|
|
323
|
+
if (!config[:http_proxy] && (ENV["http_proxy"] || ENV["HTTP_PROXY"])) ||
|
|
324
|
+
(!config[:https_proxy] && (ENV["https_proxy"] || ENV["HTTPS_PROXY"])) ||
|
|
325
|
+
(!config[:ftp_proxy] && (ENV["ftp_proxy"] || ENV["FTP_PROXY"]))
|
|
326
|
+
env << shell_env_var("no_proxy", ENV["no_proxy"]) if ENV["no_proxy"]
|
|
327
|
+
env << shell_env_var("NO_PROXY", ENV["NO_PROXY"]) if ENV["NO_PROXY"]
|
|
328
|
+
end
|
|
329
|
+
if powershell_shell?
|
|
330
|
+
env.join("\n").concat("\n").concat(code)
|
|
331
|
+
else
|
|
332
|
+
Util.wrap_command(env.join("\n").concat("\n").concat(code))
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Helper method to export
|
|
337
|
+
#
|
|
338
|
+
# @param env [Array] the environment to modify
|
|
339
|
+
# @param code [String] the type of proxy to export, one of 'http', 'https' or 'ftp'
|
|
340
|
+
# @api private
|
|
341
|
+
def export_proxy(env, type)
|
|
342
|
+
env << shell_env_var("#{type}_proxy", ENV["#{type}_proxy"]) if ENV["#{type}_proxy"]
|
|
343
|
+
env << shell_env_var("#{type.upcase}_PROXY", ENV["#{type.upcase}_PROXY"]) if
|
|
344
|
+
ENV["#{type.upcase}_PROXY"]
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Class methods which will be mixed in on inclusion of Configurable module.
|
|
348
|
+
module ClassMethods
|
|
349
|
+
|
|
350
|
+
# Sets the loaded version of this plugin, usually corresponding to the
|
|
351
|
+
# RubyGems version of the plugin's library. If the plugin does not set
|
|
352
|
+
# this value, then `nil` will be used and reported.
|
|
353
|
+
#
|
|
354
|
+
# @example setting a version used by RubyGems
|
|
355
|
+
#
|
|
356
|
+
# require "kitchen/driver/vagrant_version"
|
|
357
|
+
#
|
|
358
|
+
# module Kitchen
|
|
359
|
+
# module Driver
|
|
360
|
+
# class Vagrant < Kitchen::Driver::Base
|
|
361
|
+
#
|
|
362
|
+
# plugin_version Kitchen::Driver::VAGRANT_VERSION
|
|
363
|
+
#
|
|
364
|
+
# end
|
|
365
|
+
# end
|
|
366
|
+
# end
|
|
367
|
+
#
|
|
368
|
+
# @param version [String] a version string
|
|
369
|
+
def plugin_version(version) # rubocop:disable Style/TrivialAccessors
|
|
370
|
+
@plugin_version = version
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Returns a Hash of configuration and other useful diagnostic
|
|
374
|
+
# information.
|
|
375
|
+
#
|
|
376
|
+
# @return [Hash] a diagnostic hash
|
|
377
|
+
def diagnose
|
|
378
|
+
{
|
|
379
|
+
:class => name,
|
|
380
|
+
:version => @plugin_version,
|
|
381
|
+
:api_version => @api_version
|
|
382
|
+
}
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Sets a sane default value for a configuration attribute. These values
|
|
386
|
+
# can be overridden by provided configuration or in a subclass with
|
|
387
|
+
# another default_config declaration.
|
|
388
|
+
#
|
|
389
|
+
# @example a nil default value
|
|
390
|
+
#
|
|
391
|
+
# default_config :i_am_nil
|
|
392
|
+
#
|
|
393
|
+
# @example a primitive default value
|
|
394
|
+
#
|
|
395
|
+
# default_config :use_sudo, true
|
|
396
|
+
#
|
|
397
|
+
# @example a block to compute a default value
|
|
398
|
+
#
|
|
399
|
+
# default_config :box_name do |subject|
|
|
400
|
+
# subject.instance.platform.name
|
|
401
|
+
# end
|
|
402
|
+
#
|
|
403
|
+
# @param attr [String] configuration attribute name
|
|
404
|
+
# @param value [Object, nil] static default value for attribute
|
|
405
|
+
# @yieldparam object [Object] a reference to the instantiated object
|
|
406
|
+
# @yieldreturn [Object, nil] dynamically computed value for the attribute
|
|
407
|
+
def default_config(attr, value = nil, &block)
|
|
408
|
+
defaults[attr] = block_given? ? block : value
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Ensures that an attribute which is a path will be fully expanded at
|
|
412
|
+
# the right time. This helps make the configuration unambiguous and much
|
|
413
|
+
# easier to debug and diagnose.
|
|
414
|
+
#
|
|
415
|
+
# Note that the file path expansion is only intended for paths on the
|
|
416
|
+
# local workstation invking the Test Kitchen code.
|
|
417
|
+
#
|
|
418
|
+
# @example the default usage
|
|
419
|
+
#
|
|
420
|
+
# expand_path_for :data_path
|
|
421
|
+
#
|
|
422
|
+
# @example disabling path expansion with a falsey value
|
|
423
|
+
#
|
|
424
|
+
# expand_path_for :relative_path, false
|
|
425
|
+
#
|
|
426
|
+
# @example using a block to determine whether or not to expand
|
|
427
|
+
#
|
|
428
|
+
# expand_path_for :relative_or_not_path do |subject|
|
|
429
|
+
# subject.instance.name =~ /default/
|
|
430
|
+
# end
|
|
431
|
+
#
|
|
432
|
+
# @param attr [String] configuration attribute name
|
|
433
|
+
# @param value [Object, nil] whether or not to exand the file path
|
|
434
|
+
# @yieldparam object [Object] a reference to the instantiated object
|
|
435
|
+
# @yieldreturn [Object, nil] dynamically compute whether or not to
|
|
436
|
+
# perform the file expansion
|
|
437
|
+
def expand_path_for(attr, value = true, &block)
|
|
438
|
+
expanded_paths[attr] = block_given? ? block : value
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Ensures that an attribute must have a non-nil, non-empty String value.
|
|
442
|
+
# The default behavior will be to raise a user error and thereby halting
|
|
443
|
+
# further configuration processing. Good use cases for require_config
|
|
444
|
+
# might be cloud provider credential keys and other similar data.
|
|
445
|
+
#
|
|
446
|
+
# @example a value that must not be nil or an empty String
|
|
447
|
+
#
|
|
448
|
+
# required_config :cloud_api_token
|
|
449
|
+
#
|
|
450
|
+
# @example using a block to use custom validation logic
|
|
451
|
+
#
|
|
452
|
+
# required_config :email do |attr, value, subject|
|
|
453
|
+
# raise UserError, "Must be an email address" unless value =~ /@/
|
|
454
|
+
# end
|
|
455
|
+
#
|
|
456
|
+
# @param attr [String] configuration attribute name
|
|
457
|
+
# @yieldparam attr [Symbol] the attribute name
|
|
458
|
+
# @yieldparam value [Object] the current value of the attribute
|
|
459
|
+
# @yieldparam object [Object] a reference to the instantiated object
|
|
460
|
+
def required_config(attr, &block)
|
|
461
|
+
if !block_given?
|
|
462
|
+
klass = self
|
|
463
|
+
block = lambda do |_, value, thing|
|
|
464
|
+
if value.nil? || value.to_s.empty?
|
|
465
|
+
attribute = "#{klass}#{thing.instance.to_str}#config[:#{attr}]"
|
|
466
|
+
raise UserError, "#{attribute} cannot be blank"
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
validations[attr] = block
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# @return [Hash] a hash of attribute keys and default values which has
|
|
474
|
+
# been merged with any superclass defaults
|
|
475
|
+
# @api private
|
|
476
|
+
def defaults
|
|
477
|
+
@defaults ||= Hash.new.merge(super_defaults)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# @return [Hash] a hash of defaults from the included class' superclass
|
|
481
|
+
# if defined in the superclass, or an empty hash otherwise
|
|
482
|
+
# @api private
|
|
483
|
+
def super_defaults
|
|
484
|
+
if superclass.respond_to?(:defaults)
|
|
485
|
+
superclass.defaults
|
|
486
|
+
else
|
|
487
|
+
Hash.new
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# @return [Hash] a hash of attribute keys and truthy/falsey values to
|
|
492
|
+
# determine if said attribute needs to be fully file path expanded,
|
|
493
|
+
# which has been merged with any superclass expanded paths
|
|
494
|
+
# @api private
|
|
495
|
+
def expanded_paths
|
|
496
|
+
@expanded_paths ||= Hash.new.merge(super_expanded_paths)
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# @return [Hash] a hash of expanded paths from the included class'
|
|
500
|
+
# superclass if defined in the superclass, or an empty hash otherwise
|
|
501
|
+
# @api private
|
|
502
|
+
def super_expanded_paths
|
|
503
|
+
if superclass.respond_to?(:expanded_paths)
|
|
504
|
+
superclass.expanded_paths
|
|
505
|
+
else
|
|
506
|
+
Hash.new
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# @return [Hash] a hash of attribute keys and valudation callable blocks
|
|
511
|
+
# which has been merged with any superclass valudations
|
|
512
|
+
# @api private
|
|
513
|
+
def validations
|
|
514
|
+
@validations ||= Hash.new.merge(super_validations)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# @return [Hash] a hash of validations from the included class'
|
|
518
|
+
# superclass if defined in the superclass, or an empty hash otherwise
|
|
519
|
+
# @api private
|
|
520
|
+
def super_validations
|
|
521
|
+
if superclass.respond_to?(:validations)
|
|
522
|
+
superclass.validations
|
|
523
|
+
else
|
|
524
|
+
Hash.new
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
end
|