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