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,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,12 @@
1
+ # encoding: utf-8
2
+
3
+ module Train::Platforms
4
+ module Detect
5
+ # Main detect method to scan all platforms for a match
6
+ #
7
+ # @return Train::Platform instance or error if none found
8
+ def self.scan(backend)
9
+ Scanner.new(backend).scan
10
+ end
11
+ end
12
+ 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