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/driver/dummy.rb
CHANGED
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2012, 2013, 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 "kitchen"
|
|
20
|
-
|
|
21
|
-
module Kitchen
|
|
22
|
-
|
|
23
|
-
module Driver
|
|
24
|
-
|
|
25
|
-
# Dummy driver for Kitchen. This driver does nothing but report what would
|
|
26
|
-
# happen if this driver did anything of consequence. As a result it may
|
|
27
|
-
# be a useful driver to use when debugging or developing new features or
|
|
28
|
-
# plugins.
|
|
29
|
-
#
|
|
30
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
31
|
-
class Dummy < Kitchen::Driver::Base
|
|
32
|
-
|
|
33
|
-
kitchen_driver_api_version 2
|
|
34
|
-
|
|
35
|
-
plugin_version Kitchen::VERSION
|
|
36
|
-
|
|
37
|
-
default_config :sleep, 0
|
|
38
|
-
default_config :random_failure, false
|
|
39
|
-
|
|
40
|
-
# (see Base#create)
|
|
41
|
-
def create(state)
|
|
42
|
-
state[:my_id] = "#{instance.name}-#{Time.now.to_i}"
|
|
43
|
-
report(:create, state)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# (see Base#setup)
|
|
47
|
-
def setup(state)
|
|
48
|
-
report(:setup, state)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# (see Base#verify)
|
|
52
|
-
def verify(state)
|
|
53
|
-
report(:verify, state)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# (see Base#destroy)
|
|
57
|
-
def destroy(state)
|
|
58
|
-
report(:destroy, state)
|
|
59
|
-
state.delete(:my_id)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
|
|
64
|
-
# Report what action is taking place, sleeping if so configured, and
|
|
65
|
-
# possibly fail randomly.
|
|
66
|
-
#
|
|
67
|
-
# @param action [Symbol] the action currently taking place
|
|
68
|
-
# @param state [Hash] the state hash
|
|
69
|
-
# @api private
|
|
70
|
-
def report(action, state)
|
|
71
|
-
what = action.capitalize
|
|
72
|
-
info("[Dummy] #{what} on instance=#{instance} with state=#{state}")
|
|
73
|
-
sleep_if_set
|
|
74
|
-
failure_if_set(action)
|
|
75
|
-
debug("[Dummy] #{what} completed (#{config[:sleep]}s).")
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Sleep for a period of time, if a value is set in the config.
|
|
79
|
-
#
|
|
80
|
-
# @api private
|
|
81
|
-
def sleep_if_set
|
|
82
|
-
sleep(config[:sleep].to_f) if config[:sleep].to_f > 0.0
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Simulate a failure in an action, if set in the config.
|
|
86
|
-
#
|
|
87
|
-
# @param action [Symbol] the action currently taking place
|
|
88
|
-
# @api private
|
|
89
|
-
def failure_if_set(action)
|
|
90
|
-
if config[:"fail_#{action}"]
|
|
91
|
-
debug("[Dummy] Failure for action ##{action}.")
|
|
92
|
-
raise ActionFailed, "Action ##{action} failed for #{instance.to_str}."
|
|
93
|
-
elsif config[:random_failure] && randomly_fail?
|
|
94
|
-
debug("[Dummy] Random failure for action ##{action}.")
|
|
95
|
-
raise ActionFailed, "Action ##{action} failed for #{instance.to_str}."
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Determine whether or not to randomly fail.
|
|
100
|
-
#
|
|
101
|
-
# @return [true, false]
|
|
102
|
-
# @api private
|
|
103
|
-
def randomly_fail?
|
|
104
|
-
[true, false].sample
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2012, 2013, 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 "kitchen"
|
|
20
|
+
|
|
21
|
+
module Kitchen
|
|
22
|
+
|
|
23
|
+
module Driver
|
|
24
|
+
|
|
25
|
+
# Dummy driver for Kitchen. This driver does nothing but report what would
|
|
26
|
+
# happen if this driver did anything of consequence. As a result it may
|
|
27
|
+
# be a useful driver to use when debugging or developing new features or
|
|
28
|
+
# plugins.
|
|
29
|
+
#
|
|
30
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
31
|
+
class Dummy < Kitchen::Driver::Base
|
|
32
|
+
|
|
33
|
+
kitchen_driver_api_version 2
|
|
34
|
+
|
|
35
|
+
plugin_version Kitchen::VERSION
|
|
36
|
+
|
|
37
|
+
default_config :sleep, 0
|
|
38
|
+
default_config :random_failure, false
|
|
39
|
+
|
|
40
|
+
# (see Base#create)
|
|
41
|
+
def create(state)
|
|
42
|
+
state[:my_id] = "#{instance.name}-#{Time.now.to_i}"
|
|
43
|
+
report(:create, state)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# (see Base#setup)
|
|
47
|
+
def setup(state)
|
|
48
|
+
report(:setup, state)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# (see Base#verify)
|
|
52
|
+
def verify(state)
|
|
53
|
+
report(:verify, state)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# (see Base#destroy)
|
|
57
|
+
def destroy(state)
|
|
58
|
+
report(:destroy, state)
|
|
59
|
+
state.delete(:my_id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# Report what action is taking place, sleeping if so configured, and
|
|
65
|
+
# possibly fail randomly.
|
|
66
|
+
#
|
|
67
|
+
# @param action [Symbol] the action currently taking place
|
|
68
|
+
# @param state [Hash] the state hash
|
|
69
|
+
# @api private
|
|
70
|
+
def report(action, state)
|
|
71
|
+
what = action.capitalize
|
|
72
|
+
info("[Dummy] #{what} on instance=#{instance} with state=#{state}")
|
|
73
|
+
sleep_if_set
|
|
74
|
+
failure_if_set(action)
|
|
75
|
+
debug("[Dummy] #{what} completed (#{config[:sleep]}s).")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Sleep for a period of time, if a value is set in the config.
|
|
79
|
+
#
|
|
80
|
+
# @api private
|
|
81
|
+
def sleep_if_set
|
|
82
|
+
sleep(config[:sleep].to_f) if config[:sleep].to_f > 0.0
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Simulate a failure in an action, if set in the config.
|
|
86
|
+
#
|
|
87
|
+
# @param action [Symbol] the action currently taking place
|
|
88
|
+
# @api private
|
|
89
|
+
def failure_if_set(action)
|
|
90
|
+
if config[:"fail_#{action}"]
|
|
91
|
+
debug("[Dummy] Failure for action ##{action}.")
|
|
92
|
+
raise ActionFailed, "Action ##{action} failed for #{instance.to_str}."
|
|
93
|
+
elsif config[:random_failure] && randomly_fail?
|
|
94
|
+
debug("[Dummy] Random failure for action ##{action}.")
|
|
95
|
+
raise ActionFailed, "Action ##{action} failed for #{instance.to_str}."
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Determine whether or not to randomly fail.
|
|
100
|
+
#
|
|
101
|
+
# @return [true, false]
|
|
102
|
+
# @api private
|
|
103
|
+
def randomly_fail?
|
|
104
|
+
[true, false].sample
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
data/lib/kitchen/driver/proxy.rb
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Seth Chisamore <schisamo@opscode.com>
|
|
4
|
-
#
|
|
5
|
-
# Copyright:: Copyright (c) 2013 Opscode, Inc.
|
|
6
|
-
# License:: Apache License, Version 2.0
|
|
7
|
-
#
|
|
8
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
-
# you may not use this file except in compliance with the License.
|
|
10
|
-
# You may obtain a copy of the License at
|
|
11
|
-
#
|
|
12
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
-
#
|
|
14
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
15
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
-
# See the License for the specific language governing permissions and
|
|
18
|
-
# limitations under the License.
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
require "kitchen"
|
|
22
|
-
require "kitchen/version"
|
|
23
|
-
|
|
24
|
-
module Kitchen
|
|
25
|
-
|
|
26
|
-
module Driver
|
|
27
|
-
|
|
28
|
-
# Simple driver that proxies commands through to a test instance whose
|
|
29
|
-
# lifecycle is not managed by Test Kitchen. This driver is useful for long-
|
|
30
|
-
# lived non-ephemeral test instances that are simply "reset" between test
|
|
31
|
-
# runs. Think executing against devices like network switches--this is why
|
|
32
|
-
# the driver was created.
|
|
33
|
-
#
|
|
34
|
-
# @author Seth Chisamore <schisamo@opscode.com>
|
|
35
|
-
class Proxy < Kitchen::Driver::SSHBase
|
|
36
|
-
|
|
37
|
-
plugin_version Kitchen::VERSION
|
|
38
|
-
|
|
39
|
-
required_config :host
|
|
40
|
-
required_config :reset_command
|
|
41
|
-
|
|
42
|
-
no_parallel_for :create, :destroy
|
|
43
|
-
|
|
44
|
-
# (see Base#create)
|
|
45
|
-
def create(state)
|
|
46
|
-
state[:hostname] = config[:host]
|
|
47
|
-
reset_instance(state)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# (see Base#destroy)
|
|
51
|
-
def destroy(state)
|
|
52
|
-
return if state[:hostname].nil?
|
|
53
|
-
reset_instance(state)
|
|
54
|
-
state.delete(:hostname)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
# Resets the non-Kitchen managed instance using by issuing a command
|
|
60
|
-
# over SSH.
|
|
61
|
-
#
|
|
62
|
-
# @param state [Hash] the state hash
|
|
63
|
-
# @api private
|
|
64
|
-
def reset_instance(state)
|
|
65
|
-
if cmd = config[:reset_command]
|
|
66
|
-
info("Resetting instance state with command: #{cmd}")
|
|
67
|
-
ssh(build_ssh_args(state), cmd)
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Seth Chisamore <schisamo@opscode.com>
|
|
4
|
+
#
|
|
5
|
+
# Copyright:: Copyright (c) 2013 Opscode, Inc.
|
|
6
|
+
# License:: Apache License, Version 2.0
|
|
7
|
+
#
|
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
# you may not use this file except in compliance with the License.
|
|
10
|
+
# You may obtain a copy of the License at
|
|
11
|
+
#
|
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
#
|
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
# See the License for the specific language governing permissions and
|
|
18
|
+
# limitations under the License.
|
|
19
|
+
#
|
|
20
|
+
|
|
21
|
+
require "kitchen"
|
|
22
|
+
require "kitchen/version"
|
|
23
|
+
|
|
24
|
+
module Kitchen
|
|
25
|
+
|
|
26
|
+
module Driver
|
|
27
|
+
|
|
28
|
+
# Simple driver that proxies commands through to a test instance whose
|
|
29
|
+
# lifecycle is not managed by Test Kitchen. This driver is useful for long-
|
|
30
|
+
# lived non-ephemeral test instances that are simply "reset" between test
|
|
31
|
+
# runs. Think executing against devices like network switches--this is why
|
|
32
|
+
# the driver was created.
|
|
33
|
+
#
|
|
34
|
+
# @author Seth Chisamore <schisamo@opscode.com>
|
|
35
|
+
class Proxy < Kitchen::Driver::SSHBase
|
|
36
|
+
|
|
37
|
+
plugin_version Kitchen::VERSION
|
|
38
|
+
|
|
39
|
+
required_config :host
|
|
40
|
+
required_config :reset_command
|
|
41
|
+
|
|
42
|
+
no_parallel_for :create, :destroy
|
|
43
|
+
|
|
44
|
+
# (see Base#create)
|
|
45
|
+
def create(state)
|
|
46
|
+
state[:hostname] = config[:host]
|
|
47
|
+
reset_instance(state)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# (see Base#destroy)
|
|
51
|
+
def destroy(state)
|
|
52
|
+
return if state[:hostname].nil?
|
|
53
|
+
reset_instance(state)
|
|
54
|
+
state.delete(:hostname)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Resets the non-Kitchen managed instance using by issuing a command
|
|
60
|
+
# over SSH.
|
|
61
|
+
#
|
|
62
|
+
# @param state [Hash] the state hash
|
|
63
|
+
# @api private
|
|
64
|
+
def reset_instance(state)
|
|
65
|
+
if cmd = config[:reset_command]
|
|
66
|
+
info("Resetting instance state with command: #{cmd}")
|
|
67
|
+
ssh(build_ssh_args(state), cmd)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -1,357 +1,357 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
-
#
|
|
5
|
-
# Copyright (C) 2012, 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
|
-
module Driver
|
|
26
|
-
|
|
27
|
-
# Legacy base class for a driver that uses SSH to communication with an
|
|
28
|
-
# instance. This class has been updated to use the Instance's Transport to
|
|
29
|
-
# issue commands and transfer files and no longer uses the `Kitchen:SSH`
|
|
30
|
-
# class directly.
|
|
31
|
-
#
|
|
32
|
-
# **NOTE:** Authors of new Drivers are encouraged to inherit from
|
|
33
|
-
# `Kitchen::Driver::Base` instead and existing Driver authors are
|
|
34
|
-
# encouraged to update their Driver class to inherit from
|
|
35
|
-
# `Kitchen::Driver::SSHBase`.
|
|
36
|
-
#
|
|
37
|
-
# A subclass must implement the following methods:
|
|
38
|
-
# * #create(state)
|
|
39
|
-
# * #destroy(state)
|
|
40
|
-
#
|
|
41
|
-
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
42
|
-
# @deprecated While all possible effort has been made to preserve the
|
|
43
|
-
# original behavior of this class, future improvements to the Driver,
|
|
44
|
-
# Transport, and Verifier subsystems may not be picked up in these
|
|
45
|
-
# Drivers. When legacy Driver::SSHBase support is removed, this class
|
|
46
|
-
# will no longer be available.
|
|
47
|
-
class SSHBase
|
|
48
|
-
|
|
49
|
-
include ShellOut
|
|
50
|
-
include Configurable
|
|
51
|
-
include Logging
|
|
52
|
-
|
|
53
|
-
default_config :sudo, true
|
|
54
|
-
default_config :port, 22
|
|
55
|
-
|
|
56
|
-
# Creates a new Driver object using the provided configuration data
|
|
57
|
-
# which will be merged with any default configuration.
|
|
58
|
-
#
|
|
59
|
-
# @param config [Hash] provided driver configuration
|
|
60
|
-
def initialize(config = {})
|
|
61
|
-
init_config(config)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# (see Base#create)
|
|
65
|
-
def create(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
66
|
-
raise ClientError, "#{self.class}#create must be implemented"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# (see Base#converge)
|
|
70
|
-
def converge(state) # rubocop:disable Metrics/AbcSize
|
|
71
|
-
provisioner = instance.provisioner
|
|
72
|
-
provisioner.create_sandbox
|
|
73
|
-
sandbox_dirs = Dir.glob("#{provisioner.sandbox_path}/*")
|
|
74
|
-
|
|
75
|
-
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
76
|
-
conn.execute(env_cmd(provisioner.install_command))
|
|
77
|
-
conn.execute(env_cmd(provisioner.init_command))
|
|
78
|
-
info("Transferring files to #{instance.to_str}")
|
|
79
|
-
conn.upload(sandbox_dirs, provisioner[:root_path])
|
|
80
|
-
debug("Transfer complete")
|
|
81
|
-
conn.execute(env_cmd(provisioner.prepare_command))
|
|
82
|
-
conn.execute(env_cmd(provisioner.run_command))
|
|
83
|
-
end
|
|
84
|
-
rescue Kitchen::Transport::TransportFailed => ex
|
|
85
|
-
raise ActionFailed, ex.message
|
|
86
|
-
ensure
|
|
87
|
-
instance.provisioner.cleanup_sandbox
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# (see Base#setup)
|
|
91
|
-
def setup(state)
|
|
92
|
-
verifier = instance.verifier
|
|
93
|
-
|
|
94
|
-
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
95
|
-
conn.execute(env_cmd(verifier.install_command))
|
|
96
|
-
end
|
|
97
|
-
rescue Kitchen::Transport::TransportFailed => ex
|
|
98
|
-
raise ActionFailed, ex.message
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# (see Base#verify)
|
|
102
|
-
def verify(state) # rubocop:disable Metrics/AbcSize
|
|
103
|
-
verifier = instance.verifier
|
|
104
|
-
verifier.create_sandbox
|
|
105
|
-
sandbox_dirs = Dir.glob(File.join(verifier.sandbox_path, "*"))
|
|
106
|
-
|
|
107
|
-
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
108
|
-
conn.execute(env_cmd(verifier.init_command))
|
|
109
|
-
info("Transferring files to #{instance.to_str}")
|
|
110
|
-
conn.upload(sandbox_dirs, verifier[:root_path])
|
|
111
|
-
debug("Transfer complete")
|
|
112
|
-
conn.execute(env_cmd(verifier.prepare_command))
|
|
113
|
-
conn.execute(env_cmd(verifier.run_command))
|
|
114
|
-
end
|
|
115
|
-
rescue Kitchen::Transport::TransportFailed => ex
|
|
116
|
-
raise ActionFailed, ex.message
|
|
117
|
-
ensure
|
|
118
|
-
instance.verifier.cleanup_sandbox
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# (see Base#destroy)
|
|
122
|
-
def destroy(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
123
|
-
raise ClientError, "#{self.class}#destroy must be implemented"
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def legacy_state(state)
|
|
127
|
-
backcompat_merged_state(state)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# (see Base#login_command)
|
|
131
|
-
def login_command(state)
|
|
132
|
-
instance.transport.connection(backcompat_merged_state(state)).
|
|
133
|
-
login_command
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Executes an arbitrary command on an instance over an SSH connection.
|
|
137
|
-
#
|
|
138
|
-
# @param state [Hash] mutable instance and driver state
|
|
139
|
-
# @param command [String] the command to be executed
|
|
140
|
-
# @raise [ActionFailed] if the command could not be successfully completed
|
|
141
|
-
def remote_command(state, command)
|
|
142
|
-
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
143
|
-
conn.execute(env_cmd(command))
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# **(Deprecated)** Executes a remote command over SSH.
|
|
148
|
-
#
|
|
149
|
-
# @param ssh_args [Array] ssh arguments
|
|
150
|
-
# @param command [String] remote command to invoke
|
|
151
|
-
# @deprecated This method should no longer be called directly and exists
|
|
152
|
-
# to support very old drivers. This will be removed in the future.
|
|
153
|
-
def ssh(ssh_args, command)
|
|
154
|
-
pseudo_state = { :hostname => ssh_args[0], :username => ssh_args[1] }
|
|
155
|
-
pseudo_state.merge!(ssh_args[2])
|
|
156
|
-
connection_state = backcompat_merged_state(pseudo_state)
|
|
157
|
-
|
|
158
|
-
instance.transport.connection(connection_state) do |conn|
|
|
159
|
-
conn.execute(env_cmd(command))
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Performs whatever tests that may be required to ensure that this driver
|
|
164
|
-
# will be able to function in the current environment. This may involve
|
|
165
|
-
# checking for the presence of certain directories, software installed,
|
|
166
|
-
# etc.
|
|
167
|
-
#
|
|
168
|
-
# @raise [UserError] if the driver will not be able to perform or if a
|
|
169
|
-
# documented dependency is missing from the system
|
|
170
|
-
def verify_dependencies
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
class << self
|
|
174
|
-
# @return [Array<Symbol>] an array of action method names that cannot
|
|
175
|
-
# be run concurrently and must be run in serial via a shared mutex
|
|
176
|
-
attr_reader :serial_actions
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# Registers certain driver actions that cannot be safely run concurrently
|
|
180
|
-
# in threads across multiple instances. Typically this might be used
|
|
181
|
-
# for create or destroy actions that use an underlying resource that
|
|
182
|
-
# cannot be used at the same time.
|
|
183
|
-
#
|
|
184
|
-
# A shared mutex for this driver object will be used to synchronize all
|
|
185
|
-
# registered methods.
|
|
186
|
-
#
|
|
187
|
-
# @example a single action method that cannot be run concurrently
|
|
188
|
-
#
|
|
189
|
-
# no_parallel_for :create
|
|
190
|
-
#
|
|
191
|
-
# @example multiple action methods that cannot be run concurrently
|
|
192
|
-
#
|
|
193
|
-
# no_parallel_for :create, :destroy
|
|
194
|
-
#
|
|
195
|
-
# @param methods [Array<Symbol>] one or more actions as symbols
|
|
196
|
-
# @raise [ClientError] if any method is not a valid action method name
|
|
197
|
-
def self.no_parallel_for(*methods)
|
|
198
|
-
action_methods = [:create, :converge, :setup, :verify, :destroy]
|
|
199
|
-
|
|
200
|
-
Array(methods).each do |meth|
|
|
201
|
-
next if action_methods.include?(meth)
|
|
202
|
-
|
|
203
|
-
raise ClientError, "##{meth} is not a valid no_parallel_for method"
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
@serial_actions ||= []
|
|
207
|
-
@serial_actions += methods
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
private
|
|
211
|
-
|
|
212
|
-
def backcompat_merged_state(state)
|
|
213
|
-
driver_ssh_keys = %w[
|
|
214
|
-
forward_agent hostname password port ssh_key username
|
|
215
|
-
].map(&:to_sym)
|
|
216
|
-
config.select { |key, _| driver_ssh_keys.include?(key) }.rmerge(state)
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# Builds arguments for constructing a `Kitchen::SSH` instance.
|
|
220
|
-
#
|
|
221
|
-
# @param state [Hash] state hash
|
|
222
|
-
# @return [Array] SSH constructor arguments
|
|
223
|
-
# @api private
|
|
224
|
-
def build_ssh_args(state)
|
|
225
|
-
combined = config.to_hash.merge(state)
|
|
226
|
-
|
|
227
|
-
opts = Hash.new
|
|
228
|
-
opts[:user_known_hosts_file] = "/dev/null"
|
|
229
|
-
opts[:paranoid] = false
|
|
230
|
-
opts[:keys_only] = true if combined[:ssh_key]
|
|
231
|
-
opts[:password] = combined[:password] if combined[:password]
|
|
232
|
-
opts[:forward_agent] = combined[:forward_agent] if combined.key? :forward_agent
|
|
233
|
-
opts[:port] = combined[:port] if combined[:port]
|
|
234
|
-
opts[:keys] = Array(combined[:ssh_key]) if combined[:ssh_key]
|
|
235
|
-
opts[:logger] = logger
|
|
236
|
-
|
|
237
|
-
[combined[:hostname], combined[:username], opts]
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# Adds http, https and ftp proxy environment variables to a command, if
|
|
241
|
-
# set in configuration data or on local workstation.
|
|
242
|
-
#
|
|
243
|
-
# @param cmd [String] command string
|
|
244
|
-
# @return [String] command string
|
|
245
|
-
# @api private
|
|
246
|
-
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
|
247
|
-
def env_cmd(cmd)
|
|
248
|
-
return if cmd.nil?
|
|
249
|
-
env = "env"
|
|
250
|
-
http_proxy = config[:http_proxy] || ENV["http_proxy"] ||
|
|
251
|
-
ENV["HTTP_PROXY"]
|
|
252
|
-
https_proxy = config[:https_proxy] || ENV["https_proxy"] ||
|
|
253
|
-
ENV["HTTPS_PROXY"]
|
|
254
|
-
ftp_proxy = config[:ftp_proxy] || ENV["ftp_proxy"] ||
|
|
255
|
-
ENV["FTP_PROXY"]
|
|
256
|
-
no_proxy = if (!config[:http_proxy] && http_proxy) ||
|
|
257
|
-
(!config[:https_proxy] && https_proxy) ||
|
|
258
|
-
(!config[:ftp_proxy] && ftp_proxy)
|
|
259
|
-
ENV["no_proxy"] || ENV["NO_PROXY"]
|
|
260
|
-
end
|
|
261
|
-
env << " http_proxy=#{http_proxy}" if http_proxy
|
|
262
|
-
env << " https_proxy=#{https_proxy}" if https_proxy
|
|
263
|
-
env << " ftp_proxy=#{ftp_proxy}" if ftp_proxy
|
|
264
|
-
env << " no_proxy=#{no_proxy}" if no_proxy
|
|
265
|
-
|
|
266
|
-
env == "env" ? cmd : "#{env} #{cmd}"
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# Executes a remote command over SSH.
|
|
270
|
-
#
|
|
271
|
-
# @param command [String] remove command to run
|
|
272
|
-
# @param connection [Kitchen::SSH] an SSH connection
|
|
273
|
-
# @raise [ActionFailed] if an exception occurs
|
|
274
|
-
# @api private
|
|
275
|
-
def run_remote(command, connection)
|
|
276
|
-
return if command.nil?
|
|
277
|
-
|
|
278
|
-
connection.exec(env_cmd(command))
|
|
279
|
-
rescue SSHFailed, Net::SSH::Exception => ex
|
|
280
|
-
raise ActionFailed, ex.message
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Transfers one or more local paths over SSH.
|
|
284
|
-
#
|
|
285
|
-
# @param locals [Array<String>] array of local paths
|
|
286
|
-
# @param remote [String] remote destination path
|
|
287
|
-
# @param connection [Kitchen::SSH] an SSH connection
|
|
288
|
-
# @raise [ActionFailed] if an exception occurs
|
|
289
|
-
# @api private
|
|
290
|
-
def transfer_path(locals, remote, connection)
|
|
291
|
-
return if locals.nil? || Array(locals).empty?
|
|
292
|
-
|
|
293
|
-
info("Transferring files to #{instance.to_str}")
|
|
294
|
-
locals.each { |local| connection.upload_path!(local, remote) }
|
|
295
|
-
debug("Transfer complete")
|
|
296
|
-
rescue SSHFailed, Net::SSH::Exception => ex
|
|
297
|
-
raise ActionFailed, ex.message
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
# Blocks until a TCP socket is available where a remote SSH server
|
|
301
|
-
# should be listening.
|
|
302
|
-
#
|
|
303
|
-
# @param hostname [String] remote SSH server host
|
|
304
|
-
# @param username [String] SSH username (default: `nil`)
|
|
305
|
-
# @param options [Hash] configuration hash (default: `{}`)
|
|
306
|
-
# @api private
|
|
307
|
-
def wait_for_sshd(hostname, username = nil, options = {})
|
|
308
|
-
pseudo_state = { :hostname => hostname }
|
|
309
|
-
pseudo_state[:username] = username if username
|
|
310
|
-
pseudo_state.merge!(options)
|
|
311
|
-
|
|
312
|
-
instance.transport.connection(backcompat_merged_state(pseudo_state)).
|
|
313
|
-
wait_until_ready
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
# Intercepts any bare #puts calls in subclasses and issues an INFO log
|
|
317
|
-
# event instead.
|
|
318
|
-
#
|
|
319
|
-
# @param msg [String] message string
|
|
320
|
-
def puts(msg)
|
|
321
|
-
info(msg)
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
# Intercepts any bare #print calls in subclasses and issues an INFO log
|
|
325
|
-
# event instead.
|
|
326
|
-
#
|
|
327
|
-
# @param msg [String] message string
|
|
328
|
-
def print(msg)
|
|
329
|
-
info(msg)
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
# Delegates to Kitchen::ShellOut.run_command, overriding some default
|
|
333
|
-
# options:
|
|
334
|
-
#
|
|
335
|
-
# * `:use_sudo` defaults to the value of `config[:use_sudo]` in the
|
|
336
|
-
# Driver object
|
|
337
|
-
# * `:log_subject` defaults to a String representation of the Driver's
|
|
338
|
-
# class name
|
|
339
|
-
#
|
|
340
|
-
# @see ShellOut#run_command
|
|
341
|
-
def run_command(cmd, options = {})
|
|
342
|
-
base_options = {
|
|
343
|
-
:use_sudo => config[:use_sudo],
|
|
344
|
-
:log_subject => Thor::Util.snake_case(self.class.to_s)
|
|
345
|
-
}.merge(options)
|
|
346
|
-
super(cmd, base_options)
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
# Returns the Busser object associated with the driver.
|
|
350
|
-
#
|
|
351
|
-
# @return [Busser] a busser
|
|
352
|
-
def busser
|
|
353
|
-
instance.verifier
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
end
|
|
357
|
-
end
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
|
+
#
|
|
5
|
+
# Copyright (C) 2012, 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
|
+
module Driver
|
|
26
|
+
|
|
27
|
+
# Legacy base class for a driver that uses SSH to communication with an
|
|
28
|
+
# instance. This class has been updated to use the Instance's Transport to
|
|
29
|
+
# issue commands and transfer files and no longer uses the `Kitchen:SSH`
|
|
30
|
+
# class directly.
|
|
31
|
+
#
|
|
32
|
+
# **NOTE:** Authors of new Drivers are encouraged to inherit from
|
|
33
|
+
# `Kitchen::Driver::Base` instead and existing Driver authors are
|
|
34
|
+
# encouraged to update their Driver class to inherit from
|
|
35
|
+
# `Kitchen::Driver::SSHBase`.
|
|
36
|
+
#
|
|
37
|
+
# A subclass must implement the following methods:
|
|
38
|
+
# * #create(state)
|
|
39
|
+
# * #destroy(state)
|
|
40
|
+
#
|
|
41
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
42
|
+
# @deprecated While all possible effort has been made to preserve the
|
|
43
|
+
# original behavior of this class, future improvements to the Driver,
|
|
44
|
+
# Transport, and Verifier subsystems may not be picked up in these
|
|
45
|
+
# Drivers. When legacy Driver::SSHBase support is removed, this class
|
|
46
|
+
# will no longer be available.
|
|
47
|
+
class SSHBase
|
|
48
|
+
|
|
49
|
+
include ShellOut
|
|
50
|
+
include Configurable
|
|
51
|
+
include Logging
|
|
52
|
+
|
|
53
|
+
default_config :sudo, true
|
|
54
|
+
default_config :port, 22
|
|
55
|
+
|
|
56
|
+
# Creates a new Driver object using the provided configuration data
|
|
57
|
+
# which will be merged with any default configuration.
|
|
58
|
+
#
|
|
59
|
+
# @param config [Hash] provided driver configuration
|
|
60
|
+
def initialize(config = {})
|
|
61
|
+
init_config(config)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# (see Base#create)
|
|
65
|
+
def create(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
66
|
+
raise ClientError, "#{self.class}#create must be implemented"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# (see Base#converge)
|
|
70
|
+
def converge(state) # rubocop:disable Metrics/AbcSize
|
|
71
|
+
provisioner = instance.provisioner
|
|
72
|
+
provisioner.create_sandbox
|
|
73
|
+
sandbox_dirs = Dir.glob("#{provisioner.sandbox_path}/*")
|
|
74
|
+
|
|
75
|
+
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
76
|
+
conn.execute(env_cmd(provisioner.install_command))
|
|
77
|
+
conn.execute(env_cmd(provisioner.init_command))
|
|
78
|
+
info("Transferring files to #{instance.to_str}")
|
|
79
|
+
conn.upload(sandbox_dirs, provisioner[:root_path])
|
|
80
|
+
debug("Transfer complete")
|
|
81
|
+
conn.execute(env_cmd(provisioner.prepare_command))
|
|
82
|
+
conn.execute(env_cmd(provisioner.run_command))
|
|
83
|
+
end
|
|
84
|
+
rescue Kitchen::Transport::TransportFailed => ex
|
|
85
|
+
raise ActionFailed, ex.message
|
|
86
|
+
ensure
|
|
87
|
+
instance.provisioner.cleanup_sandbox
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# (see Base#setup)
|
|
91
|
+
def setup(state)
|
|
92
|
+
verifier = instance.verifier
|
|
93
|
+
|
|
94
|
+
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
95
|
+
conn.execute(env_cmd(verifier.install_command))
|
|
96
|
+
end
|
|
97
|
+
rescue Kitchen::Transport::TransportFailed => ex
|
|
98
|
+
raise ActionFailed, ex.message
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# (see Base#verify)
|
|
102
|
+
def verify(state) # rubocop:disable Metrics/AbcSize
|
|
103
|
+
verifier = instance.verifier
|
|
104
|
+
verifier.create_sandbox
|
|
105
|
+
sandbox_dirs = Dir.glob(File.join(verifier.sandbox_path, "*"))
|
|
106
|
+
|
|
107
|
+
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
108
|
+
conn.execute(env_cmd(verifier.init_command))
|
|
109
|
+
info("Transferring files to #{instance.to_str}")
|
|
110
|
+
conn.upload(sandbox_dirs, verifier[:root_path])
|
|
111
|
+
debug("Transfer complete")
|
|
112
|
+
conn.execute(env_cmd(verifier.prepare_command))
|
|
113
|
+
conn.execute(env_cmd(verifier.run_command))
|
|
114
|
+
end
|
|
115
|
+
rescue Kitchen::Transport::TransportFailed => ex
|
|
116
|
+
raise ActionFailed, ex.message
|
|
117
|
+
ensure
|
|
118
|
+
instance.verifier.cleanup_sandbox
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# (see Base#destroy)
|
|
122
|
+
def destroy(state) # rubocop:disable Lint/UnusedMethodArgument
|
|
123
|
+
raise ClientError, "#{self.class}#destroy must be implemented"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def legacy_state(state)
|
|
127
|
+
backcompat_merged_state(state)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# (see Base#login_command)
|
|
131
|
+
def login_command(state)
|
|
132
|
+
instance.transport.connection(backcompat_merged_state(state)).
|
|
133
|
+
login_command
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Executes an arbitrary command on an instance over an SSH connection.
|
|
137
|
+
#
|
|
138
|
+
# @param state [Hash] mutable instance and driver state
|
|
139
|
+
# @param command [String] the command to be executed
|
|
140
|
+
# @raise [ActionFailed] if the command could not be successfully completed
|
|
141
|
+
def remote_command(state, command)
|
|
142
|
+
instance.transport.connection(backcompat_merged_state(state)) do |conn|
|
|
143
|
+
conn.execute(env_cmd(command))
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# **(Deprecated)** Executes a remote command over SSH.
|
|
148
|
+
#
|
|
149
|
+
# @param ssh_args [Array] ssh arguments
|
|
150
|
+
# @param command [String] remote command to invoke
|
|
151
|
+
# @deprecated This method should no longer be called directly and exists
|
|
152
|
+
# to support very old drivers. This will be removed in the future.
|
|
153
|
+
def ssh(ssh_args, command)
|
|
154
|
+
pseudo_state = { :hostname => ssh_args[0], :username => ssh_args[1] }
|
|
155
|
+
pseudo_state.merge!(ssh_args[2])
|
|
156
|
+
connection_state = backcompat_merged_state(pseudo_state)
|
|
157
|
+
|
|
158
|
+
instance.transport.connection(connection_state) do |conn|
|
|
159
|
+
conn.execute(env_cmd(command))
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Performs whatever tests that may be required to ensure that this driver
|
|
164
|
+
# will be able to function in the current environment. This may involve
|
|
165
|
+
# checking for the presence of certain directories, software installed,
|
|
166
|
+
# etc.
|
|
167
|
+
#
|
|
168
|
+
# @raise [UserError] if the driver will not be able to perform or if a
|
|
169
|
+
# documented dependency is missing from the system
|
|
170
|
+
def verify_dependencies
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
class << self
|
|
174
|
+
# @return [Array<Symbol>] an array of action method names that cannot
|
|
175
|
+
# be run concurrently and must be run in serial via a shared mutex
|
|
176
|
+
attr_reader :serial_actions
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Registers certain driver actions that cannot be safely run concurrently
|
|
180
|
+
# in threads across multiple instances. Typically this might be used
|
|
181
|
+
# for create or destroy actions that use an underlying resource that
|
|
182
|
+
# cannot be used at the same time.
|
|
183
|
+
#
|
|
184
|
+
# A shared mutex for this driver object will be used to synchronize all
|
|
185
|
+
# registered methods.
|
|
186
|
+
#
|
|
187
|
+
# @example a single action method that cannot be run concurrently
|
|
188
|
+
#
|
|
189
|
+
# no_parallel_for :create
|
|
190
|
+
#
|
|
191
|
+
# @example multiple action methods that cannot be run concurrently
|
|
192
|
+
#
|
|
193
|
+
# no_parallel_for :create, :destroy
|
|
194
|
+
#
|
|
195
|
+
# @param methods [Array<Symbol>] one or more actions as symbols
|
|
196
|
+
# @raise [ClientError] if any method is not a valid action method name
|
|
197
|
+
def self.no_parallel_for(*methods)
|
|
198
|
+
action_methods = [:create, :converge, :setup, :verify, :destroy]
|
|
199
|
+
|
|
200
|
+
Array(methods).each do |meth|
|
|
201
|
+
next if action_methods.include?(meth)
|
|
202
|
+
|
|
203
|
+
raise ClientError, "##{meth} is not a valid no_parallel_for method"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
@serial_actions ||= []
|
|
207
|
+
@serial_actions += methods
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
private
|
|
211
|
+
|
|
212
|
+
def backcompat_merged_state(state)
|
|
213
|
+
driver_ssh_keys = %w[
|
|
214
|
+
forward_agent hostname password port ssh_key username
|
|
215
|
+
].map(&:to_sym)
|
|
216
|
+
config.select { |key, _| driver_ssh_keys.include?(key) }.rmerge(state)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Builds arguments for constructing a `Kitchen::SSH` instance.
|
|
220
|
+
#
|
|
221
|
+
# @param state [Hash] state hash
|
|
222
|
+
# @return [Array] SSH constructor arguments
|
|
223
|
+
# @api private
|
|
224
|
+
def build_ssh_args(state)
|
|
225
|
+
combined = config.to_hash.merge(state)
|
|
226
|
+
|
|
227
|
+
opts = Hash.new
|
|
228
|
+
opts[:user_known_hosts_file] = "/dev/null"
|
|
229
|
+
opts[:paranoid] = false
|
|
230
|
+
opts[:keys_only] = true if combined[:ssh_key]
|
|
231
|
+
opts[:password] = combined[:password] if combined[:password]
|
|
232
|
+
opts[:forward_agent] = combined[:forward_agent] if combined.key? :forward_agent
|
|
233
|
+
opts[:port] = combined[:port] if combined[:port]
|
|
234
|
+
opts[:keys] = Array(combined[:ssh_key]) if combined[:ssh_key]
|
|
235
|
+
opts[:logger] = logger
|
|
236
|
+
|
|
237
|
+
[combined[:hostname], combined[:username], opts]
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Adds http, https and ftp proxy environment variables to a command, if
|
|
241
|
+
# set in configuration data or on local workstation.
|
|
242
|
+
#
|
|
243
|
+
# @param cmd [String] command string
|
|
244
|
+
# @return [String] command string
|
|
245
|
+
# @api private
|
|
246
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
|
247
|
+
def env_cmd(cmd)
|
|
248
|
+
return if cmd.nil?
|
|
249
|
+
env = "env"
|
|
250
|
+
http_proxy = config[:http_proxy] || ENV["http_proxy"] ||
|
|
251
|
+
ENV["HTTP_PROXY"]
|
|
252
|
+
https_proxy = config[:https_proxy] || ENV["https_proxy"] ||
|
|
253
|
+
ENV["HTTPS_PROXY"]
|
|
254
|
+
ftp_proxy = config[:ftp_proxy] || ENV["ftp_proxy"] ||
|
|
255
|
+
ENV["FTP_PROXY"]
|
|
256
|
+
no_proxy = if (!config[:http_proxy] && http_proxy) ||
|
|
257
|
+
(!config[:https_proxy] && https_proxy) ||
|
|
258
|
+
(!config[:ftp_proxy] && ftp_proxy)
|
|
259
|
+
ENV["no_proxy"] || ENV["NO_PROXY"]
|
|
260
|
+
end
|
|
261
|
+
env << " http_proxy=#{http_proxy}" if http_proxy
|
|
262
|
+
env << " https_proxy=#{https_proxy}" if https_proxy
|
|
263
|
+
env << " ftp_proxy=#{ftp_proxy}" if ftp_proxy
|
|
264
|
+
env << " no_proxy=#{no_proxy}" if no_proxy
|
|
265
|
+
|
|
266
|
+
env == "env" ? cmd : "#{env} #{cmd}"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Executes a remote command over SSH.
|
|
270
|
+
#
|
|
271
|
+
# @param command [String] remove command to run
|
|
272
|
+
# @param connection [Kitchen::SSH] an SSH connection
|
|
273
|
+
# @raise [ActionFailed] if an exception occurs
|
|
274
|
+
# @api private
|
|
275
|
+
def run_remote(command, connection)
|
|
276
|
+
return if command.nil?
|
|
277
|
+
|
|
278
|
+
connection.exec(env_cmd(command))
|
|
279
|
+
rescue SSHFailed, Net::SSH::Exception => ex
|
|
280
|
+
raise ActionFailed, ex.message
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Transfers one or more local paths over SSH.
|
|
284
|
+
#
|
|
285
|
+
# @param locals [Array<String>] array of local paths
|
|
286
|
+
# @param remote [String] remote destination path
|
|
287
|
+
# @param connection [Kitchen::SSH] an SSH connection
|
|
288
|
+
# @raise [ActionFailed] if an exception occurs
|
|
289
|
+
# @api private
|
|
290
|
+
def transfer_path(locals, remote, connection)
|
|
291
|
+
return if locals.nil? || Array(locals).empty?
|
|
292
|
+
|
|
293
|
+
info("Transferring files to #{instance.to_str}")
|
|
294
|
+
locals.each { |local| connection.upload_path!(local, remote) }
|
|
295
|
+
debug("Transfer complete")
|
|
296
|
+
rescue SSHFailed, Net::SSH::Exception => ex
|
|
297
|
+
raise ActionFailed, ex.message
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Blocks until a TCP socket is available where a remote SSH server
|
|
301
|
+
# should be listening.
|
|
302
|
+
#
|
|
303
|
+
# @param hostname [String] remote SSH server host
|
|
304
|
+
# @param username [String] SSH username (default: `nil`)
|
|
305
|
+
# @param options [Hash] configuration hash (default: `{}`)
|
|
306
|
+
# @api private
|
|
307
|
+
def wait_for_sshd(hostname, username = nil, options = {})
|
|
308
|
+
pseudo_state = { :hostname => hostname }
|
|
309
|
+
pseudo_state[:username] = username if username
|
|
310
|
+
pseudo_state.merge!(options)
|
|
311
|
+
|
|
312
|
+
instance.transport.connection(backcompat_merged_state(pseudo_state)).
|
|
313
|
+
wait_until_ready
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Intercepts any bare #puts calls in subclasses and issues an INFO log
|
|
317
|
+
# event instead.
|
|
318
|
+
#
|
|
319
|
+
# @param msg [String] message string
|
|
320
|
+
def puts(msg)
|
|
321
|
+
info(msg)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Intercepts any bare #print calls in subclasses and issues an INFO log
|
|
325
|
+
# event instead.
|
|
326
|
+
#
|
|
327
|
+
# @param msg [String] message string
|
|
328
|
+
def print(msg)
|
|
329
|
+
info(msg)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Delegates to Kitchen::ShellOut.run_command, overriding some default
|
|
333
|
+
# options:
|
|
334
|
+
#
|
|
335
|
+
# * `:use_sudo` defaults to the value of `config[:use_sudo]` in the
|
|
336
|
+
# Driver object
|
|
337
|
+
# * `:log_subject` defaults to a String representation of the Driver's
|
|
338
|
+
# class name
|
|
339
|
+
#
|
|
340
|
+
# @see ShellOut#run_command
|
|
341
|
+
def run_command(cmd, options = {})
|
|
342
|
+
base_options = {
|
|
343
|
+
:use_sudo => config[:use_sudo],
|
|
344
|
+
:log_subject => Thor::Util.snake_case(self.class.to_s)
|
|
345
|
+
}.merge(options)
|
|
346
|
+
super(cmd, base_options)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Returns the Busser object associated with the driver.
|
|
350
|
+
#
|
|
351
|
+
# @return [Busser] a busser
|
|
352
|
+
def busser
|
|
353
|
+
instance.verifier
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|