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