train 0.12.1

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