train 3.2.14 → 3.2.20
Sign up to get free protection for your applications and to get access to all the features.
- 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
|