train-core 1.4.4

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