train-core 1.4.4

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +780 -0
  3. data/Gemfile +36 -0
  4. data/LICENSE +201 -0
  5. data/README.md +197 -0
  6. data/lib/train.rb +158 -0
  7. data/lib/train/errors.rb +32 -0
  8. data/lib/train/extras.rb +11 -0
  9. data/lib/train/extras/command_wrapper.rb +137 -0
  10. data/lib/train/extras/stat.rb +132 -0
  11. data/lib/train/file.rb +151 -0
  12. data/lib/train/file/local.rb +75 -0
  13. data/lib/train/file/local/unix.rb +96 -0
  14. data/lib/train/file/local/windows.rb +63 -0
  15. data/lib/train/file/remote.rb +36 -0
  16. data/lib/train/file/remote/aix.rb +21 -0
  17. data/lib/train/file/remote/linux.rb +19 -0
  18. data/lib/train/file/remote/qnx.rb +41 -0
  19. data/lib/train/file/remote/unix.rb +106 -0
  20. data/lib/train/file/remote/windows.rb +94 -0
  21. data/lib/train/options.rb +80 -0
  22. data/lib/train/platforms.rb +84 -0
  23. data/lib/train/platforms/common.rb +34 -0
  24. data/lib/train/platforms/detect.rb +12 -0
  25. data/lib/train/platforms/detect/helpers/os_common.rb +145 -0
  26. data/lib/train/platforms/detect/helpers/os_linux.rb +75 -0
  27. data/lib/train/platforms/detect/helpers/os_windows.rb +120 -0
  28. data/lib/train/platforms/detect/scanner.rb +84 -0
  29. data/lib/train/platforms/detect/specifications/api.rb +15 -0
  30. data/lib/train/platforms/detect/specifications/os.rb +578 -0
  31. data/lib/train/platforms/detect/uuid.rb +34 -0
  32. data/lib/train/platforms/family.rb +26 -0
  33. data/lib/train/platforms/platform.rb +101 -0
  34. data/lib/train/plugins.rb +40 -0
  35. data/lib/train/plugins/base_connection.rb +169 -0
  36. data/lib/train/plugins/transport.rb +49 -0
  37. data/lib/train/transports/local.rb +232 -0
  38. data/lib/train/version.rb +7 -0
  39. data/train-core.gemspec +27 -0
  40. metadata +116 -0
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ module Train::Platforms::Detect::Helpers
4
+ module Linux
5
+ def redhatish_platform(conf)
6
+ conf =~ /^red hat/i ? 'redhat' : /(\w+)/i.match(conf)[1].downcase
7
+ end
8
+
9
+ def redhatish_version(conf)
10
+ case conf
11
+ when /rawhide/i
12
+ /((\d+) \(Rawhide\))/i.match(conf)[1].downcase
13
+ when /derived from .*linux/i
14
+ /Linux ((\d+|\.)+)/i.match(conf)[1]
15
+ else
16
+ /release ([\d\.]+)/.match(conf)[1]
17
+ end
18
+ end
19
+
20
+ def linux_os_release
21
+ data = unix_file_contents('/etc/os-release')
22
+ return if data.nil?
23
+
24
+ os_info = parse_os_release_info(data)
25
+ cisco_info_file = os_info['CISCO_RELEASE_INFO']
26
+ if cisco_info_file
27
+ os_info.merge!(parse_os_release_info(unix_file_contents(cisco_info_file)))
28
+ end
29
+
30
+ os_info
31
+ end
32
+
33
+ def parse_os_release_info(raw)
34
+ return {} if raw.nil?
35
+
36
+ raw.lines.each_with_object({}) do |line, memo|
37
+ line.strip!
38
+ next if line.empty?
39
+ key, value = line.split('=', 2)
40
+ memo[key] = value.gsub(/\A"|"\Z/, '') unless value.empty?
41
+ end
42
+ end
43
+
44
+ def lsb_config(content)
45
+ id = /^DISTRIB_ID=["']?(.+?)["']?$/.match(content)
46
+ release = /^DISTRIB_RELEASE=["']?(.+?)["']?$/.match(content)
47
+ codename = /^DISTRIB_CODENAME=["']?(.+?)["']?$/.match(content)
48
+ {
49
+ id: id.nil? ? nil : id[1],
50
+ release: release.nil? ? nil : release[1],
51
+ codename: codename.nil? ? nil : codename[1],
52
+ }
53
+ end
54
+
55
+ def lsb_release(content)
56
+ id = /^Distributor ID:\s+(.+)$/.match(content)
57
+ release = /^Release:\s+(.+)$/.match(content)
58
+ codename = /^Codename:\s+(.+)$/.match(content)
59
+ {
60
+ id: id.nil? ? nil : id[1],
61
+ release: release.nil? ? nil : release[1],
62
+ codename: codename.nil? ? nil : codename[1],
63
+ }
64
+ end
65
+
66
+ def read_linux_lsb
67
+ return @lsb unless @lsb.empty?
68
+ if !(raw = unix_file_contents('/etc/lsb-release')).nil?
69
+ @lsb = lsb_config(raw)
70
+ elsif !(raw = unix_file_contents('/usr/bin/lsb-release')).nil?
71
+ @lsb = lsb_release(raw)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+
3
+ module Train::Platforms::Detect::Helpers
4
+ module Windows
5
+ def detect_windows
6
+ res = @backend.run_command('cmd /c ver')
7
+ return false if res.exit_status != 0 or res.stdout.empty?
8
+
9
+ # if the ver contains `Windows`, we know its a Windows system
10
+ version = res.stdout.strip
11
+ return false unless version.downcase =~ /windows/
12
+ @platform[:family] = 'windows'
13
+
14
+ # try to extract release from eg. `Microsoft Windows [Version 6.3.9600]`
15
+ release = /\[(?<name>.*)\]/.match(version)
16
+ unless release[:name].nil?
17
+ # release is 6.3.9600 now
18
+ @platform[:release] = release[:name].downcase.gsub('version', '').strip
19
+ # fallback, if we are not able to extract the name from wmic later
20
+ @platform[:name] = "Windows #{@platform[:release]}"
21
+ end
22
+
23
+ # try to use wmic, but lets keep it optional
24
+ read_wmic
25
+
26
+ true
27
+ end
28
+
29
+ # reads os name and version from wmic
30
+ # @see https://msdn.microsoft.com/en-us/library/bb742610.aspx#EEAA
31
+ # Thanks to Matt Wrock (https://github.com/mwrock) for this hint
32
+ def read_wmic
33
+ res = @backend.run_command('wmic os get * /format:list')
34
+ if res.exit_status == 0
35
+ sys_info = {}
36
+ res.stdout.lines.each { |line|
37
+ m = /^\s*([^=]*?)\s*=\s*(.*?)\s*$/.match(line)
38
+ sys_info[m[1].to_sym] = m[2] unless m.nil? || m[1].nil?
39
+ }
40
+
41
+ @platform[:release] = sys_info[:Version]
42
+ # additional info on windows
43
+ @platform[:build] = sys_info[:BuildNumber]
44
+ @platform[:name] = sys_info[:Caption]
45
+ @platform[:name] = @platform[:name].gsub('Microsoft', '').strip unless @platform[:name].empty?
46
+ @platform[:arch] = read_wmic_cpu
47
+ end
48
+ end
49
+
50
+ # `OSArchitecture` from `read_wmic` does not match a normal standard
51
+ # For example, `x86_64` shows as `64-bit`
52
+ def read_wmic_cpu
53
+ res = @backend.run_command('wmic cpu get architecture /format:list')
54
+ if res.exit_status == 0
55
+ sys_info = {}
56
+ res.stdout.lines.each { |line|
57
+ m = /^\s*([^=]*?)\s*=\s*(.*?)\s*$/.match(line)
58
+ sys_info[m[1].to_sym] = m[2] unless m.nil? || m[1].nil?
59
+ }
60
+ end
61
+
62
+ # This converts `wmic os get architecture` output to a normal standard
63
+ # https://msdn.microsoft.com/en-us/library/aa394373(VS.85).aspx
64
+ arch_map = {
65
+ 0 => 'i386',
66
+ 1 => 'mips',
67
+ 2 => 'alpha',
68
+ 3 => 'powerpc',
69
+ 5 => 'arm',
70
+ 6 => 'ia64',
71
+ 9 => 'x86_64',
72
+ }
73
+
74
+ # The value of `wmic cpu get architecture` is always a number between 0-9
75
+ arch_number = sys_info[:Architecture].to_i
76
+ arch_map[arch_number]
77
+ end
78
+
79
+ # This method scans the target os for a unique uuid to use
80
+ def windows_uuid
81
+ uuid = windows_uuid_from_chef
82
+ uuid = windows_uuid_from_machine_file if uuid.nil?
83
+ uuid = windows_uuid_from_wmic if uuid.nil?
84
+ uuid = windows_uuid_from_registry if uuid.nil?
85
+ raise Train::TransportError, 'Cannot find a UUID for your node.' if uuid.nil?
86
+ uuid
87
+ end
88
+
89
+ def windows_uuid_from_machine_file
90
+ %W(
91
+ #{ENV['SYSTEMDRIVE']}\\chef\\chef_guid
92
+ #{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}\\.chef\\chef_guid
93
+ ).each do |path|
94
+ file = @backend.file(path)
95
+ return file.content.chomp if file.exist? && !file.size.zero?
96
+ end
97
+ nil
98
+ end
99
+
100
+ def windows_uuid_from_chef
101
+ file = @backend.file("#{ENV['SYSTEMDRIVE']}\\chef\\cache\\data_collector_metadata.json")
102
+ return if !file.exist? || file.size.zero?
103
+ json = JSON.parse(file.content)
104
+ json['node_uuid'] if json['node_uuid']
105
+ end
106
+
107
+ def windows_uuid_from_wmic
108
+ result = @backend.run_command('wmic csproduct get UUID')
109
+ return unless result.exit_status.zero?
110
+ result.stdout.split("\r\n")[-1].strip
111
+ end
112
+
113
+ def windows_uuid_from_registry
114
+ cmd = '(Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" -Name "MachineGuid")."MachineGuid"'
115
+ result = @backend.run_command(cmd)
116
+ return unless result.exit_status.zero?
117
+ result.stdout.chomp
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+
3
+ require 'train/platforms/detect/helpers/os_common'
4
+
5
+ module Train::Platforms::Detect
6
+ class Scanner
7
+ include Train::Platforms::Detect::Helpers::OSCommon
8
+
9
+ def initialize(backend)
10
+ @backend = backend
11
+ @platform = {}
12
+ @family_hierarchy = []
13
+
14
+ # detect cache variables
15
+ @files = {}
16
+ @uname = {}
17
+ @lsb = {}
18
+ @cache = {}
19
+ end
20
+
21
+ # Main detect method to scan all platforms for a match
22
+ #
23
+ # @return Train::Platform instance or error if none found
24
+ def scan
25
+ # start with the platform/families who have no families (the top levels)
26
+ top = Train::Platforms.top_platforms
27
+ top.each do |_name, plat|
28
+ # we are doing a instance_eval here to make sure we have the proper
29
+ # context with all the detect helper methods
30
+ next unless instance_eval(&plat.detect) == true
31
+
32
+ # if we have a match start looking at the children
33
+ plat_result = scan_children(plat)
34
+ next if plat_result.nil?
35
+
36
+ # return platform to backend
37
+ @family_hierarchy << plat.name
38
+ return get_platform(plat_result)
39
+ end
40
+
41
+ fail Train::PlatformDetectionFailed, 'Sorry, we are unable to detect your platform'
42
+ end
43
+
44
+ def scan_children(parent)
45
+ parent.children.each do |plat, condition|
46
+ next unless instance_eval(&plat.detect) == true
47
+
48
+ if plat.class == Train::Platforms::Platform
49
+ return plat if condition.empty? || check_condition(condition)
50
+ elsif plat.class == Train::Platforms::Family
51
+ plat = scan_family_children(plat)
52
+ return plat unless plat.nil?
53
+ end
54
+ end
55
+
56
+ nil
57
+ end
58
+
59
+ def scan_family_children(plat)
60
+ child_result = scan_children(plat) unless plat.children.nil?
61
+ return if child_result.nil?
62
+ @family_hierarchy << plat.name
63
+ child_result
64
+ end
65
+
66
+ def check_condition(condition)
67
+ condition.each do |k, v|
68
+ op, expected = v.strip.split(' ')
69
+ op = '==' if op == '='
70
+ return false if @platform[k].nil? || !instance_eval("'#{@platform[k]}' #{op} '#{expected}'")
71
+ end
72
+
73
+ true
74
+ end
75
+
76
+ def get_platform(plat)
77
+ plat.backend = @backend
78
+ plat.platform = @platform
79
+ plat.add_platform_methods
80
+ plat.family_hierarchy = @family_hierarchy
81
+ plat
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module Train::Platforms::Detect::Specifications
4
+ class Api
5
+ def self.load
6
+ plat = Train::Platforms
7
+
8
+ plat.family('api')
9
+
10
+ plat.family('cloud').in_family('api')
11
+ plat.name('aws').in_family('cloud')
12
+ plat.name('azure').in_family('cloud')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,578 @@
1
+ # encoding: utf-8
2
+
3
+ # rubocop:disable Style/Next
4
+ # rubocop:disable Metrics/AbcSize
5
+ # rubocop:disable Metrics/CyclomaticComplexity
6
+ # rubocop:disable Metrics/ClassLength
7
+ # rubocop:disable Metrics/MethodLength
8
+ # rubocop:disable Metrics/PerceivedComplexity
9
+
10
+ module Train::Platforms::Detect::Specifications
11
+ class OS
12
+ def self.load
13
+ plat = Train::Platforms
14
+
15
+ # master family
16
+ plat.family('os').detect { true }
17
+
18
+ plat.family('windows').in_family('os')
19
+ .detect {
20
+ if winrm? || (@backend.local? && ruby_host_os(/mswin|mingw32|windows/))
21
+ true
22
+ end
23
+ }
24
+ # windows platform
25
+ plat.name('windows').in_family('windows')
26
+ .detect {
27
+ true if detect_windows == true
28
+ }
29
+
30
+ # unix master family
31
+ plat.family('unix').in_family('os')
32
+ .detect {
33
+ # we want to catch a special case here where cisco commands
34
+ # don't return an exit status and still print to stdout
35
+ if unix_uname_s =~ /./ && !unix_uname_s.start_with?('Line has invalid autocommand ') && !unix_uname_s.start_with?('The command you have entered')
36
+ @platform[:arch] = unix_uname_m
37
+ true
38
+ end
39
+ }
40
+
41
+ # linux master family
42
+ plat.family('linux').in_family('unix')
43
+ .detect {
44
+ true if unix_uname_s =~ /linux/i
45
+ }
46
+
47
+ # debian family
48
+ plat.family('debian').in_family('linux')
49
+ .detect {
50
+ true unless unix_file_contents('/etc/debian_version').nil?
51
+ }
52
+ plat.name('ubuntu').title('Ubuntu Linux').in_family('debian')
53
+ .detect {
54
+ lsb = read_linux_lsb
55
+ if lsb && lsb[:id] =~ /ubuntu/i
56
+ @platform[:release] = lsb[:release]
57
+ true
58
+ end
59
+ }
60
+ plat.name('linuxmint').title('LinuxMint').in_family('debian')
61
+ .detect {
62
+ lsb = read_linux_lsb
63
+ if lsb && lsb[:id] =~ /linuxmint/i
64
+ @platform[:release] = lsb[:release]
65
+ true
66
+ end
67
+ }
68
+ plat.name('raspbian').title('Raspbian Linux').in_family('debian')
69
+ .detect {
70
+ if (linux_os_release && linux_os_release['NAME'] =~ /raspbian/i) || \
71
+ unix_file_exist?('/usr/bin/raspi-config')
72
+ @platform[:release] = unix_file_contents('/etc/debian_version').chomp
73
+ true
74
+ end
75
+ }
76
+ plat.name('debian').title('Debian Linux').in_family('debian')
77
+ .detect {
78
+ lsb = read_linux_lsb
79
+ if lsb && lsb[:id] =~ /debian/i
80
+ @platform[:release] = lsb[:release]
81
+ true
82
+ end
83
+
84
+ # if we get this far we have to be some type of debian
85
+ @platform[:release] = unix_file_contents('/etc/debian_version').chomp
86
+ true
87
+ }
88
+
89
+ # fedora family
90
+ plat.family('fedora').in_family('linux')
91
+ .detect {
92
+ true if linux_os_release && linux_os_release['NAME'] =~ /fedora/i
93
+ }
94
+ plat.name('fedora').title('Fedora').in_family('fedora')
95
+ .detect {
96
+ @platform[:release] = linux_os_release['VERSION_ID']
97
+ true
98
+ }
99
+
100
+ # arista_eos family
101
+ # this checks for the arista bash shell
102
+ # must come before redhat as it uses fedora under the hood
103
+ plat.family('arista_eos').title('Arista EOS Family').in_family('linux')
104
+ .detect {
105
+ true
106
+ }
107
+ plat.name('arista_eos_bash').title('Arista EOS Bash Shell').in_family('arista_eos')
108
+ .detect {
109
+ if unix_file_exist?('/usr/bin/FastCli')
110
+ cmd = @backend.run_command('FastCli -p 15 -c "show version | json"')
111
+ if cmd.exit_status == 0 && !cmd.stdout.empty?
112
+ require 'json'
113
+ begin
114
+ eos_ver = JSON.parse(cmd.stdout)
115
+ @platform[:release] = eos_ver['version']
116
+ @platform[:arch] = eos_ver['architecture']
117
+ true
118
+ rescue JSON::ParserError
119
+ nil
120
+ end
121
+ end
122
+ end
123
+ }
124
+
125
+ # redhat family
126
+ plat.family('redhat').in_family('linux')
127
+ .detect {
128
+ # I am not sure this returns true for all redhats in this family
129
+ # for now we are going to just try each platform
130
+ # return true unless unix_file_contents('/etc/redhat-release').nil?
131
+
132
+ true
133
+ }
134
+ plat.name('centos').title('Centos Linux').in_family('redhat')
135
+ .detect {
136
+ lsb = read_linux_lsb
137
+ if lsb && lsb[:id] =~ /centos/i
138
+ @platform[:release] = lsb[:release]
139
+ true
140
+ elsif linux_os_release && linux_os_release['NAME'] =~ /centos/i
141
+ @platform[:release] = redhatish_version(unix_file_contents('/etc/redhat-release'))
142
+ true
143
+ end
144
+ }
145
+ plat.name('oracle').title('Oracle Linux').in_family('redhat')
146
+ .detect {
147
+ if !(raw = unix_file_contents('/etc/oracle-release')).nil?
148
+ @platform[:release] = redhatish_version(raw)
149
+ true
150
+ elsif !(raw = unix_file_contents('/etc/enterprise-release')).nil?
151
+ @platform[:release] = redhatish_version(raw)
152
+ true
153
+ end
154
+ }
155
+ plat.name('scientific').title('Scientific Linux').in_family('redhat')
156
+ .detect {
157
+ lsb = read_linux_lsb
158
+ if lsb && lsb[:id] =~ /scientific/i
159
+ @platform[:release] = lsb[:release]
160
+ true
161
+ end
162
+ }
163
+ plat.name('xenserver').title('Xenserer Linux').in_family('redhat')
164
+ .detect {
165
+ lsb = read_linux_lsb
166
+ if lsb && lsb[:id] =~ /xenserver/i
167
+ @platform[:release] = lsb[:release]
168
+ true
169
+ end
170
+ }
171
+ plat.name('parallels-release').title('Parallels Linux').in_family('redhat')
172
+ .detect {
173
+ if !(raw = unix_file_contents('/etc/parallels-release')).nil?
174
+ @platform[:name] = redhatish_platform(raw)
175
+ @platform[:release] = raw[/(\d\.\d\.\d)/, 1]
176
+ true
177
+ end
178
+ }
179
+ plat.name('wrlinux').title('Wind River Linux').in_family('redhat')
180
+ .detect {
181
+ if linux_os_release && linux_os_release['ID_LIKE'] =~ /wrlinux/i
182
+ @platform[:release] = linux_os_release['VERSION']
183
+ true
184
+ end
185
+ }
186
+ plat.name('amazon').title('Amazon Linux').in_family('redhat')
187
+ .detect {
188
+ lsb = read_linux_lsb
189
+ if lsb && lsb[:id] =~ /amazon/i
190
+ @platform[:release] = lsb[:release]
191
+ true
192
+ elsif (raw = unix_file_contents('/etc/system-release')) =~ /amazon/i
193
+ @platform[:name] = redhatish_platform(raw)
194
+ @platform[:release] = redhatish_version(raw)
195
+ true
196
+ end
197
+ }
198
+ plat.name('cloudlinux').title('CloudLinux').in_family('redhat')
199
+ .detect {
200
+ lsb = read_linux_lsb
201
+ if lsb && lsb[:id] =~ /cloudlinux/i
202
+ @platform[:release] = lsb[:release]
203
+ true
204
+ elsif (raw = unix_file_contents('/etc/redhat-release')) =~ /cloudlinux/i
205
+ @platform[:name] = redhatish_platform(raw)
206
+ @platform[:release] = redhatish_version(raw)
207
+ true
208
+ end
209
+ }
210
+ # keep redhat at the end as a fallback for anything with a redhat-release
211
+ plat.name('redhat').title('Red Hat Linux').in_family('redhat')
212
+ .detect {
213
+ lsb = read_linux_lsb
214
+ if lsb && lsb[:id] =~ /redhat/i
215
+ @platform[:release] = lsb[:release]
216
+ true
217
+ elsif !(raw = unix_file_contents('/etc/redhat-release')).nil?
218
+ # must be some type of redhat
219
+ @platform[:name] = redhatish_platform(raw)
220
+ @platform[:release] = redhatish_version(raw)
221
+ true
222
+ end
223
+ }
224
+
225
+ # suse family
226
+ plat.family('suse').in_family('linux')
227
+ .detect {
228
+ if !(suse = unix_file_contents('/etc/SuSE-release')).nil?
229
+ version = suse.scan(/VERSION = (\d+)\nPATCHLEVEL = (\d+)/).flatten.join('.')
230
+ version = suse[/VERSION = ([\d\.]{2,})/, 1] if version == ''
231
+ @platform[:release] = version
232
+ true
233
+ end
234
+ }
235
+ plat.name('opensuse').title('OpenSUSE Linux').in_family('suse')
236
+ .detect {
237
+ true if unix_file_contents('/etc/SuSE-release') =~ /^opensuse/i
238
+ }
239
+ plat.name('suse').title('Suse Linux').in_family('suse')
240
+ .detect {
241
+ true if unix_file_contents('/etc/SuSE-release') =~ /suse/i
242
+ }
243
+
244
+ # arch
245
+ plat.name('arch').title('Arch Linux').in_family('linux')
246
+ .detect {
247
+ if !unix_file_contents('/etc/arch-release').nil?
248
+ # Because this is a rolling release distribution,
249
+ # use the kernel release, ex. 4.1.6-1-ARCH
250
+ @platform[:release] = unix_uname_r
251
+ true
252
+ end
253
+ }
254
+
255
+ # slackware
256
+ plat.name('slackware').title('Slackware Linux').in_family('linux')
257
+ .detect {
258
+ if !(raw = unix_file_contents('/etc/slackware-version')).nil?
259
+ @platform[:release] = raw.scan(/(\d+|\.+)/).join
260
+ true
261
+ end
262
+ }
263
+
264
+ # gentoo
265
+ plat.name('gentoo').title('Gentoo Linux').in_family('linux')
266
+ .detect {
267
+ if !(raw = unix_file_contents('/etc/gentoo-release')).nil?
268
+ @platform[:release] = raw.scan(/(\d+|\.+)/).join
269
+ true
270
+ end
271
+ }
272
+
273
+ # exherbo
274
+ plat.name('exherbo').title('Exherbo Linux').in_family('linux')
275
+ .detect {
276
+ unless unix_file_contents('/etc/exherbo-release').nil?
277
+ # Because this is a rolling release distribution,
278
+ # use the kernel release, ex. 4.1.6
279
+ @platform[:release] = unix_uname_r
280
+ true
281
+ end
282
+ }
283
+
284
+ # alpine
285
+ plat.name('alpine').title('Alpine Linux').in_family('linux')
286
+ .detect {
287
+ if !(raw = unix_file_contents('/etc/alpine-release')).nil?
288
+ @platform[:release] = raw.strip
289
+ true
290
+ end
291
+ }
292
+
293
+ # coreos
294
+ plat.name('coreos').title('CoreOS Linux').in_family('linux')
295
+ .detect {
296
+ unless unix_file_contents('/etc/coreos/update.conf').nil?
297
+ lsb = read_linux_lsb
298
+ @platform[:release] = lsb[:release]
299
+ true
300
+ end
301
+ }
302
+
303
+ # brocade family detected here if device responds to 'uname' command,
304
+ # happens when logging in as root
305
+ plat.family('brocade').title('Brocade Family').in_family('linux')
306
+ .detect {
307
+ !brocade_version.nil?
308
+ }
309
+
310
+ # genaric linux
311
+ # this should always be last in the linux family list
312
+ plat.name('linux').title('Genaric Linux').in_family('linux')
313
+ .detect {
314
+ true
315
+ }
316
+
317
+ # openvms
318
+ plat.name('openvms').title('OpenVMS').in_family('unix')
319
+ .detect {
320
+ if unix_uname_s =~ /unrecognized command verb/i
321
+ cmd = @backend.run_command('show system/noprocess')
322
+ unless cmd.exit_status != 0 || cmd.stdout.empty?
323
+ @platform[:name] = cmd.stdout.downcase.split(' ')[0]
324
+ cmd = @backend.run_command('write sys$output f$getsyi("VERSION")')
325
+ @platform[:release] = cmd.stdout.downcase.split("\n")[1][1..-1]
326
+ cmd = @backend.run_command('write sys$output f$getsyi("ARCH_NAME")')
327
+ @platform[:arch] = cmd.stdout.downcase.split("\n")[1]
328
+ true
329
+ end
330
+ end
331
+ }
332
+
333
+ # aix
334
+ plat.family('aix').in_family('unix')
335
+ .detect {
336
+ true if unix_uname_s =~ /aix/i
337
+ }
338
+ plat.name('aix').title('Aix').in_family('aix')
339
+ .detect {
340
+ out = @backend.run_command('uname -rvp').stdout
341
+ m = out.match(/(\d+)\s+(\d+)\s+(.*)/)
342
+ unless m.nil?
343
+ @platform[:release] = "#{m[2]}.#{m[1]}"
344
+ @platform[:arch] = m[3].to_s
345
+ end
346
+ true
347
+ }
348
+
349
+ # solaris family
350
+ plat.family('solaris').in_family('unix')
351
+ .detect {
352
+ if unix_uname_s =~ /sunos/i
353
+ unless (version = /^5\.(?<release>\d+)$/.match(unix_uname_r)).nil?
354
+ @platform[:release] = version['release']
355
+ end
356
+
357
+ arch = @backend.run_command('uname -p')
358
+ @platform[:arch] = arch.stdout.chomp if arch.exit_status == 0
359
+ true
360
+ end
361
+ }
362
+ plat.name('smartos').title('SmartOS').in_family('solaris')
363
+ .detect {
364
+ rel = unix_file_contents('/etc/release')
365
+ if /^.*(SmartOS).*$/ =~ rel
366
+ true
367
+ end
368
+ }
369
+ plat.name('omnios').title('Omnios').in_family('solaris')
370
+ .detect {
371
+ rel = unix_file_contents('/etc/release')
372
+ if !(m = /^\s*(OmniOS).*r(\d+).*$/.match(rel)).nil?
373
+ @platform[:release] = m[2]
374
+ true
375
+ end
376
+ }
377
+ plat.name('openindiana').title('Openindiana').in_family('solaris')
378
+ .detect {
379
+ rel = unix_file_contents('/etc/release')
380
+ if !(m = /^\s*(OpenIndiana).*oi_(\d+).*$/.match(rel)).nil?
381
+ @platform[:release] = m[2]
382
+ true
383
+ end
384
+ }
385
+ plat.name('opensolaris').title('Open Solaris').in_family('solaris')
386
+ .detect {
387
+ rel = unix_file_contents('/etc/release')
388
+ if !(m = /^\s*(OpenSolaris).*snv_(\d+).*$/.match(rel)).nil?
389
+ @platform[:release] = m[2]
390
+ true
391
+ end
392
+ }
393
+ plat.name('nexentacore').title('Nexentacore').in_family('solaris')
394
+ .detect {
395
+ rel = unix_file_contents('/etc/release')
396
+ if /^\s*(NexentaCore)\s.*$/ =~ rel
397
+ true
398
+ end
399
+ }
400
+ plat.name('solaris').title('Solaris').in_family('solaris')
401
+ .detect {
402
+ rel = unix_file_contents('/etc/release')
403
+ if !(m = /Oracle Solaris (\d+)/.match(rel)).nil?
404
+ # TODO: should be string!
405
+ @platform[:release] = m[1]
406
+ true
407
+ elsif /^\s*(Solaris)\s.*$/ =~ rel
408
+ true
409
+ end
410
+
411
+ # must be some unknown solaris
412
+ true
413
+ }
414
+
415
+ # hpux
416
+ plat.family('hpux').in_family('unix')
417
+ .detect {
418
+ true if unix_uname_s =~ /hp-ux/i
419
+ }
420
+ plat.name('hpux').title('Hpux').in_family('hpux')
421
+ .detect {
422
+ @platform[:release] = unix_uname_r.lines[0].chomp
423
+ true
424
+ }
425
+
426
+ # qnx
427
+ plat.family('qnx').in_family('unix')
428
+ .detect {
429
+ true if unix_uname_s =~ /qnx/i
430
+ }
431
+ plat.name('qnx').title('QNX').in_family('qnx')
432
+ .detect {
433
+ @platform[:name] = unix_uname_s.lines[0].chomp.downcase
434
+ @platform[:release] = unix_uname_r.lines[0].chomp
435
+ @platform[:arch] = unix_uname_m
436
+ true
437
+ }
438
+
439
+ # bsd family
440
+ plat.family('bsd').in_family('unix')
441
+ .detect {
442
+ # we need a better way to determin this family
443
+ # for now we are going to just try each platform
444
+ true
445
+ }
446
+ plat.family('darwin').in_family('bsd')
447
+ .detect {
448
+ if unix_uname_s =~ /darwin/i
449
+ cmd = unix_file_contents('/usr/bin/sw_vers')
450
+ unless cmd.nil?
451
+ m = cmd.match(/^ProductVersion:\s+(.+)$/)
452
+ @platform[:release] = m.nil? ? nil : m[1]
453
+ m = cmd.match(/^BuildVersion:\s+(.+)$/)
454
+ @platform[:build] = m.nil? ? nil : m[1]
455
+ end
456
+ @platform[:release] = unix_uname_r.lines[0].chomp if @platform[:release].nil?
457
+ @platform[:arch] = unix_uname_m
458
+ true
459
+ end
460
+ }
461
+ plat.name('mac_os_x').title('macOS X').in_family('darwin')
462
+ .detect {
463
+ cmd = unix_file_contents('/System/Library/CoreServices/SystemVersion.plist')
464
+ @platform[:uuid_command] = "system_profiler SPHardwareDataType | awk '/UUID/ { print $3; }'"
465
+ true if cmd =~ /Mac OS X/i
466
+ }
467
+ plat.name('darwin').title('Darwin').in_family('darwin')
468
+ .detect {
469
+ # must be some other type of darwin
470
+ @platform[:name] = unix_uname_s.lines[0].chomp
471
+ true
472
+ }
473
+ plat.name('freebsd').title('Freebsd').in_family('bsd')
474
+ .detect {
475
+ if unix_uname_s =~ /freebsd/i
476
+ @platform[:name] = unix_uname_s.lines[0].chomp
477
+ @platform[:release] = unix_uname_r.lines[0].chomp
478
+ true
479
+ end
480
+ }
481
+ plat.name('openbsd').title('Openbsd').in_family('bsd')
482
+ .detect {
483
+ if unix_uname_s =~ /openbsd/i
484
+ @platform[:name] = unix_uname_s.lines[0].chomp
485
+ @platform[:release] = unix_uname_r.lines[0].chomp
486
+ true
487
+ end
488
+ }
489
+ plat.name('netbsd').title('Netbsd').in_family('bsd')
490
+ .detect {
491
+ if unix_uname_s =~ /netbsd/i
492
+ @platform[:name] = unix_uname_s.lines[0].chomp
493
+ @platform[:release] = unix_uname_r.lines[0].chomp
494
+ true
495
+ end
496
+ }
497
+
498
+ # arista_eos family
499
+ plat.family('arista_eos').title('Arista EOS Family').in_family('os')
500
+ .detect {
501
+ true
502
+ }
503
+ plat.name('arista_eos').title('Arista EOS').in_family('arista_eos')
504
+ .detect {
505
+ cmd = @backend.run_command('show version | json')
506
+ if cmd.exit_status == 0 && !cmd.stdout.empty?
507
+ require 'json'
508
+ begin
509
+ eos_ver = JSON.parse(cmd.stdout)
510
+ @platform[:release] = eos_ver['version']
511
+ @platform[:arch] = eos_ver['architecture']
512
+ true
513
+ rescue JSON::ParserError
514
+ nil
515
+ end
516
+ end
517
+ }
518
+
519
+ # esx
520
+ plat.family('esx').title('ESXi Family').in_family('os')
521
+ .detect {
522
+ true if unix_uname_s =~ /vmkernel/i
523
+ }
524
+ plat.name('vmkernel').in_family('esx')
525
+ .detect {
526
+ @platform[:name] = unix_uname_s.lines[0].chomp
527
+ @platform[:release] = unix_uname_r.lines[0].chomp
528
+ true
529
+ }
530
+
531
+ # cisco_ios family
532
+ plat.family('cisco').title('Cisco Family').in_family('os')
533
+ .detect {
534
+ !cisco_show_version.nil?
535
+ }
536
+ plat.name('cisco_ios').title('Cisco IOS').in_family('cisco')
537
+ .detect {
538
+ v = cisco_show_version
539
+ next unless v[:type] == 'ios'
540
+ @platform[:release] = v[:version]
541
+ @platform[:arch] = nil
542
+ true
543
+ }
544
+ plat.name('cisco_ios_xe').title('Cisco IOS XE').in_family('cisco')
545
+ .detect {
546
+ v = cisco_show_version
547
+ next unless v[:type] == 'ios-xe'
548
+ @platform[:release] = v[:version]
549
+ @platform[:arch] = nil
550
+ true
551
+ }
552
+ plat.name('cisco_nexus').title('Cisco Nexus').in_family('cisco')
553
+ .detect {
554
+ v = cisco_show_version
555
+ next unless v[:type] == 'nexus'
556
+ @platform[:release] = v[:version]
557
+ @platform[:arch] = nil
558
+ @platform[:uuid_command] = 'show inventory chassis | include SN'
559
+ true
560
+ }
561
+
562
+ # brocade family
563
+ plat.family('brocade').title('Brocade Family').in_family('os')
564
+ .detect {
565
+ !brocade_version.nil?
566
+ }
567
+
568
+ plat.name('brocade_fos').title('Brocade FOS').in_family('brocade')
569
+ .detect {
570
+ v = brocade_version
571
+ next unless v[:type] == 'fos'
572
+ @platform[:release] = v[:version]
573
+ @platform[:arch] = nil
574
+ true
575
+ }
576
+ end
577
+ end
578
+ end