train 0.29.2 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -2
  3. data/lib/train.rb +1 -0
  4. data/lib/train/errors.rb +6 -0
  5. data/lib/train/extras.rb +0 -1
  6. data/lib/train/platforms.rb +82 -0
  7. data/lib/train/platforms/common.rb +34 -0
  8. data/lib/train/platforms/detect.rb +12 -0
  9. data/lib/train/platforms/detect/helpers/os_common.rb +56 -0
  10. data/lib/train/platforms/detect/helpers/os_linux.rb +75 -0
  11. data/lib/train/{extras/os_detect_windows.rb → platforms/detect/helpers/os_windows.rb} +3 -10
  12. data/lib/train/platforms/detect/scanner.rb +84 -0
  13. data/lib/train/platforms/detect/specifications/os.rb +480 -0
  14. data/lib/train/platforms/family.rb +26 -0
  15. data/lib/train/platforms/platform.rb +80 -0
  16. data/lib/train/plugins/base_connection.rb +75 -27
  17. data/lib/train/transports/docker.rb +17 -28
  18. data/lib/train/transports/local.rb +21 -22
  19. data/lib/train/transports/mock.rb +44 -30
  20. data/lib/train/transports/ssh_connection.rb +55 -67
  21. data/lib/train/transports/winrm_connection.rb +16 -26
  22. data/lib/train/version.rb +1 -1
  23. data/test/unit/file/remote/linux_test.rb +2 -2
  24. data/test/unit/platforms/detect/os_common_test.rb +85 -0
  25. data/test/unit/platforms/detect/os_linux_test.rb +124 -0
  26. data/test/unit/{extras/os_detect_windows_test.rb → platforms/detect/os_windows_test.rb} +5 -2
  27. data/test/unit/platforms/detect/scanner_test.rb +61 -0
  28. data/test/unit/platforms/family_test.rb +32 -0
  29. data/test/unit/platforms/os_detect_test.rb +175 -0
  30. data/test/unit/{extras/os_common_test.rb → platforms/platform_test.rb} +103 -18
  31. data/test/unit/platforms/platforms_test.rb +42 -0
  32. data/test/unit/plugins/connection_test.rb +106 -8
  33. data/test/unit/transports/local_test.rb +20 -15
  34. data/test/unit/transports/mock_test.rb +16 -6
  35. data/test/unit/transports/ssh_test.rb +17 -15
  36. metadata +28 -19
  37. data/lib/train/extras/linux_lsb.rb +0 -60
  38. data/lib/train/extras/os_common.rb +0 -151
  39. data/lib/train/extras/os_detect_arista_eos.rb +0 -34
  40. data/lib/train/extras/os_detect_darwin.rb +0 -40
  41. data/lib/train/extras/os_detect_esx.rb +0 -22
  42. data/lib/train/extras/os_detect_linux.rb +0 -164
  43. data/lib/train/extras/os_detect_openvms.rb +0 -29
  44. data/lib/train/extras/os_detect_unix.rb +0 -106
  45. data/lib/train/extras/uname.rb +0 -28
  46. data/lib/train/transports/local_os.rb +0 -51
  47. 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
- @files = {}
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[@files.map { |x, y| [x, y.to_json] }],
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
- @files[path] = Train::Transports::Mock::Connection::File.from_json(jf)
70
+ @cache[:file][path] = Train::Transports::Mock::Connection::File.from_json(jf)
50
71
  end
51
72
  end
52
73
 
53
- # Execute a command using this connection.
54
- #
55
- # @param command [String] command string to execute
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 [OSCommon] operating system information
64
- def os
65
- fail Train::ClientError, "#{self.class} does not implement #os()"
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
- # Interact with files on the target. Read, write, and get metadata
69
- # from files via the transport.
70
- #
71
- # @param [String] path which is being inspected
72
- # @return [FileCommon] file object that allows for interaction
73
- def file(_path, *_args)
74
- fail Train::ClientError, "#{self.class} does not implement #file(...)"
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 Train::ClientError, "#{self.class} does not implement #login_command()"
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 os
73
- @os ||= OS.new(self)
72
+ def uri
73
+ if @container.nil?
74
+ "docker://#{@id}"
75
+ else
76
+ "docker://#{@container.id}"
77
+ end
74
78
  end
75
79
 
76
- def file(path)
77
- @files[path] ||=\
78
- if os.aix?
79
- Train::File::Remote::Aix.new(self, path)
80
- elsif os.solaris?
81
- Train::File::Remote::Unix.new(self, path)
82
- else
83
- Train::File::Remote::Linux.new(self, path)
84
- end
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 run_command(cmd)
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 run_command(cmd)
29
- cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil?
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 os
38
- @os ||= OS.new(self)
30
+ def uri
31
+ 'local://'
39
32
  end
40
33
 
41
- def file(path)
42
- @files[path] ||= \
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
- def login_command
51
- nil # none, open your shell
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 uri
55
- 'local://'
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
- Connection::OSCommon.instance_methods(false),
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
- @commands = {}
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
- os_params = { name: 'unknown', family: 'unknown', release: 'unknown', arch: 'unknown' }.merge(value)
81
- @os = OS.new(self, os_params)
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
- @commands[cmd] = Command.new(stdout || '', stderr || '', exit_status)
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
- class Train::Transports::Mock::Connection
120
- Command = Struct.new(:stdout, :stderr, :exit_status)
121
- end
132
+ private
122
133
 
123
- class Train::Transports::Mock::Connection
124
- class OS < OSCommon
125
- def initialize(backend, desc)
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 detect_family
130
- # no op, we do not need to detect the os
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)