train 0.29.2 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -2
- data/lib/train.rb +1 -0
- data/lib/train/errors.rb +6 -0
- data/lib/train/extras.rb +0 -1
- data/lib/train/platforms.rb +82 -0
- data/lib/train/platforms/common.rb +34 -0
- data/lib/train/platforms/detect.rb +12 -0
- data/lib/train/platforms/detect/helpers/os_common.rb +56 -0
- data/lib/train/platforms/detect/helpers/os_linux.rb +75 -0
- data/lib/train/{extras/os_detect_windows.rb → platforms/detect/helpers/os_windows.rb} +3 -10
- data/lib/train/platforms/detect/scanner.rb +84 -0
- data/lib/train/platforms/detect/specifications/os.rb +480 -0
- data/lib/train/platforms/family.rb +26 -0
- data/lib/train/platforms/platform.rb +80 -0
- data/lib/train/plugins/base_connection.rb +75 -27
- data/lib/train/transports/docker.rb +17 -28
- data/lib/train/transports/local.rb +21 -22
- data/lib/train/transports/mock.rb +44 -30
- data/lib/train/transports/ssh_connection.rb +55 -67
- data/lib/train/transports/winrm_connection.rb +16 -26
- data/lib/train/version.rb +1 -1
- data/test/unit/file/remote/linux_test.rb +2 -2
- data/test/unit/platforms/detect/os_common_test.rb +85 -0
- data/test/unit/platforms/detect/os_linux_test.rb +124 -0
- data/test/unit/{extras/os_detect_windows_test.rb → platforms/detect/os_windows_test.rb} +5 -2
- data/test/unit/platforms/detect/scanner_test.rb +61 -0
- data/test/unit/platforms/family_test.rb +32 -0
- data/test/unit/platforms/os_detect_test.rb +175 -0
- data/test/unit/{extras/os_common_test.rb → platforms/platform_test.rb} +103 -18
- data/test/unit/platforms/platforms_test.rb +42 -0
- data/test/unit/plugins/connection_test.rb +106 -8
- data/test/unit/transports/local_test.rb +20 -15
- data/test/unit/transports/mock_test.rb +16 -6
- data/test/unit/transports/ssh_test.rb +17 -15
- metadata +28 -19
- data/lib/train/extras/linux_lsb.rb +0 -60
- data/lib/train/extras/os_common.rb +0 -151
- data/lib/train/extras/os_detect_arista_eos.rb +0 -34
- data/lib/train/extras/os_detect_darwin.rb +0 -40
- data/lib/train/extras/os_detect_esx.rb +0 -22
- data/lib/train/extras/os_detect_linux.rb +0 -164
- data/lib/train/extras/os_detect_openvms.rb +0 -29
- data/lib/train/extras/os_detect_unix.rb +0 -106
- data/lib/train/extras/uname.rb +0 -28
- data/lib/train/transports/local_os.rb +0 -51
- data/test/unit/extras/os_detect_linux_test.rb +0 -230
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Train::Platforms
|
4
|
+
class Family
|
5
|
+
include Train::Platforms::Common
|
6
|
+
attr_accessor :children, :condition, :families, :name
|
7
|
+
|
8
|
+
def initialize(name, condition)
|
9
|
+
@name = name
|
10
|
+
@condition = condition
|
11
|
+
@families = {}
|
12
|
+
@children = {}
|
13
|
+
@detect = nil
|
14
|
+
@title = "#{name.to_s.capitalize} Family"
|
15
|
+
|
16
|
+
# add itself to the families list
|
17
|
+
Train::Platforms.families[@name.to_s] = self
|
18
|
+
end
|
19
|
+
|
20
|
+
def title(title = nil)
|
21
|
+
return @title if title.nil?
|
22
|
+
@title = title
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Train::Platforms
|
4
|
+
class Platform
|
5
|
+
include Train::Platforms::Common
|
6
|
+
attr_accessor :backend, :condition, :families, :family_hierarchy, :platform
|
7
|
+
|
8
|
+
def initialize(name, condition = {})
|
9
|
+
@name = name
|
10
|
+
@condition = condition
|
11
|
+
@families = {}
|
12
|
+
@family_hierarchy = []
|
13
|
+
@platform = {}
|
14
|
+
@detect = nil
|
15
|
+
@title = name.to_s.capitalize
|
16
|
+
|
17
|
+
# add itself to the platform list
|
18
|
+
Train::Platforms.list[name] = self
|
19
|
+
end
|
20
|
+
|
21
|
+
def direct_families
|
22
|
+
@families.collect { |k, _v| k.name }
|
23
|
+
end
|
24
|
+
|
25
|
+
def name
|
26
|
+
# Override here incase a updated name was set
|
27
|
+
# during the detect logic
|
28
|
+
@platform[:name] || @name
|
29
|
+
end
|
30
|
+
|
31
|
+
# This is for backwords compatability with
|
32
|
+
# the current inspec os resource.
|
33
|
+
def[](name)
|
34
|
+
if respond_to?(name)
|
35
|
+
send(name)
|
36
|
+
else
|
37
|
+
'unknown'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def title(title = nil)
|
42
|
+
return @title if title.nil?
|
43
|
+
@title = title
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_hash
|
48
|
+
@platform
|
49
|
+
end
|
50
|
+
|
51
|
+
# Add generic family? and platform methods to an existing platform
|
52
|
+
#
|
53
|
+
# This is done later to add any custom
|
54
|
+
# families/properties that were created
|
55
|
+
def add_platform_methods
|
56
|
+
family_list = Train::Platforms.families
|
57
|
+
family_list.each_value do |k|
|
58
|
+
next if respond_to?(k.name + '?')
|
59
|
+
define_singleton_method(k.name + '?') do
|
60
|
+
family_hierarchy.include?(k.name)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Helper methods for direct platform info
|
65
|
+
@platform.each_key do |m|
|
66
|
+
next if respond_to?(m)
|
67
|
+
define_singleton_method(m) do
|
68
|
+
@platform[m]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Create method for name if its not already true
|
73
|
+
plat_name = name.downcase.tr(' ', '_') + '?'
|
74
|
+
return if respond_to?(plat_name)
|
75
|
+
define_singleton_method(plat_name) do
|
76
|
+
true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,8 +1,4 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
#
|
3
|
-
# Author:: Salim Afiune (<salim@afiunemaya.com.mx>)
|
4
|
-
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
5
|
-
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
6
2
|
|
7
3
|
require 'train/errors'
|
8
4
|
require 'train/extras'
|
@@ -19,9 +15,6 @@ class Train::Plugins::Transport
|
|
19
15
|
class BaseConnection
|
20
16
|
include Train::Extras
|
21
17
|
|
22
|
-
# Provide access to the files cache.
|
23
|
-
attr_reader :files
|
24
|
-
|
25
18
|
# Create a new Connection instance.
|
26
19
|
#
|
27
20
|
# @param options [Hash] connection options
|
@@ -29,7 +22,35 @@ class Train::Plugins::Transport
|
|
29
22
|
def initialize(options = nil)
|
30
23
|
@options = options || {}
|
31
24
|
@logger = @options.delete(:logger) || Logger.new(STDOUT)
|
32
|
-
|
25
|
+
Train::Platforms::Detect::Specifications::OS.load
|
26
|
+
|
27
|
+
# default caching options
|
28
|
+
@cache_enabled = {
|
29
|
+
file: true,
|
30
|
+
command: false,
|
31
|
+
}
|
32
|
+
|
33
|
+
@cache = {}
|
34
|
+
@cache_enabled.each_key do |type|
|
35
|
+
clear_cache(type)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def cache_enabled?(type)
|
40
|
+
@cache_enabled[type.to_sym]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Enable caching types for Train. Currently we support
|
44
|
+
# :file and :command types
|
45
|
+
def enable_cache(type)
|
46
|
+
fail Train::UnknownCacheType, "#{type} is not a valid cache type" unless @cache_enabled.keys.include?(type.to_sym)
|
47
|
+
@cache_enabled[type.to_sym] = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def disable_cache(type)
|
51
|
+
fail Train::UnknownCacheType, "#{type} is not a valid cache type" unless @cache_enabled.keys.include?(type.to_sym)
|
52
|
+
@cache_enabled[type.to_sym] = false
|
53
|
+
clear_cache(type.to_sym)
|
33
54
|
end
|
34
55
|
|
35
56
|
# Closes the session connection, if it is still active.
|
@@ -39,39 +60,45 @@ class Train::Plugins::Transport
|
|
39
60
|
|
40
61
|
def to_json
|
41
62
|
{
|
42
|
-
'files' => Hash[@
|
63
|
+
'files' => Hash[@cache[:file].map { |x, y| [x, y.to_json] }],
|
43
64
|
}
|
44
65
|
end
|
45
66
|
|
46
67
|
def load_json(j)
|
47
68
|
require 'train/transports/mock'
|
48
69
|
j['files'].each do |path, jf|
|
49
|
-
@
|
70
|
+
@cache[:file][path] = Train::Transports::Mock::Connection::File.from_json(jf)
|
50
71
|
end
|
51
72
|
end
|
52
73
|
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
# @return [CommandResult] contains the result of running the command
|
57
|
-
def run_command(_command)
|
58
|
-
fail Train::ClientError, "#{self.class} does not implement #run_command()"
|
74
|
+
# Is this a local transport?
|
75
|
+
def local?
|
76
|
+
false
|
59
77
|
end
|
60
78
|
|
61
79
|
# Get information on the operating system which this transport connects to.
|
62
80
|
#
|
63
|
-
# @return [
|
64
|
-
def
|
65
|
-
|
81
|
+
# @return [Platform] system information
|
82
|
+
def platform
|
83
|
+
@platform ||= Train::Platforms::Detect.scan(self)
|
66
84
|
end
|
85
|
+
# we need to keep os as a method for backwards compatibility with inspec
|
86
|
+
alias os platform
|
67
87
|
|
68
|
-
#
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
88
|
+
# This is the main command call for all connections. This will call the private
|
89
|
+
# run_command_via_connection on the connection with optional caching
|
90
|
+
def run_command(cmd)
|
91
|
+
return run_command_via_connection(cmd) unless cache_enabled?(:command)
|
92
|
+
|
93
|
+
@cache[:command][cmd] ||= run_command_via_connection(cmd)
|
94
|
+
end
|
95
|
+
|
96
|
+
# This is the main file call for all connections. This will call the private
|
97
|
+
# file_via_connection on the connection with optional caching
|
98
|
+
def file(path, *args)
|
99
|
+
return file_via_connection(path, *args) unless cache_enabled?(:file)
|
100
|
+
|
101
|
+
@cache[:file][path] ||= file_via_connection(path, *args)
|
75
102
|
end
|
76
103
|
|
77
104
|
# Builds a LoginCommand which can be used to open an interactive
|
@@ -79,7 +106,7 @@ class Train::Plugins::Transport
|
|
79
106
|
#
|
80
107
|
# @return [LoginCommand] array of command line tokens
|
81
108
|
def login_command
|
82
|
-
fail
|
109
|
+
fail NotImplementedError, "#{self.class} does not implement #login_command()"
|
83
110
|
end
|
84
111
|
|
85
112
|
# Block and return only when the remote host is prepared and ready to
|
@@ -93,6 +120,27 @@ class Train::Plugins::Transport
|
|
93
120
|
|
94
121
|
private
|
95
122
|
|
123
|
+
# Execute a command using this connection.
|
124
|
+
#
|
125
|
+
# @param command [String] command string to execute
|
126
|
+
# @return [CommandResult] contains the result of running the command
|
127
|
+
def run_command_via_connection(_command)
|
128
|
+
fail NotImplementedError, "#{self.class} does not implement #run_command_via_connection()"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Interact with files on the target. Read, write, and get metadata
|
132
|
+
# from files via the transport.
|
133
|
+
#
|
134
|
+
# @param [String] path which is being inspected
|
135
|
+
# @return [FileCommon] file object that allows for interaction
|
136
|
+
def file_via_connection(_path, *_args)
|
137
|
+
fail NotImplementedError, "#{self.class} does not implement #file_via_connection(...)"
|
138
|
+
end
|
139
|
+
|
140
|
+
def clear_cache(type)
|
141
|
+
@cache[type.to_sym] = {}
|
142
|
+
end
|
143
|
+
|
96
144
|
# @return [Logger] logger for reporting information
|
97
145
|
# @api private
|
98
146
|
attr_reader :logger
|
@@ -69,22 +69,27 @@ class Train::Transports::Docker
|
|
69
69
|
# nothing to do at the moment
|
70
70
|
end
|
71
71
|
|
72
|
-
def
|
73
|
-
@
|
72
|
+
def uri
|
73
|
+
if @container.nil?
|
74
|
+
"docker://#{@id}"
|
75
|
+
else
|
76
|
+
"docker://#{@container.id}"
|
77
|
+
end
|
74
78
|
end
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
80
|
+
private
|
81
|
+
|
82
|
+
def file_via_connection(path)
|
83
|
+
if os.aix?
|
84
|
+
Train::File::Remote::Aix.new(self, path)
|
85
|
+
elsif os.solaris?
|
86
|
+
Train::File::Remote::Unix.new(self, path)
|
87
|
+
else
|
88
|
+
Train::File::Remote::Linux.new(self, path)
|
89
|
+
end
|
85
90
|
end
|
86
91
|
|
87
|
-
def
|
92
|
+
def run_command_via_connection(cmd)
|
88
93
|
cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil?
|
89
94
|
stdout, stderr, exit_status = @container.exec(
|
90
95
|
[
|
@@ -97,21 +102,5 @@ class Train::Transports::Docker
|
|
97
102
|
# @TODO: differentiate any other error
|
98
103
|
raise
|
99
104
|
end
|
100
|
-
|
101
|
-
def uri
|
102
|
-
if @container.nil?
|
103
|
-
"docker://#{@id}"
|
104
|
-
else
|
105
|
-
"docker://#{@container.id}"
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
class OS < OSCommon
|
110
|
-
def initialize(backend)
|
111
|
-
# hardcoded to unix/linux for now, until other operating systems
|
112
|
-
# are supported
|
113
|
-
super(backend, { family: 'unix' })
|
114
|
-
end
|
115
|
-
end
|
116
105
|
end
|
117
106
|
end
|
@@ -17,42 +17,41 @@ module Train::Transports
|
|
17
17
|
end
|
18
18
|
|
19
19
|
class Connection < BaseConnection
|
20
|
-
require 'train/transports/local_os'
|
21
|
-
|
22
20
|
def initialize(options)
|
23
21
|
super(options)
|
24
22
|
@cmd_wrapper = nil
|
25
23
|
@cmd_wrapper = CommandWrapper.load(self, options)
|
26
24
|
end
|
27
25
|
|
28
|
-
def
|
29
|
-
|
30
|
-
res = Mixlib::ShellOut.new(cmd)
|
31
|
-
res.run_command
|
32
|
-
CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
33
|
-
rescue Errno::ENOENT => _
|
34
|
-
CommandResult.new('', '', 1)
|
26
|
+
def login_command
|
27
|
+
nil # none, open your shell
|
35
28
|
end
|
36
29
|
|
37
|
-
def
|
38
|
-
|
30
|
+
def uri
|
31
|
+
'local://'
|
39
32
|
end
|
40
33
|
|
41
|
-
def
|
42
|
-
|
43
|
-
if os.windows?
|
44
|
-
Train::File::Local::Windows.new(self, path)
|
45
|
-
else
|
46
|
-
Train::File::Local::Unix.new(self, path)
|
47
|
-
end
|
34
|
+
def local?
|
35
|
+
true
|
48
36
|
end
|
49
37
|
|
50
|
-
|
51
|
-
|
38
|
+
private
|
39
|
+
|
40
|
+
def run_command_via_connection(cmd)
|
41
|
+
cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil?
|
42
|
+
res = Mixlib::ShellOut.new(cmd)
|
43
|
+
res.run_command
|
44
|
+
CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
45
|
+
rescue Errno::ENOENT => _
|
46
|
+
CommandResult.new('', '', 1)
|
52
47
|
end
|
53
48
|
|
54
|
-
def
|
55
|
-
|
49
|
+
def file_via_connection(path)
|
50
|
+
if os.windows?
|
51
|
+
Train::File::Local::Windows.new(self, path)
|
52
|
+
else
|
53
|
+
Train::File::Local::Unix.new(self, path)
|
54
|
+
end
|
56
55
|
end
|
57
56
|
end
|
58
57
|
end
|
@@ -1,7 +1,4 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
#
|
3
|
-
# author: Dominik Richter
|
4
|
-
# author: Christoph Hartmann
|
5
2
|
|
6
3
|
require 'train/plugins'
|
7
4
|
require 'digest'
|
@@ -34,7 +31,7 @@ module Train::Transports
|
|
34
31
|
'Train::Transports::Mock::Connection::File' =>
|
35
32
|
Connection::FileCommon.instance_methods(false),
|
36
33
|
'Train::Transports::Mock::Connection::OS' =>
|
37
|
-
|
34
|
+
Train::Platform.instance_methods(false),
|
38
35
|
}
|
39
36
|
|
40
37
|
# rubocop:disable Metrics/ParameterLists
|
@@ -60,13 +57,13 @@ end
|
|
60
57
|
|
61
58
|
class Train::Transports::Mock
|
62
59
|
class Connection < BaseConnection
|
63
|
-
attr_accessor :files, :commands
|
64
60
|
attr_reader :os
|
65
61
|
|
66
62
|
def initialize(conf = nil)
|
67
63
|
super(conf)
|
68
64
|
mock_os
|
69
|
-
|
65
|
+
enable_cache(:file)
|
66
|
+
enable_cache(:command)
|
70
67
|
end
|
71
68
|
|
72
69
|
def uri
|
@@ -76,13 +73,41 @@ class Train::Transports::Mock
|
|
76
73
|
def mock_os(value = {})
|
77
74
|
# if a user passes a nil value, set to an empty hash so the merge still succeeds
|
78
75
|
value ||= {}
|
76
|
+
value.each { |k, v| value[k] = 'unknown' if v.nil? }
|
77
|
+
value = { name: 'mock', family: 'mock', release: 'unknown', arch: 'unknown' }.merge(value)
|
79
78
|
|
80
|
-
|
81
|
-
|
79
|
+
platform = Train::Platforms.name(value[:name])
|
80
|
+
platform.family_hierarchy = mock_os_hierarchy(platform).flatten
|
81
|
+
platform.platform = value
|
82
|
+
platform.add_platform_methods
|
83
|
+
@os = platform
|
84
|
+
end
|
85
|
+
|
86
|
+
def mock_os_hierarchy(plat)
|
87
|
+
plat.families.each_with_object([]) do |(k, _v), memo|
|
88
|
+
memo << k.name
|
89
|
+
memo << mock_os_hierarchy(k) unless k.families.empty?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def commands=(commands)
|
94
|
+
@cache[:command] = commands
|
95
|
+
end
|
96
|
+
|
97
|
+
def commands
|
98
|
+
@cache[:command]
|
99
|
+
end
|
100
|
+
|
101
|
+
def files=(files)
|
102
|
+
@cache[:file] = files
|
103
|
+
end
|
104
|
+
|
105
|
+
def files
|
106
|
+
@cache[:file]
|
82
107
|
end
|
83
108
|
|
84
109
|
def mock_command(cmd, stdout = nil, stderr = nil, exit_status = 0)
|
85
|
-
@
|
110
|
+
@cache[:command][cmd] = Command.new(stdout || '', stderr || '', exit_status)
|
86
111
|
end
|
87
112
|
|
88
113
|
def command_not_found(cmd)
|
@@ -95,43 +120,32 @@ class Train::Transports::Mock
|
|
95
120
|
mock_command(cmd, nil, nil, 1)
|
96
121
|
end
|
97
122
|
|
98
|
-
def run_command(cmd)
|
99
|
-
@commands[cmd] ||
|
100
|
-
@commands[Digest::SHA256.hexdigest cmd.to_s] ||
|
101
|
-
command_not_found(cmd)
|
102
|
-
end
|
103
|
-
|
104
123
|
def file_not_found(path)
|
105
124
|
STDERR.puts('File not mocked: '+path.to_s) if @options[:verbose]
|
106
125
|
File.new(self, path)
|
107
126
|
end
|
108
127
|
|
109
|
-
def file(path)
|
110
|
-
@files[path] ||= file_not_found(path)
|
111
|
-
end
|
112
|
-
|
113
128
|
def to_s
|
114
129
|
'Mock Connection'
|
115
130
|
end
|
116
|
-
end
|
117
|
-
end
|
118
131
|
|
119
|
-
|
120
|
-
Command = Struct.new(:stdout, :stderr, :exit_status)
|
121
|
-
end
|
132
|
+
private
|
122
133
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
super(backend, desc)
|
134
|
+
def run_command_via_connection(cmd)
|
135
|
+
@cache[:command][Digest::SHA256.hexdigest cmd.to_s] ||
|
136
|
+
command_not_found(cmd)
|
127
137
|
end
|
128
138
|
|
129
|
-
def
|
130
|
-
|
139
|
+
def file_via_connection(path)
|
140
|
+
file_not_found(path)
|
131
141
|
end
|
132
142
|
end
|
133
143
|
end
|
134
144
|
|
145
|
+
class Train::Transports::Mock::Connection
|
146
|
+
Command = Struct.new(:stdout, :stderr, :exit_status)
|
147
|
+
end
|
148
|
+
|
135
149
|
class Train::Transports::Mock::Connection
|
136
150
|
class File < Train::File
|
137
151
|
def self.from_json(json)
|