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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +780 -0
- data/Gemfile +36 -0
- data/LICENSE +201 -0
- data/README.md +197 -0
- data/lib/train.rb +158 -0
- data/lib/train/errors.rb +32 -0
- data/lib/train/extras.rb +11 -0
- data/lib/train/extras/command_wrapper.rb +137 -0
- data/lib/train/extras/stat.rb +132 -0
- data/lib/train/file.rb +151 -0
- data/lib/train/file/local.rb +75 -0
- data/lib/train/file/local/unix.rb +96 -0
- data/lib/train/file/local/windows.rb +63 -0
- data/lib/train/file/remote.rb +36 -0
- data/lib/train/file/remote/aix.rb +21 -0
- data/lib/train/file/remote/linux.rb +19 -0
- data/lib/train/file/remote/qnx.rb +41 -0
- data/lib/train/file/remote/unix.rb +106 -0
- data/lib/train/file/remote/windows.rb +94 -0
- data/lib/train/options.rb +80 -0
- data/lib/train/platforms.rb +84 -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 +145 -0
- data/lib/train/platforms/detect/helpers/os_linux.rb +75 -0
- data/lib/train/platforms/detect/helpers/os_windows.rb +120 -0
- data/lib/train/platforms/detect/scanner.rb +84 -0
- data/lib/train/platforms/detect/specifications/api.rb +15 -0
- data/lib/train/platforms/detect/specifications/os.rb +578 -0
- data/lib/train/platforms/detect/uuid.rb +34 -0
- data/lib/train/platforms/family.rb +26 -0
- data/lib/train/platforms/platform.rb +101 -0
- data/lib/train/plugins.rb +40 -0
- data/lib/train/plugins/base_connection.rb +169 -0
- data/lib/train/plugins/transport.rb +49 -0
- data/lib/train/transports/local.rb +232 -0
- data/lib/train/version.rb +7 -0
- data/train-core.gemspec +27 -0
- metadata +116 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Train
|
4
|
+
class File
|
5
|
+
class Remote
|
6
|
+
class Windows < Train::File::Remote
|
7
|
+
attr_reader :path
|
8
|
+
# Ensures we do not use invalid characters for file names
|
9
|
+
# @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#naming_conventions
|
10
|
+
def sanitize_filename(path)
|
11
|
+
return if path.nil?
|
12
|
+
# we do not filter :, backslash and forward slash, since they are part of the path
|
13
|
+
@spath = path.gsub(/[<>"|?*]/, '')
|
14
|
+
end
|
15
|
+
|
16
|
+
def basename(suffix = nil, sep = '\\')
|
17
|
+
super(suffix, sep)
|
18
|
+
end
|
19
|
+
|
20
|
+
def content
|
21
|
+
return @content if defined?(@content)
|
22
|
+
@content = @backend.run_command("Get-Content(\"#{@spath}\") | Out-String").stdout
|
23
|
+
return @content unless @content.empty?
|
24
|
+
@content = nil if directory? # or size.nil? or size > 0
|
25
|
+
@content
|
26
|
+
end
|
27
|
+
|
28
|
+
def exist?
|
29
|
+
return @exist if defined?(@exist)
|
30
|
+
@exist = @backend.run_command(
|
31
|
+
"(Test-Path -Path \"#{@spath}\").ToString()").stdout.chomp == 'True'
|
32
|
+
end
|
33
|
+
|
34
|
+
def owner
|
35
|
+
owner = @backend.run_command(
|
36
|
+
"Get-Acl \"#{@spath}\" | select -expand Owner").stdout.strip
|
37
|
+
return if owner.empty?
|
38
|
+
owner
|
39
|
+
end
|
40
|
+
|
41
|
+
def type
|
42
|
+
if attributes.include?('Archive') && !attributes.include?('Directory')
|
43
|
+
return :file
|
44
|
+
elsif attributes.include?('ReparsePoint')
|
45
|
+
return :symlink
|
46
|
+
elsif attributes.include?('Directory')
|
47
|
+
return :directory
|
48
|
+
end
|
49
|
+
:unknown
|
50
|
+
end
|
51
|
+
|
52
|
+
def size
|
53
|
+
if file?
|
54
|
+
@backend.run_command("((Get-Item '#{@spath}').Length)").stdout.strip.to_i
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def product_version
|
59
|
+
@product_version ||= @backend.run_command(
|
60
|
+
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"#{@spath}\").ProductVersion").stdout.chomp
|
61
|
+
end
|
62
|
+
|
63
|
+
def file_version
|
64
|
+
@file_version ||= @backend.run_command(
|
65
|
+
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"#{@spath}\").FileVersion").stdout.chomp
|
66
|
+
end
|
67
|
+
|
68
|
+
%w{
|
69
|
+
mode group uid gid mtime selinux_label
|
70
|
+
}.each do |field|
|
71
|
+
define_method field.to_sym do
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def link_path
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def mounted
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def attributes
|
87
|
+
return @attributes if defined?(@attributes)
|
88
|
+
@attributes = @backend.run_command(
|
89
|
+
"(Get-ItemProperty -Path \"#{@spath}\").attributes.ToString()").stdout.chomp.split(/\s*,\s*/)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
|
+
# Author:: Christoph Hartmann (<chris@lollyrock.com>)
|
5
|
+
|
6
|
+
module Train
|
7
|
+
module Options
|
8
|
+
def self.attach(target)
|
9
|
+
target.class.method(:include).call(ClassOptions)
|
10
|
+
target.method(:include).call(InstanceOptions)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassOptions
|
14
|
+
def option(name, conf = nil, &block)
|
15
|
+
d = conf || {}
|
16
|
+
unless d.is_a? Hash
|
17
|
+
fail Train::ClientError,
|
18
|
+
"The transport plugin #{self} declared an option #{name} "\
|
19
|
+
"and didn't provide a valid configuration hash."
|
20
|
+
end
|
21
|
+
|
22
|
+
if !conf.nil? and !conf[:default].nil? and block_given?
|
23
|
+
fail Train::ClientError,
|
24
|
+
"The transport plugin #{self} declared an option #{name} "\
|
25
|
+
'with both a default value and block. Only use one of these.'
|
26
|
+
end
|
27
|
+
|
28
|
+
d[:default] = block if block_given?
|
29
|
+
|
30
|
+
default_options[name] = d
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_options
|
34
|
+
@default_options = {} unless defined? @default_options
|
35
|
+
@default_options
|
36
|
+
end
|
37
|
+
|
38
|
+
def include_options(other)
|
39
|
+
unless other.respond_to?(:default_options)
|
40
|
+
fail "Trying to include options from module #{other.inspect}, "\
|
41
|
+
"which doesn't seem to support options."
|
42
|
+
end
|
43
|
+
default_options.merge!(other.default_options)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module InstanceOptions
|
48
|
+
# @return [Hash] options, which created this Transport
|
49
|
+
attr_reader :options
|
50
|
+
|
51
|
+
def default_options
|
52
|
+
self.class.default_options
|
53
|
+
end
|
54
|
+
|
55
|
+
def merge_options(base, opts)
|
56
|
+
res = base.merge(opts || {})
|
57
|
+
default_options.each do |field, hm|
|
58
|
+
next unless res[field].nil? and hm.key?(:default)
|
59
|
+
default = hm[:default]
|
60
|
+
if default.is_a? Proc
|
61
|
+
res[field] = default.call(res)
|
62
|
+
else
|
63
|
+
res[field] = default
|
64
|
+
end
|
65
|
+
end
|
66
|
+
res
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_options(opts)
|
70
|
+
default_options.each do |field, hm|
|
71
|
+
if opts[field].nil? and hm[:required]
|
72
|
+
fail Train::ClientError,
|
73
|
+
"You must provide a value for #{field.to_s.inspect}."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
opts
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'train/platforms/common'
|
4
|
+
require 'train/platforms/detect'
|
5
|
+
require 'train/platforms/detect/scanner'
|
6
|
+
require 'train/platforms/detect/specifications/os'
|
7
|
+
require 'train/platforms/detect/specifications/api'
|
8
|
+
require 'train/platforms/detect/uuid'
|
9
|
+
require 'train/platforms/family'
|
10
|
+
require 'train/platforms/platform'
|
11
|
+
|
12
|
+
module Train::Platforms
|
13
|
+
class << self
|
14
|
+
# Retrieve the current platform list
|
15
|
+
#
|
16
|
+
# @return [Hash] map with platform names and their objects
|
17
|
+
def list
|
18
|
+
@list ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Retrieve the current family list
|
22
|
+
#
|
23
|
+
# @return [Hash] map with family names and their objects
|
24
|
+
def families
|
25
|
+
@families ||= {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create or update a platform
|
30
|
+
#
|
31
|
+
# @return Train::Platform
|
32
|
+
def self.name(name, condition = {})
|
33
|
+
# Check the list to see if one is already created
|
34
|
+
plat = list[name]
|
35
|
+
unless plat.nil?
|
36
|
+
# Pass the condition incase we are adding a family relationship
|
37
|
+
plat.condition = condition unless condition.nil?
|
38
|
+
return plat
|
39
|
+
end
|
40
|
+
|
41
|
+
Train::Platforms::Platform.new(name, condition)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Create or update a family
|
45
|
+
#
|
46
|
+
# @return Train::Platforms::Family
|
47
|
+
def self.family(name, condition = {})
|
48
|
+
# Check the families to see if one is already created
|
49
|
+
family = families[name]
|
50
|
+
unless family.nil?
|
51
|
+
# Pass the condition incase we are adding a family relationship
|
52
|
+
family.condition = condition unless condition.nil?
|
53
|
+
return family
|
54
|
+
end
|
55
|
+
|
56
|
+
Train::Platforms::Family.new(name, condition)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Find the families or top level platforms
|
60
|
+
#
|
61
|
+
# @return [Hash] with top level family and platforms
|
62
|
+
def self.top_platforms
|
63
|
+
top_platforms = list.select { |_key, value| value.families.empty? }
|
64
|
+
top_platforms.merge!(families.select { |_key, value| value.families.empty? })
|
65
|
+
top_platforms
|
66
|
+
end
|
67
|
+
|
68
|
+
# List all platforms and families in a readable output
|
69
|
+
def self.list_all
|
70
|
+
top_platforms = self.top_platforms
|
71
|
+
top_platforms.each_value do |platform|
|
72
|
+
puts platform.title
|
73
|
+
print_children(platform) if defined?(platform.children)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.print_children(parent, pad = 2)
|
78
|
+
parent.children.each do |key, value|
|
79
|
+
obj = key
|
80
|
+
puts "#{' ' * pad}-> #{obj.title}#{value unless value.empty?}"
|
81
|
+
print_children(obj, pad + 2) if defined?(obj.children) && !obj.children.nil?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Train::Platforms
|
4
|
+
module Common
|
5
|
+
# Add a family connection. This will create a family
|
6
|
+
# if it does not exist and add a child relationship.
|
7
|
+
def in_family(family)
|
8
|
+
if self.class == Train::Platforms::Family && @name == family
|
9
|
+
fail "Unable to add family #{@name} to itself: '#{@name}.in_family(#{family})'"
|
10
|
+
end
|
11
|
+
|
12
|
+
# add family to the family list
|
13
|
+
family = Train::Platforms.family(family)
|
14
|
+
family.children[self] = @condition
|
15
|
+
|
16
|
+
@families[family] = @condition
|
17
|
+
@condition = nil
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def detect(&block)
|
22
|
+
if block_given?
|
23
|
+
@detect = block
|
24
|
+
self
|
25
|
+
elsif @detect.nil?
|
26
|
+
# we are returning a block that just returns false here
|
27
|
+
# to skip the family/platform evaluation if detect is not set
|
28
|
+
->(_) { false }
|
29
|
+
else
|
30
|
+
@detect
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'train/platforms/detect/helpers/os_linux'
|
4
|
+
require 'train/platforms/detect/helpers/os_windows'
|
5
|
+
require 'rbconfig'
|
6
|
+
|
7
|
+
module Train::Platforms::Detect::Helpers
|
8
|
+
module OSCommon # rubocop:disable Metrics/ModuleLength
|
9
|
+
include Train::Platforms::Detect::Helpers::Linux
|
10
|
+
include Train::Platforms::Detect::Helpers::Windows
|
11
|
+
|
12
|
+
def ruby_host_os(regex)
|
13
|
+
::RbConfig::CONFIG['host_os'] =~ regex
|
14
|
+
end
|
15
|
+
|
16
|
+
def winrm?
|
17
|
+
Object.const_defined?('Train::Transports::WinRM::Connection') &&
|
18
|
+
@backend.class == Train::Transports::WinRM::Connection
|
19
|
+
end
|
20
|
+
|
21
|
+
def unix_file_contents(path)
|
22
|
+
# keep a log of files incase multiple checks call the same one
|
23
|
+
return @files[path] if @files.key?(path)
|
24
|
+
|
25
|
+
res = @backend.run_command("test -f #{path} && cat #{path}")
|
26
|
+
# ignore files that can't be read
|
27
|
+
@files[path] = res.exit_status.zero? ? res.stdout : nil
|
28
|
+
@files[path]
|
29
|
+
end
|
30
|
+
|
31
|
+
def unix_file_exist?(path)
|
32
|
+
@backend.run_command("test -f #{path}").exit_status.zero?
|
33
|
+
end
|
34
|
+
|
35
|
+
def command_output(cmd)
|
36
|
+
res = @backend.run_command(cmd).stdout
|
37
|
+
res.strip! unless res.nil?
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
def unix_uname_s
|
42
|
+
return @uname[:s] if @uname.key?(:s)
|
43
|
+
@uname[:s] = command_output('uname -s')
|
44
|
+
end
|
45
|
+
|
46
|
+
def unix_uname_r
|
47
|
+
return @uname[:r] if @uname.key?(:r)
|
48
|
+
@uname[:r] = command_output('uname -r')
|
49
|
+
end
|
50
|
+
|
51
|
+
def unix_uname_m
|
52
|
+
return @uname[:m] if @uname.key?(:m)
|
53
|
+
@uname[:m] = command_output('uname -m')
|
54
|
+
end
|
55
|
+
|
56
|
+
def brocade_version
|
57
|
+
return @cache[:brocade] if @cache.key?(:brocade)
|
58
|
+
res = command_output('version')
|
59
|
+
|
60
|
+
m = res.match(/^Fabric OS:\s+v(\S+)$/)
|
61
|
+
unless m.nil?
|
62
|
+
return @cache[:brocade] = { version: m[1], type: 'fos' }
|
63
|
+
end
|
64
|
+
|
65
|
+
@cache[:brocade] = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def cisco_show_version
|
69
|
+
return @cache[:cisco] if @cache.key?(:cisco)
|
70
|
+
res = command_output('show version')
|
71
|
+
|
72
|
+
m = res.match(/^Cisco IOS Software, [^,]+? \(([^,]+?)\), Version (\d+\.\d+)/)
|
73
|
+
unless m.nil?
|
74
|
+
return @cache[:cisco] = { version: m[2], model: m[1], type: 'ios' }
|
75
|
+
end
|
76
|
+
|
77
|
+
m = res.match(/^Cisco IOS Software, IOS-XE Software, [^,]+? \(([^,]+?)\), Version (\d+\.\d+\.\d+[A-Z]*)/)
|
78
|
+
unless m.nil?
|
79
|
+
return @cache[:cisco] = { version: m[2], model: m[1], type: 'ios-xe' }
|
80
|
+
end
|
81
|
+
|
82
|
+
m = res.match(/^Cisco Nexus Operating System \(NX-OS\) Software/)
|
83
|
+
unless m.nil?
|
84
|
+
v = res[/^\s*system:\s+version (\d+\.\d+)/, 1]
|
85
|
+
return @cache[:cisco] = { version: v, type: 'nexus' }
|
86
|
+
end
|
87
|
+
|
88
|
+
@cache[:cisco] = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def unix_uuid
|
92
|
+
uuid = unix_uuid_from_chef
|
93
|
+
uuid = unix_uuid_from_machine_file if uuid.nil?
|
94
|
+
uuid = uuid_from_command if uuid.nil?
|
95
|
+
raise Train::TransportError, 'Cannot find a UUID for your node.' if uuid.nil?
|
96
|
+
uuid
|
97
|
+
end
|
98
|
+
|
99
|
+
def unix_uuid_from_chef
|
100
|
+
file = @backend.file('/var/chef/cache/data_collector_metadata.json')
|
101
|
+
if file.exist? && !file.size.zero?
|
102
|
+
json = ::JSON.parse(file.content)
|
103
|
+
return json['node_uuid'] if json['node_uuid']
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def unix_uuid_from_machine_file
|
108
|
+
%W(
|
109
|
+
/etc/chef/chef_guid
|
110
|
+
#{ENV['HOME']}/.chef/chef_guid
|
111
|
+
/etc/machine-id
|
112
|
+
/var/lib/dbus/machine-id
|
113
|
+
/var/db/dbus/machine-id
|
114
|
+
).each do |path|
|
115
|
+
file = @backend.file(path)
|
116
|
+
next unless file.exist? && !file.size.zero?
|
117
|
+
return file.content.chomp if path =~ /guid/
|
118
|
+
return uuid_from_string(file.content.chomp)
|
119
|
+
end
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
# This takes a command from the platform detect block to run.
|
124
|
+
# We expect the command to return a unique identifier which
|
125
|
+
# we turn into a UUID.
|
126
|
+
def uuid_from_command
|
127
|
+
return unless @platform[:uuid_command]
|
128
|
+
result = @backend.run_command(@platform[:uuid_command])
|
129
|
+
uuid_from_string(result.stdout.chomp) if result.exit_status.zero? && !result.stdout.empty?
|
130
|
+
end
|
131
|
+
|
132
|
+
# This hashes the passed string into SHA1.
|
133
|
+
# Then it downgrades the 160bit SHA1 to a 128bit
|
134
|
+
# then we format it as a valid UUIDv5.
|
135
|
+
def uuid_from_string(string)
|
136
|
+
hash = Digest::SHA1.new
|
137
|
+
hash.update(string)
|
138
|
+
ary = hash.digest.unpack('NnnnnN')
|
139
|
+
ary[2] = (ary[2] & 0x0FFF) | (5 << 12)
|
140
|
+
ary[3] = (ary[3] & 0x3FFF) | 0x8000
|
141
|
+
# rubocop:disable Style/FormatString
|
142
|
+
'%08x-%04x-%04x-%04x-%04x%08x' % ary
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|