train 0.12.1

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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +71 -0
  3. data/CHANGELOG.md +308 -0
  4. data/Gemfile +30 -0
  5. data/LICENSE +201 -0
  6. data/README.md +156 -0
  7. data/Rakefile +148 -0
  8. data/lib/train.rb +117 -0
  9. data/lib/train/errors.rb +23 -0
  10. data/lib/train/extras.rb +17 -0
  11. data/lib/train/extras/command_wrapper.rb +148 -0
  12. data/lib/train/extras/file_aix.rb +20 -0
  13. data/lib/train/extras/file_common.rb +161 -0
  14. data/lib/train/extras/file_linux.rb +16 -0
  15. data/lib/train/extras/file_unix.rb +79 -0
  16. data/lib/train/extras/file_windows.rb +91 -0
  17. data/lib/train/extras/linux_lsb.rb +60 -0
  18. data/lib/train/extras/os_common.rb +136 -0
  19. data/lib/train/extras/os_detect_darwin.rb +32 -0
  20. data/lib/train/extras/os_detect_linux.rb +148 -0
  21. data/lib/train/extras/os_detect_unix.rb +99 -0
  22. data/lib/train/extras/os_detect_windows.rb +57 -0
  23. data/lib/train/extras/stat.rb +133 -0
  24. data/lib/train/options.rb +80 -0
  25. data/lib/train/plugins.rb +40 -0
  26. data/lib/train/plugins/base_connection.rb +86 -0
  27. data/lib/train/plugins/transport.rb +49 -0
  28. data/lib/train/transports/docker.rb +103 -0
  29. data/lib/train/transports/local.rb +52 -0
  30. data/lib/train/transports/local_file.rb +90 -0
  31. data/lib/train/transports/local_os.rb +51 -0
  32. data/lib/train/transports/mock.rb +147 -0
  33. data/lib/train/transports/ssh.rb +163 -0
  34. data/lib/train/transports/ssh_connection.rb +225 -0
  35. data/lib/train/transports/winrm.rb +184 -0
  36. data/lib/train/transports/winrm_connection.rb +194 -0
  37. data/lib/train/version.rb +7 -0
  38. data/test/integration/.kitchen.yml +43 -0
  39. data/test/integration/Berksfile +3 -0
  40. data/test/integration/bootstrap.sh +17 -0
  41. data/test/integration/chefignore +1 -0
  42. data/test/integration/cookbooks/test/metadata.rb +1 -0
  43. data/test/integration/cookbooks/test/recipes/default.rb +100 -0
  44. data/test/integration/cookbooks/test/recipes/prep_files.rb +47 -0
  45. data/test/integration/docker_run.rb +153 -0
  46. data/test/integration/docker_test.rb +24 -0
  47. data/test/integration/docker_test_container.rb +24 -0
  48. data/test/integration/helper.rb +61 -0
  49. data/test/integration/sudo/customcommand.rb +15 -0
  50. data/test/integration/sudo/nopasswd.rb +16 -0
  51. data/test/integration/sudo/passwd.rb +21 -0
  52. data/test/integration/sudo/reqtty.rb +17 -0
  53. data/test/integration/sudo/run_as.rb +12 -0
  54. data/test/integration/test-travis-1.yaml +13 -0
  55. data/test/integration/test-travis-2.yaml +13 -0
  56. data/test/integration/test_local.rb +19 -0
  57. data/test/integration/test_ssh.rb +39 -0
  58. data/test/integration/tests/path_block_device_test.rb +74 -0
  59. data/test/integration/tests/path_character_device_test.rb +74 -0
  60. data/test/integration/tests/path_file_test.rb +79 -0
  61. data/test/integration/tests/path_folder_test.rb +90 -0
  62. data/test/integration/tests/path_missing_test.rb +77 -0
  63. data/test/integration/tests/path_pipe_test.rb +78 -0
  64. data/test/integration/tests/path_symlink_test.rb +95 -0
  65. data/test/integration/tests/run_command_test.rb +28 -0
  66. data/test/unit/extras/command_wrapper_test.rb +78 -0
  67. data/test/unit/extras/file_common_test.rb +180 -0
  68. data/test/unit/extras/linux_file_test.rb +167 -0
  69. data/test/unit/extras/os_common_test.rb +269 -0
  70. data/test/unit/extras/os_detect_linux_test.rb +189 -0
  71. data/test/unit/extras/os_detect_windows_test.rb +99 -0
  72. data/test/unit/extras/stat_test.rb +148 -0
  73. data/test/unit/extras/windows_file_test.rb +44 -0
  74. data/test/unit/helper.rb +7 -0
  75. data/test/unit/plugins/connection_test.rb +44 -0
  76. data/test/unit/plugins/transport_test.rb +111 -0
  77. data/test/unit/plugins_test.rb +22 -0
  78. data/test/unit/train_test.rb +156 -0
  79. data/test/unit/transports/local_file_test.rb +184 -0
  80. data/test/unit/transports/local_test.rb +87 -0
  81. data/test/unit/transports/mock_test.rb +87 -0
  82. data/test/unit/transports/ssh_test.rb +109 -0
  83. data/test/unit/version_test.rb +8 -0
  84. data/test/windows/local_test.rb +46 -0
  85. data/test/windows/winrm_test.rb +52 -0
  86. data/train.gemspec +38 -0
  87. metadata +295 -0
@@ -0,0 +1,156 @@
1
+ # Train - Transport Interface
2
+
3
+ Train lets you talk to your local or remote operating systems with a unified interface.
4
+
5
+ It allows you to:
6
+
7
+ * execute commands via `run_command`
8
+ * interact with files via `file`
9
+ * identify the target operating system via `os`
10
+
11
+ Train supports:
12
+
13
+ * Local execution
14
+ * SSH
15
+ * WinRM
16
+ * Docker
17
+ * Mock (for testing and debugging)
18
+
19
+ # Examples
20
+
21
+ ## Setup
22
+
23
+ **Local**
24
+
25
+ ```ruby
26
+ require 'train'
27
+ train = Train.create('local')
28
+ ```
29
+
30
+ **SSH**
31
+
32
+ ```ruby
33
+ require 'train'
34
+ train = Train.create('ssh',
35
+ host: '1.2.3.4', port: 22, user: 'root', key_files: '/vagrant')
36
+ ```
37
+
38
+ **WinRM**
39
+
40
+ ```ruby
41
+ require 'train'
42
+ train = Train.create('winrm',
43
+ host: '1.2.3.4', user: 'Administrator', password: '...', ssl: true, self_signed: true)
44
+ ```
45
+
46
+ **Docker**
47
+
48
+ ```ruby
49
+ require 'train'
50
+ train = Train.create('docker', host: 'container_id...')
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ To get a list of available options for a plugin:
56
+
57
+ ```ruby
58
+ puts Train.options('ssh')
59
+ ```
60
+ This will provide all configuration options:
61
+
62
+ ```ruby
63
+ {
64
+ :host => { :required => true},
65
+ :port => { :default => 22, :required => true},
66
+ :user => { :default => "root", :required => true},
67
+ :keys => { :default => nil},
68
+ :password => { :default => nil},
69
+ ...
70
+ ```
71
+
72
+ ## Usage
73
+
74
+ ```ruby
75
+ # start or reuse a connection
76
+ conn = train.connection
77
+
78
+ # run a command on Linux/Unix/Mac
79
+ puts conn.run_command('whoami').stdout
80
+
81
+ # get OS info
82
+ puts conn.os[:family]
83
+ puts conn.os[:release]
84
+
85
+ # access files
86
+ puts conn.file('/proc/version').content
87
+
88
+ # close the connection
89
+ conn.close
90
+ ```
91
+
92
+ # Testing
93
+
94
+ We perform `unit`, `integration` and `windows` tests.
95
+
96
+ * `unit` tests ensure the intended behaviour of the implementation
97
+ * `integration` tests run against VMs and docker containers
98
+ * `windows` tests that run on appveyor for windows integration tests
99
+
100
+ ## Windows
101
+
102
+ ```
103
+ # run windows tests
104
+ bundle exec rake test:windows
105
+
106
+ # run single tests
107
+ bundle exec ruby -I .\test\windows\ .\test\windows\local_test.rb
108
+ ```
109
+
110
+
111
+ # Kudos and Contributors
112
+
113
+ Train is heavily based on the work of:
114
+
115
+ * [test-kitchen](https://github.com/test-kitchen/test-kitchen)
116
+
117
+ by [Fletcher Nichol](fnichol@nichol.ca)
118
+ and [a great community of contributors](https://github.com/test-kitchen/test-kitchen/graphs/contributors)
119
+
120
+ * [ohai](https://github.com/chef/ohai)
121
+
122
+ by Adam Jacob, Chef Software Inc.
123
+ and [a great community of contributors](https://github.com/chef/ohai/graphs/contributors)
124
+
125
+
126
+ ## Contributing
127
+
128
+ 1. Fork it
129
+ 1. Create your feature branch (git checkout -b my-new-feature)
130
+ 1. Commit your changes (git commit -am 'Add some feature')
131
+ 1. Push to the branch (git push origin my-new-feature)
132
+ 1. Create new Pull Request
133
+
134
+ ## License
135
+
136
+ | **Author:** | Dominik Richter (<drichter@chef.io>)
137
+
138
+ | **Author:** | Christoph Hartmann (<chartmann@chef.io>)
139
+
140
+ | **Copyright:** | Copyright (c) 2015 Chef Software Inc.
141
+
142
+ | **Copyright:** | Copyright (c) 2015 Vulcano Security GmbH.
143
+
144
+ | **License:** | Apache License, Version 2.0
145
+
146
+ Licensed under the Apache License, Version 2.0 (the "License");
147
+ you may not use this file except in compliance with the License.
148
+ You may obtain a copy of the License at
149
+
150
+ http://www.apache.org/licenses/LICENSE-2.0
151
+
152
+ Unless required by applicable law or agreed to in writing, software
153
+ distributed under the License is distributed on an "AS IS" BASIS,
154
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
155
+ See the License for the specific language governing permissions and
156
+ limitations under the License.
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env rake
2
+ # encoding: utf-8
3
+
4
+ require 'bundler'
5
+ require 'bundler/gem_tasks'
6
+ require 'rake/testtask'
7
+ require 'rubocop/rake_task'
8
+
9
+ # Rubocop
10
+ desc 'Run Rubocop lint checks'
11
+ task :rubocop do
12
+ RuboCop::RakeTask.new
13
+ end
14
+
15
+ # lint the project
16
+ desc 'Run robocop linter'
17
+ task lint: [:rubocop]
18
+
19
+ # run tests
20
+ task default: [:test, :lint]
21
+
22
+ Rake::TestTask.new do |t|
23
+ t.libs << 'test/unit'
24
+ t.pattern = 'test/unit/**/*_test.rb'
25
+ t.warning = true
26
+ t.verbose = true
27
+ t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION)
28
+ end
29
+
30
+ namespace :test do
31
+ task :docker do
32
+ path = File.join(File.dirname(__FILE__), 'test', 'integration')
33
+ sh('sh', '-c', "cd #{path} && ruby -I ../../lib docker_test.rb tests/*")
34
+ end
35
+
36
+ task :windows do
37
+ Dir.glob('test/windows/*_test.rb').all? do |file|
38
+ sh(Gem.ruby, '-w', '-I .\test\windows', file)
39
+ end or fail 'Failures'
40
+ end
41
+
42
+ task :vm do
43
+ concurrency = ENV['CONCURRENCY'] || 4
44
+ path = File.join(File.dirname(__FILE__), 'test', 'integration')
45
+ sh('sh', '-c', "cd #{path} && kitchen test -c #{concurrency}")
46
+ end
47
+
48
+ # Target required:
49
+ # rake "test:ssh[user@server]"
50
+ # sh -c cd /home/foobarbam/src/gems/train/test/integration \
51
+ # && target=user@server ruby -I ../../lib test_ssh.rb tests/*
52
+ # ...
53
+ # Turn debug logging back on:
54
+ # debug=1 rake "test:ssh[user@server]"
55
+ # Use a different ssh key:
56
+ # key_files=/home/foobarbam/.ssh/id_rsa2 rake "test:ssh[user@server]"
57
+ # Run with a specific test:
58
+ # test=path_block_device_test.rb rake "test:ssh[user@server]"
59
+ task :ssh, [:target] do |t, args|
60
+ path = File.join(File.dirname(__FILE__), 'test', 'integration')
61
+ key_files = ENV['key_files'] || File.join(ENV['HOME'], '.ssh', 'id_rsa')
62
+
63
+ sh_cmd = "cd #{path} && target=#{args[:target]} key_files=#{key_files}"
64
+
65
+ sh_cmd += " debug=#{ENV['debug']}" if ENV['debug']
66
+ sh_cmd += ' ruby -I ../../lib test_ssh.rb tests/'
67
+ sh_cmd += ENV['test'] || '*'
68
+
69
+ sh('sh', '-c', sh_cmd)
70
+ end
71
+ end
72
+
73
+ # Print the current version of this gem or update it.
74
+ #
75
+ # @param [Type] target the new version you want to set, or nil if you only want to show
76
+ def train_version(target = nil)
77
+ path = 'lib/train/version.rb'
78
+ require_relative path.sub(/.rb$/, '')
79
+
80
+ nu_version = target.nil? ? '' : " -> #{target}"
81
+ puts "Train: #{Train::VERSION}#{nu_version}"
82
+
83
+ unless target.nil?
84
+ raw = File.read(path)
85
+ nu = raw.sub(/VERSION.*/, "VERSION = '#{target}'.freeze")
86
+ File.write(path, nu)
87
+ load(path)
88
+ end
89
+ end
90
+
91
+ # Check if a command is available
92
+ #
93
+ # @param [Type] x the command you are interested in
94
+ # @param [Type] msg the message to display if the command is missing
95
+ def require_command(x, msg = nil)
96
+ return if system("command -v #{x} || exit 1")
97
+ msg ||= 'Please install it first!'
98
+ puts "\033[31;1mCan't find command #{x.inspect}. #{msg}\033[0m"
99
+ exit 1
100
+ end
101
+
102
+ # Check if a required environment variable has been set
103
+ #
104
+ # @param [String] x the variable you are interested in
105
+ # @param [String] msg the message you want to display if the variable is missing
106
+ def require_env(x, msg = nil)
107
+ exists = `env | grep "^#{x}="`
108
+ return unless exists.empty?
109
+ puts "\033[31;1mCan't find environment variable #{x.inspect}. #{msg}\033[0m"
110
+ exit 1
111
+ end
112
+
113
+ # Check the requirements for running an update of this repository.
114
+ def check_update_requirements
115
+ require_command 'git'
116
+ require_command 'github_changelog_generator', "\n"\
117
+ "For more information on how to install it see:\n"\
118
+ " https://github.com/skywinder/github-changelog-generator\n"
119
+ require_env 'CHANGELOG_GITHUB_TOKEN', "\n"\
120
+ "Please configure this token to make sure you can run all commands\n"\
121
+ "against GitHub.\n\n"\
122
+ "See github_changelog_generator homepage for more information:\n"\
123
+ " https://github.com/skywinder/github-changelog-generator\n"
124
+ end
125
+
126
+ # Show the current version of this gem.
127
+ desc 'Show the version of this gem'
128
+ task :version do
129
+ train_version
130
+ end
131
+
132
+ desc 'Generate the changelog'
133
+ task :changelog do
134
+ require_relative 'lib/train/version'
135
+ system "github_changelog_generator -u chef -p train --future-release #{Train::VERSION}"
136
+ end
137
+
138
+ # Update the version of this gem and create an updated
139
+ # changelog. It covers everything short of actually releasing
140
+ # the gem.
141
+ desc 'Bump the version of this gem'
142
+ task :bump_version, [:version] do |_, args|
143
+ v = args[:version] || ENV['to']
144
+ fail "You must specify a target version! rake bump_version to=1.2.3" if v.empty?
145
+ check_update_requirements
146
+ train_version(v)
147
+ Rake::Task['changelog'].invoke
148
+ end
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Dominik Richter (<dominik.richter@gmail.com>)
4
+
5
+ require 'train/version'
6
+ require 'train/plugins'
7
+ require 'train/errors'
8
+ require 'uri'
9
+
10
+ module Train
11
+ # Create a new transport instance, with the plugin indicated by the
12
+ # given name.
13
+ #
14
+ # @param [String] name of the plugin
15
+ # @param [Array] *args list of arguments for the plugin
16
+ # @return [Transport] instance of the new transport or nil
17
+ def self.create(name, *args)
18
+ cls = load_transport(name)
19
+ cls.new(*args) unless cls.nil?
20
+ end
21
+
22
+ # Retrieve the configuration options of a transport plugin.
23
+ #
24
+ # @param [String] name of the plugin
25
+ # @return [Hash] map of default options
26
+ def self.options(name)
27
+ cls = load_transport(name)
28
+ cls.default_options unless cls.nil?
29
+ end
30
+
31
+ # Load the transport plugin indicated by name. If the plugin is not
32
+ # yet found in the plugin registry, it will be attempted to load from
33
+ # `train/transports/plugin_name`.
34
+ #
35
+ # @param [String] name of the plugin
36
+ # @return [Train::Transport] the transport plugin
37
+ def self.load_transport(name)
38
+ res = Train::Plugins.registry[name.to_s]
39
+ return res unless res.nil?
40
+
41
+ # if the plugin wasnt loaded yet:
42
+ require 'train/transports/' + name.to_s
43
+ Train::Plugins.registry[name.to_s]
44
+ rescue LoadError => _
45
+ raise Train::UserError,
46
+ "Can't find train plugin #{name.inspect}. Please install it first."
47
+ end
48
+
49
+ # Resolve target configuration in URI-scheme into
50
+ # all respective fields and merge with existing configuration.
51
+ # e.g. ssh://bob@remote => backend: ssh, user: bob, host: remote
52
+ def self.target_config(config = nil) # rubocop:disable Metrics/AbcSize
53
+ conf = config.nil? ? {} : config.dup
54
+
55
+ # symbolize keys
56
+ conf = conf.each_with_object({}) do |(k, v), acc|
57
+ acc[k.to_sym] = v
58
+ acc
59
+ end
60
+
61
+ group_keys_and_keyfiles(conf)
62
+
63
+ return conf if conf[:target].to_s.empty?
64
+
65
+ # split up the target's host/scheme configuration
66
+ uri = URI.parse(conf[:target].to_s)
67
+ unless uri.host.nil? and uri.scheme.nil?
68
+ conf[:backend] ||= uri.scheme
69
+ conf[:host] ||= uri.host
70
+ conf[:port] ||= uri.port
71
+ conf[:user] ||= uri.user
72
+ conf[:password] ||= uri.password
73
+ conf[:path] ||= uri.path
74
+ end
75
+
76
+ # ensure path is nil, if its empty; e.g. required to reset defaults for winrm
77
+ conf[:path] = nil if !conf[:path].nil? && conf[:path].to_s.empty?
78
+
79
+ # return the updated config
80
+ conf
81
+ end
82
+
83
+ def self.validate_backend(conf, default = :local)
84
+ return default if conf.nil?
85
+ res = conf[:backend]
86
+ return res if !res.nil?
87
+
88
+ if !conf[:target].nil?
89
+ fail Train::UserError, 'Cannot determine backend from target '\
90
+ "configuration #{conf[:target].inspect}. Valid example: ssh://192.168.0.1."
91
+ end
92
+
93
+ if !conf[:host].nil?
94
+ fail Train::UserError, 'Host configured, but no backend was provided. Please '\
95
+ 'specify how you want to connect. Valid example: ssh://192.168.0.1.'
96
+ end
97
+
98
+ conf[:backend] = default
99
+ end
100
+
101
+ def self.group_keys_and_keyfiles(conf)
102
+ # in case the user specified a key-file, register it that way
103
+ # we will clear the list of keys and put keys and key_files separately
104
+ keys_mixed = conf[:keys]
105
+ return if keys_mixed.nil?
106
+
107
+ conf[:key_files] = []
108
+ conf[:keys] = []
109
+ keys_mixed.each do |key|
110
+ if !key.nil? and File.file?(key)
111
+ conf[:key_files].push(key)
112
+ else
113
+ conf[:keys].push(key)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ # Author:: Dominik Richter (<dominik.richter@gmail.com>)
5
+ # Author:: Christoph Hartmann (<chris@lollyrock.com>)
6
+ #
7
+ # Copyright (C) 2013, Fletcher Nichol
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+
11
+ module Train
12
+ # Base exception class for all exceptions that are caused by user input
13
+ # errors.
14
+ class UserError < ::StandardError; end
15
+
16
+ # Base exception class for all exceptions that are caused by incorrect use
17
+ # of an API.
18
+ class ClientError < ::StandardError; end
19
+
20
+ # Base exception class for all exceptions that are caused by other failures
21
+ # in the transport layer.
22
+ class TransportError < ::StandardError; end
23
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Dominik Richter (<dominik.richter@gmail.com>)
4
+
5
+ module Train::Extras
6
+ autoload :CommandWrapper, 'train/extras/command_wrapper'
7
+ autoload :FileCommon, 'train/extras/file_common'
8
+ autoload :AixFile, 'train/extras/file_aix'
9
+ autoload :UnixFile, 'train/extras/file_unix'
10
+ autoload :LinuxFile, 'train/extras/file_linux'
11
+ autoload :WindowsFile, 'train/extras/file_windows'
12
+ autoload :OSCommon, 'train/extras/os_common'
13
+ autoload :Stat, 'train/extras/stat'
14
+
15
+ CommandResult = Struct.new(:stdout, :stderr, :exit_status)
16
+ LoginCommand = Struct.new(:command, :arguments)
17
+ end