train 3.2.14 → 3.2.20
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
- metadata +29 -149
- data/LICENSE +0 -201
- data/lib/train.rb +0 -193
- data/lib/train/errors.rb +0 -44
- data/lib/train/extras.rb +0 -11
- data/lib/train/extras/command_wrapper.rb +0 -201
- data/lib/train/extras/stat.rb +0 -136
- data/lib/train/file.rb +0 -212
- data/lib/train/file/local.rb +0 -82
- data/lib/train/file/local/unix.rb +0 -96
- data/lib/train/file/local/windows.rb +0 -68
- data/lib/train/file/remote.rb +0 -40
- data/lib/train/file/remote/aix.rb +0 -29
- data/lib/train/file/remote/linux.rb +0 -21
- data/lib/train/file/remote/qnx.rb +0 -41
- data/lib/train/file/remote/unix.rb +0 -110
- data/lib/train/file/remote/windows.rb +0 -110
- data/lib/train/globals.rb +0 -5
- data/lib/train/options.rb +0 -81
- data/lib/train/platforms.rb +0 -102
- data/lib/train/platforms/common.rb +0 -34
- data/lib/train/platforms/detect.rb +0 -12
- data/lib/train/platforms/detect/helpers/os_common.rb +0 -160
- data/lib/train/platforms/detect/helpers/os_linux.rb +0 -80
- data/lib/train/platforms/detect/helpers/os_windows.rb +0 -142
- data/lib/train/platforms/detect/scanner.rb +0 -85
- data/lib/train/platforms/detect/specifications/api.rb +0 -20
- data/lib/train/platforms/detect/specifications/os.rb +0 -629
- data/lib/train/platforms/detect/uuid.rb +0 -32
- data/lib/train/platforms/family.rb +0 -31
- data/lib/train/platforms/platform.rb +0 -109
- data/lib/train/plugin_test_helper.rb +0 -51
- data/lib/train/plugins.rb +0 -40
- data/lib/train/plugins/base_connection.rb +0 -198
- data/lib/train/plugins/transport.rb +0 -49
- data/lib/train/transports/cisco_ios_connection.rb +0 -133
- data/lib/train/transports/local.rb +0 -240
- data/lib/train/transports/mock.rb +0 -183
- data/lib/train/transports/ssh.rb +0 -271
- data/lib/train/transports/ssh_connection.rb +0 -342
- data/lib/train/version.rb +0 -7
data/lib/train.rb
DELETED
@@ -1,193 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
#
|
3
|
-
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
|
-
|
5
|
-
require_relative "train/version"
|
6
|
-
require_relative "train/options"
|
7
|
-
require_relative "train/plugins"
|
8
|
-
require_relative "train/errors"
|
9
|
-
require_relative "train/platforms"
|
10
|
-
require "uri"
|
11
|
-
|
12
|
-
module Train
|
13
|
-
# Create a new transport instance, with the plugin indicated by the
|
14
|
-
# given name.
|
15
|
-
#
|
16
|
-
# @param [String] name of the plugin
|
17
|
-
# @param [Array] *args list of arguments for the plugin
|
18
|
-
# @return [Transport] instance of the new transport or nil
|
19
|
-
def self.create(name, *args)
|
20
|
-
cls = load_transport(name)
|
21
|
-
cls.new(*args) unless cls.nil?
|
22
|
-
end
|
23
|
-
|
24
|
-
# Retrieve the configuration options of a transport plugin.
|
25
|
-
#
|
26
|
-
# @param [String] name of the plugin
|
27
|
-
# @return [Hash] map of default options
|
28
|
-
def self.options(name)
|
29
|
-
cls = load_transport(name)
|
30
|
-
cls.default_options unless cls.nil?
|
31
|
-
end
|
32
|
-
|
33
|
-
# Load the transport plugin indicated by name. If the plugin is not
|
34
|
-
# yet found in the plugin registry, it will be attempted to load from
|
35
|
-
# `train/transports/plugin_name`.
|
36
|
-
#
|
37
|
-
# @param [String] name of the plugin
|
38
|
-
# @return [Train::Transport] the transport plugin
|
39
|
-
def self.load_transport(transport_name)
|
40
|
-
transport_name = transport_name.to_s
|
41
|
-
transport_class = Train::Plugins.registry[transport_name]
|
42
|
-
return transport_class unless transport_class.nil?
|
43
|
-
|
44
|
-
# Try to load the transport name from the core transports...
|
45
|
-
require "train/transports/" + transport_name
|
46
|
-
Train::Plugins.registry[transport_name]
|
47
|
-
rescue LoadError => _
|
48
|
-
begin
|
49
|
-
# If it's not in the core transports, try loading from a train plugin gem.
|
50
|
-
gem_name = "train-" + transport_name
|
51
|
-
require gem_name
|
52
|
-
return Train::Plugins.registry[transport_name]
|
53
|
-
# rubocop: disable Lint/HandleExceptions
|
54
|
-
rescue LoadError => _
|
55
|
-
# rubocop: enable Lint/HandleExceptions
|
56
|
-
# Intentionally empty rescue - we're handling it below anyway
|
57
|
-
end
|
58
|
-
|
59
|
-
ex = Train::PluginLoadError.new("Can't find train plugin #{transport_name}. Please install it first.")
|
60
|
-
ex.transport_name = transport_name
|
61
|
-
raise ex
|
62
|
-
end
|
63
|
-
|
64
|
-
# Legacy code to unpack a series of items from an incoming Hash
|
65
|
-
# Inspec::Config.unpack_train_credentials now handles this in most cases that InSpec needs
|
66
|
-
# If you need to unpack a URI, use unpack_target_from_uri
|
67
|
-
# TODO: deprecate; can't issue a warning because train doesn't have a logger until the connection is setup (See base_connection.rb)
|
68
|
-
def self.target_config(config = nil)
|
69
|
-
conf = config.dup
|
70
|
-
# Symbolize keys
|
71
|
-
conf.keys.each do |key|
|
72
|
-
unless key.is_a? Symbol
|
73
|
-
conf[key.to_sym] = conf.delete(key)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
group_keys_and_keyfiles(conf) # TODO: move logic into SSH plugin
|
78
|
-
return conf if conf[:target].to_s.empty?
|
79
|
-
|
80
|
-
unpack_target_from_uri(conf[:target], conf).merge(conf)
|
81
|
-
end
|
82
|
-
|
83
|
-
# Given a string that looks like a URI, unpack connection credentials.
|
84
|
-
# The name of the desired transport is always taken from the 'scheme' slot of the URI;
|
85
|
-
# the remaining portion of the URI is parsed as if it were an HTTP URL, and then
|
86
|
-
# the URL components are stored in the credentials hash. It is up to the transport
|
87
|
-
# to interpret the fields in a sensible way for that transport.
|
88
|
-
# New transport authors are encouraged to use transport://credset format (see
|
89
|
-
# inspec/inspec/issues/3661) rather than inventing a new field mapping.
|
90
|
-
def self.unpack_target_from_uri(uri_string, opts = {}) # rubocop: disable Metrics/AbcSize
|
91
|
-
creds = {}
|
92
|
-
return creds if uri_string.empty?
|
93
|
-
|
94
|
-
# split up the target's host/scheme configuration
|
95
|
-
uri = parse_uri(uri_string)
|
96
|
-
unless uri.host.nil? && uri.scheme.nil?
|
97
|
-
creds[:backend] ||= uri.scheme
|
98
|
-
creds[:host] ||= uri.hostname
|
99
|
-
creds[:port] ||= uri.port
|
100
|
-
creds[:user] ||= uri.user
|
101
|
-
creds[:path] ||= uri.path
|
102
|
-
creds[:password] ||=
|
103
|
-
if opts[:www_form_encoded_password] && !uri.password.nil?
|
104
|
-
URI.decode_www_form_component(uri.password)
|
105
|
-
else
|
106
|
-
uri.password
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# ensure path is nil, if its empty; e.g. required to reset defaults for winrm # TODO: move logic into winrm plugin
|
111
|
-
creds[:path] = nil if !creds[:path].nil? && creds[:path].to_s.empty?
|
112
|
-
|
113
|
-
# compact! is available in ruby 2.4+
|
114
|
-
# TODO: rewrite next line using compact! once we drop support for ruby 2.3
|
115
|
-
creds = creds.delete_if { |_, value| value.nil? }
|
116
|
-
|
117
|
-
# return the updated config
|
118
|
-
creds
|
119
|
-
end
|
120
|
-
|
121
|
-
# Parse a URI. Supports empty URI's with paths, e.g. `mock://`
|
122
|
-
#
|
123
|
-
# @param string [string] URI string, e.g. `schema://domain.com`
|
124
|
-
# @return [URI::Generic] parsed URI object
|
125
|
-
def self.parse_uri(string)
|
126
|
-
URI.parse(string)
|
127
|
-
rescue URI::InvalidURIError => e
|
128
|
-
# A use-case we want to catch is parsing empty URIs with a schema
|
129
|
-
# e.g. mock://. To do this, we match it manually and fake the hostname
|
130
|
-
case string
|
131
|
-
when %r{^([a-z]+)://$}
|
132
|
-
string += "dummy"
|
133
|
-
when /^([a-z]+):$/
|
134
|
-
string += "//dummy"
|
135
|
-
else
|
136
|
-
raise Train::UserError, e
|
137
|
-
end
|
138
|
-
|
139
|
-
uri = URI.parse(string)
|
140
|
-
uri.host = nil
|
141
|
-
uri
|
142
|
-
end
|
143
|
-
private_class_method :parse_uri
|
144
|
-
|
145
|
-
# Examine the given credential information, and if all is well,
|
146
|
-
# return the transport name.
|
147
|
-
# TODO: this actually does no validation of the credential options whatsoever
|
148
|
-
def self.validate_backend(credentials, default_transport_name = "local")
|
149
|
-
return default_transport_name if credentials.nil?
|
150
|
-
|
151
|
-
transport_name = credentials[:backend]
|
152
|
-
|
153
|
-
# TODO: Determine if it is ever possible (or supported) for transport_name to be 'localhost'
|
154
|
-
# TODO: After inspec/inspec/pull/3750 is merged, should be able to remove nil from the list
|
155
|
-
if credentials[:sudo] && [nil, "local", "localhost"].include?(transport_name)
|
156
|
-
raise Train::UserError, "Sudo is only valid when running against a remote host. "\
|
157
|
-
"To run this locally with elevated privileges, run the command with `sudo ...`."
|
158
|
-
end
|
159
|
-
|
160
|
-
return transport_name unless transport_name.nil?
|
161
|
-
|
162
|
-
unless credentials[:target].nil?
|
163
|
-
# We should not get here, because if target_uri unpacking was successful,
|
164
|
-
# it would have set credentials[:backend]
|
165
|
-
raise Train::UserError, "Cannot determine backend from target "\
|
166
|
-
"configuration #{credentials[:target]}. Valid example: ssh://192.168.0.1"
|
167
|
-
end
|
168
|
-
|
169
|
-
unless credentials[:host].nil?
|
170
|
-
raise Train::UserError, "Host configured, but no backend was provided. Please "\
|
171
|
-
"specify how you want to connect. Valid example: ssh://192.168.0.1"
|
172
|
-
end
|
173
|
-
|
174
|
-
credentials[:backend] = default_transport_name
|
175
|
-
end
|
176
|
-
|
177
|
-
def self.group_keys_and_keyfiles(conf)
|
178
|
-
# in case the user specified a key-file, register it that way
|
179
|
-
# we will clear the list of keys and put keys and key_files separately
|
180
|
-
keys_mixed = conf[:keys]
|
181
|
-
return if keys_mixed.nil?
|
182
|
-
|
183
|
-
conf[:key_files] = []
|
184
|
-
conf[:keys] = []
|
185
|
-
keys_mixed.each do |key|
|
186
|
-
if !key.nil? && File.file?(key)
|
187
|
-
conf[:key_files].push(key)
|
188
|
-
else
|
189
|
-
conf[:keys].push(key)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
data/lib/train/errors.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
#
|
3
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
4
|
-
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
5
|
-
# Author:: Christoph Hartmann (<chris@lollyrock.com>)
|
6
|
-
#
|
7
|
-
# Copyright (C) 2013, Fletcher Nichol
|
8
|
-
#
|
9
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
10
|
-
|
11
|
-
module Train
|
12
|
-
# Base exception for any exception explicitly raised by the Train library.
|
13
|
-
class Error < ::StandardError
|
14
|
-
attr_reader :reason
|
15
|
-
|
16
|
-
def initialize(message = "", reason = :not_provided)
|
17
|
-
super(message)
|
18
|
-
@reason = reason
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# Base exception class for all exceptions that are caused by user input
|
23
|
-
# errors.
|
24
|
-
class UserError < Error; end
|
25
|
-
|
26
|
-
# We could not load a plugin, because of a user error
|
27
|
-
class PluginLoadError < UserError
|
28
|
-
attr_accessor :transport_name
|
29
|
-
end
|
30
|
-
|
31
|
-
# Base exception class for all exceptions that are caused by incorrect use
|
32
|
-
# of an API.
|
33
|
-
class ClientError < Error; end
|
34
|
-
|
35
|
-
# Base exception class for all exceptions that are caused by other failures
|
36
|
-
# in the transport layer.
|
37
|
-
class TransportError < Error; end
|
38
|
-
|
39
|
-
# Exception for when no platform can be detected.
|
40
|
-
class PlatformDetectionFailed < Error; end
|
41
|
-
|
42
|
-
# Exception for when a invalid cache type is passed.
|
43
|
-
class UnknownCacheType < Error; end
|
44
|
-
end
|
data/lib/train/extras.rb
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
#
|
3
|
-
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
|
-
|
5
|
-
module Train::Extras
|
6
|
-
require_relative "extras/command_wrapper"
|
7
|
-
require_relative "extras/stat"
|
8
|
-
|
9
|
-
CommandResult = Struct.new(:stdout, :stderr, :exit_status)
|
10
|
-
LoginCommand = Struct.new(:command, :arguments)
|
11
|
-
end
|
@@ -1,201 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
require "base64"
|
6
|
-
require_relative "../errors"
|
7
|
-
|
8
|
-
module Train::Extras
|
9
|
-
# Define the interface of all command wrappers.
|
10
|
-
class CommandWrapperBase
|
11
|
-
# Verify that the command wrapper is initialized properly and working.
|
12
|
-
#
|
13
|
-
# @return [Any] verification result, nil if all went well, otherwise a message
|
14
|
-
def verify
|
15
|
-
raise Train::ClientError, "#{self.class} does not implement #verify()"
|
16
|
-
end
|
17
|
-
|
18
|
-
# Wrap a command and return the augmented command which can be executed.
|
19
|
-
#
|
20
|
-
# @param [Strin] command that will be wrapper
|
21
|
-
# @return [String] result of wrapping the command
|
22
|
-
def run(_command)
|
23
|
-
raise Train::ClientError, "#{self.class} does not implement #run(command)"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Wrap linux commands and add functionality like sudo.
|
28
|
-
class LinuxCommand < CommandWrapperBase
|
29
|
-
Train::Options.attach(self)
|
30
|
-
|
31
|
-
option :shell, default: false
|
32
|
-
option :shell_options, default: nil
|
33
|
-
option :shell_command, default: nil
|
34
|
-
option :sudo, default: false
|
35
|
-
option :sudo_options, default: nil
|
36
|
-
option :sudo_password, default: nil
|
37
|
-
option :sudo_command, default: nil
|
38
|
-
option :user
|
39
|
-
|
40
|
-
attr_reader :backend
|
41
|
-
|
42
|
-
def initialize(backend, options)
|
43
|
-
@backend = backend
|
44
|
-
validate_options(options)
|
45
|
-
|
46
|
-
@shell = options[:shell]
|
47
|
-
@shell_options = options[:shell_options] # e.g. '--login'
|
48
|
-
@shell_command = options[:shell_command] # e.g. '/bin/sh'
|
49
|
-
@sudo = options[:sudo]
|
50
|
-
@sudo_options = options[:sudo_options]
|
51
|
-
@sudo_password = options[:sudo_password]
|
52
|
-
@sudo_command = options[:sudo_command]
|
53
|
-
@user = options[:user]
|
54
|
-
end
|
55
|
-
|
56
|
-
# (see CommandWrapperBase::verify)
|
57
|
-
def verify
|
58
|
-
cmd = if @sudo
|
59
|
-
# Wrap it up. It needs /dev/null on the outside to disable stdin
|
60
|
-
# NOTE: can't use @sudo_command because -v conflicts with -E.
|
61
|
-
# See test-kitchen's use of this variable for conflict.
|
62
|
-
"sh -c '(sudo -v) < /dev/null'"
|
63
|
-
else
|
64
|
-
run("echo")
|
65
|
-
end
|
66
|
-
|
67
|
-
# rubocop:disable Style/BlockDelimiters
|
68
|
-
res = @backend.with_sudo_pty {
|
69
|
-
@backend.run_command(cmd)
|
70
|
-
}
|
71
|
-
return nil if res.exit_status == 0
|
72
|
-
|
73
|
-
rawerr = "#{res.stdout} #{res.stderr}".strip
|
74
|
-
|
75
|
-
case rawerr
|
76
|
-
when "Sorry, try again"
|
77
|
-
["Wrong sudo password.", :bad_sudo_password]
|
78
|
-
when "sudo: no tty present and no askpass program specified"
|
79
|
-
["Sudo requires a password, please configure it.", :sudo_password_required]
|
80
|
-
when "sudo: command not found"
|
81
|
-
["Can't find sudo command. Please either install and "\
|
82
|
-
"configure it on the target or deactivate sudo.", :sudo_command_not_found]
|
83
|
-
when "sudo: sorry, you must have a tty to run sudo"
|
84
|
-
["Sudo requires a TTY. Please see the README on how to configure "\
|
85
|
-
"sudo to allow for non-interactive usage.", :sudo_no_tty]
|
86
|
-
else
|
87
|
-
[rawerr, nil]
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def verify!
|
92
|
-
msg, reason = verify
|
93
|
-
return nil unless msg
|
94
|
-
|
95
|
-
raise Train::UserError.new("Sudo failed: #{msg}", reason)
|
96
|
-
end
|
97
|
-
|
98
|
-
# (see CommandWrapperBase::run)
|
99
|
-
def run(command)
|
100
|
-
shell_wrap(sudo_wrap(command))
|
101
|
-
end
|
102
|
-
|
103
|
-
def self.active?(options)
|
104
|
-
options.is_a?(Hash) && (
|
105
|
-
options[:sudo] ||
|
106
|
-
options[:shell]
|
107
|
-
)
|
108
|
-
end
|
109
|
-
|
110
|
-
private
|
111
|
-
|
112
|
-
# wrap the cmd in a sudo command
|
113
|
-
def sudo_wrap(cmd)
|
114
|
-
return cmd unless @sudo
|
115
|
-
return cmd if @user == "root"
|
116
|
-
|
117
|
-
res = (@sudo_command || "sudo") + " "
|
118
|
-
|
119
|
-
if @sudo_password
|
120
|
-
str = safe_string(@sudo_password + "\n")
|
121
|
-
res = "#{str} | #{res}-S "
|
122
|
-
end
|
123
|
-
|
124
|
-
res << "#{@sudo_options} " if @sudo_options
|
125
|
-
|
126
|
-
res + cmd
|
127
|
-
end
|
128
|
-
|
129
|
-
# wrap the cmd in a subshell allowing for options to
|
130
|
-
# passed to the subshell
|
131
|
-
def shell_wrap(cmd)
|
132
|
-
return cmd unless @shell
|
133
|
-
|
134
|
-
shell = @shell_command || "$SHELL"
|
135
|
-
options = " #{@shell_options}" if @shell_options
|
136
|
-
|
137
|
-
"#{safe_string(cmd)} | #{shell}#{options}"
|
138
|
-
end
|
139
|
-
|
140
|
-
# encapsulates encoding the string into a safe form, and decoding for use.
|
141
|
-
# @return [String] A command line snippet that can be used as part of a pipeline.
|
142
|
-
def safe_string(str)
|
143
|
-
b64str = Base64.strict_encode64(str)
|
144
|
-
"echo #{b64str} | base64 --decode"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# Wrap windows commands.
|
149
|
-
class WindowsCommand < CommandWrapperBase
|
150
|
-
Train::Options.attach(self)
|
151
|
-
|
152
|
-
option :shell_command, default: nil
|
153
|
-
|
154
|
-
def initialize(backend, options)
|
155
|
-
@backend = backend
|
156
|
-
validate_options(options)
|
157
|
-
|
158
|
-
@shell_command = options[:shell_command] # e.g. 'powershell'
|
159
|
-
end
|
160
|
-
|
161
|
-
# (see CommandWrapperBase::run)
|
162
|
-
def run(command)
|
163
|
-
powershell_wrap(command)
|
164
|
-
end
|
165
|
-
|
166
|
-
private
|
167
|
-
|
168
|
-
# Wrap the cmd in an encoded command to allow pipes and quotes
|
169
|
-
def powershell_wrap(cmd)
|
170
|
-
shell = @shell_command || "powershell"
|
171
|
-
|
172
|
-
# Prevent progress stream from leaking into stderr
|
173
|
-
script = "$ProgressPreference='SilentlyContinue';" + cmd
|
174
|
-
|
175
|
-
# Encode script so PowerShell can use it
|
176
|
-
script = script.encode("UTF-16LE", "UTF-8")
|
177
|
-
base64_script = Base64.strict_encode64(script)
|
178
|
-
|
179
|
-
cmd = "#{shell} -NoProfile -EncodedCommand #{base64_script}"
|
180
|
-
cmd
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
class CommandWrapper
|
185
|
-
include_options LinuxCommand
|
186
|
-
include_options WindowsCommand
|
187
|
-
|
188
|
-
def self.load(transport, options)
|
189
|
-
if transport.platform.unix?
|
190
|
-
return nil unless LinuxCommand.active?(options)
|
191
|
-
|
192
|
-
res = LinuxCommand.new(transport, options)
|
193
|
-
res.verify!
|
194
|
-
|
195
|
-
res
|
196
|
-
elsif transport.platform.windows?
|
197
|
-
WindowsCommand.new(transport, options)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|