winrm-transport 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8fceb01bb304b3dbde260f959336ea7dc319dd81
4
+ data.tar.gz: 35dfc04b0d86e4ec094e6646b2e0ffc34224e623
5
+ SHA512:
6
+ metadata.gz: 5ea5f23b2e9bc90cd2fe5c8db87b18fed3b5061e9b4858160cef498583b07c0ca7fc47da1a22f33e244b17a17bef653233070456fc953afa1f27bcfac1b2a0d0
7
+ data.tar.gz: d2ac24211bd20bc50cfaae3c5c02c430293bf5701975072fb50cb429948db816551aa9a486ee136b69193c475f17665e9455ee30eaf566c22e078a012ff4be4b
data/.cane ADDED
@@ -0,0 +1,2 @@
1
+ --abc-max 20
2
+ --style-measure 100
@@ -0,0 +1,15 @@
1
+ *.gem
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ /.rvmrc
12
+ /.rbenv-version
13
+ /.ruby-version
14
+ /.project
15
+ /.DS_Store
@@ -0,0 +1,26 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 2.2
5
+ - 2.1
6
+ - 2.0.0
7
+ - 1.9.3
8
+ - ruby-head
9
+
10
+ before_install:
11
+ - echo "Updating Bundler to ~> 1.9 until TravisCI has upgraded."
12
+ - gem install bundler -v "~> 1.9"
13
+ - bundle --version
14
+
15
+ bundler_args: --without guard
16
+
17
+ sudo: false
18
+
19
+ matrix:
20
+ allow_failures:
21
+ - rvm: ruby-head
22
+
23
+ addons:
24
+ code_climate:
25
+ repo_token:
26
+ secure: "WSrIaJVtAh88T7cd3DQY38yxiDz44Oms4Z5Dm+mVUv8gpKWixMR3t5ShmDW8+XXWWj3s3WUa2t5yZugrfz0gkrKCxSII6VURakBKy2jEKnEnJWoh8LKYjXHYW2fOU/PpclSFl0Ynxvt8Nn/F9dpbaGAqj2DwycAV4EtByuy06X0="
@@ -0,0 +1,3 @@
1
+ ## 1.0.0 / 2015-03-29
2
+
3
+ The initial release.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # -*- encoding: utf-8 -*-
2
+ source "https://rubygems.org"
3
+ gemspec
4
+
5
+ group :guard do
6
+ gem "guard-minitest"
7
+ gem "guard-rubocop"
8
+ gem "guard-yard"
9
+ end
10
+
11
+ group :test do
12
+ gem "codeclimate-test-reporter", :require => nil
13
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ ignore %r{^\.gem/}
3
+
4
+ def rubocop_opts
5
+ { :all_on_start => false, :keep_failed => false, :cli => "-r finstyle" }
6
+ end
7
+
8
+ def yard_opts
9
+ { :port => "8808" }
10
+ end
11
+
12
+ group :red_green_refactor, :halt_on_fail => true do
13
+ guard :minitest do
14
+ watch(%r{^spec/(.*)_spec\.rb})
15
+ watch(%r{^lib/(.*)([^/]+)\.rb}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
16
+ watch(%r{^spec/spec_helper\.rb}) { "spec" }
17
+ end
18
+
19
+ guard :rubocop, rubocop_opts do
20
+ watch(%r{.+\.rb$})
21
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
22
+ end
23
+ end
24
+
25
+ guard :yard, yard_opts do
26
+ watch(%r{lib/.+\.rb})
27
+ end
@@ -0,0 +1,15 @@
1
+ Author:: Fletcher Nichol (<fnichol@nichol.ca>)
2
+
3
+ Copyright 2015 Fletcher Nichol
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
@@ -0,0 +1,80 @@
1
+ # WinRM::Transport
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/winrm-transport.svg)](http://badge.fury.io/rb/winrm-transport)
4
+ [![Build Status](https://secure.travis-ci.org/test-kitchen/winrm-transport.svg?branch=master)](https://travis-ci.org/test-kitchen/winrm-transport)
5
+ [![Code Climate](https://codeclimate.com/github/test-kitchen/winrm-transport.svg)](https://codeclimate.com/github/test-kitchen/winrm-transport)
6
+ [![Test Coverage](https://codeclimate.com/github/test-kitchen/winrm-transport/badges/coverage.svg)](https://codeclimate.com/github/test-kitchen/winrm-transport)
7
+ [![Inline docs](http://inch-ci.org/github/test-kitchen/winrm-transport.svg?branch=master)](http://inch-ci.org/github/test-kitchen/winrm-transport)
8
+ [![Dependency Status](https://gemnasium.com/test-kitchen/winrm-transport.svg)](https://gemnasium.com/test-kitchen/winrm-transport)
9
+
10
+ WinRM transport logic for re-using remote shells and uploading files. The original code was extracted from the [Test Kitchen][test_kitchen] project and remains the primary reference use case.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'winrm-transport'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install winrm-transport
27
+
28
+ ## Usage
29
+
30
+ This a library gem and doesn't have any CLI commands. There are 2 primary object classes:
31
+
32
+ * [WinRM::Transport::CommandExecutor][command_executor]: an object which can
33
+ execute multiple commands and PowerShell script in one shared remote shell
34
+ session.
35
+ * [WinRM::Transport::FileTransporter][file_transporter]: an object which can
36
+ upload one or more files or directories to a remote host over WinRM only
37
+ using PowerShell scripts and CMD commands.
38
+
39
+ ## Versioning
40
+
41
+ WinRM::Transport aims to adhere to [Semantic Versioning 2.0.0][semver].
42
+
43
+ ## Development
44
+
45
+ * Source hosted at [GitHub][repo]
46
+ * Report issues/questions/feature requests on [GitHub Issues][issues]
47
+
48
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
49
+ `bin/console` for an interactive prompt that will allow you to experiment.
50
+
51
+ ## Contributing
52
+
53
+ Pull requests are very welcome! Make sure your patches are well tested.
54
+ Ideally create a topic branch for every separate change you make. For
55
+ example:
56
+
57
+ 1. Fork it ( https://github.com/test-kitchen/winrm-transport/fork )
58
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
59
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
60
+ 4. Push to the branch (`git push origin my-new-feature`)
61
+ 5. Create a new Pull Request
62
+
63
+ ## Authors
64
+
65
+ Created and maintained by [Fletcher Nichol][fnichol] (<fnichol@nichol.ca>) and
66
+ a growing community of [contributors][contributors].
67
+
68
+ ## License
69
+
70
+ Apache License, Version 2.0 (see [LICENSE.txt][license])
71
+
72
+ [command_executor]: https://github.com/test-kitchen/winrm-transport/blob/master/lib/winrm/transport/command_executor.rb
73
+ [contributors]: https://github.com/test-kitchen/winrm-transport/graphs/contributors
74
+ [file_transporter]: https://github.com/test-kitchen/winrm-transport/blob/master/lib/winrm/transport/file_transporter.rb
75
+ [fnichol]: https://github.com/fnichol
76
+ [issues]: https://github.com/test-kitchen/winrm-transpor/issues
77
+ [license]: https://github.com/test-kitchen/winrm-transport/blob/master/LICENSE.txt
78
+ [repo]: https://github.com/test-kitchen/winrm-transport
79
+ [semver]: http://semver.org/
80
+ [test_kitchen]: http://kitchen.ci
@@ -0,0 +1,49 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rake/testtask"
6
+ Rake::TestTask.new(:unit) do |t|
7
+ t.libs.push "lib"
8
+ t.test_files = FileList["spec/**/*_spec.rb"]
9
+ t.verbose = true
10
+ end
11
+
12
+ desc "Run all test suites"
13
+ task :test => [:unit]
14
+
15
+ desc "Display LOC stats"
16
+ task :stats do
17
+ puts "\n## Production Code Stats"
18
+ sh "countloc -r lib"
19
+ puts "\n## Test Code Stats"
20
+ sh "countloc -r spec"
21
+ end
22
+
23
+ require "finstyle"
24
+ require "rubocop/rake_task"
25
+ RuboCop::RakeTask.new(:style) do |task|
26
+ task.options << "--display-cop-names"
27
+ end
28
+
29
+ require "cane/rake_task"
30
+ desc "Run cane to check quality metrics"
31
+ Cane::RakeTask.new do |cane|
32
+ cane.canefile = "./.cane"
33
+ end
34
+
35
+ desc "Run all quality tasks"
36
+ task :quality => [:cane, :style, :stats]
37
+
38
+ require "yard"
39
+ YARD::Rake::YardocTask.new
40
+
41
+ desc "Generate gem dependency graph"
42
+ task :viz do
43
+ Bundler.with_clean_env do
44
+ sh "bundle viz --without test development guard " \
45
+ "--requirements --version"
46
+ end
47
+ end
48
+
49
+ task :default => [:test, :quality]
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "winrm/transport"
5
+
6
+ require "pry"
7
+ Pry.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require "winrm/transport/version"
20
+
21
+ module WinRM
22
+
23
+ # WinRM transport logic for re-using remote shells and uploading files.
24
+ #
25
+ # @author Fletcher Nichol <fnichol@nichol.ca>
26
+ module Transport
27
+ end
28
+ end
@@ -0,0 +1,217 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Matt Wrock (<matt@mattwrock.com>)
4
+ #
5
+ # Copyright (C) 2014, Matt Wrock
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require "winrm/transport/logging"
20
+
21
+ module WinRM
22
+
23
+ module Transport
24
+
25
+ # Object which can execute multiple commands and Powershell scripts in
26
+ # one shared remote shell session. The maximum number of commands per
27
+ # shell is determined by interrogating the remote host when the session
28
+ # is opened and the remote shell is automatically recycled before the
29
+ # threshold is reached.
30
+ #
31
+ # @author Matt Wrock <matt@mattwrock.com>
32
+ # @author Fletcher Nichol <fnichol@nichol.ca>
33
+ class CommandExecutor
34
+
35
+ include Logging
36
+
37
+ # @return [Integer,nil] the safe maximum number of commands that can
38
+ # be executed in one remote shell session, or `nil` if the
39
+ # threshold has not yet been determined
40
+ attr_reader :max_commands
41
+
42
+ # @return [String,nil] the identifier for the current open remote
43
+ # shell session, or `nil` if the session is not open
44
+ attr_reader :shell
45
+
46
+ # Creates a CommandExecutor given a `WinRM::WinRMWebService` object.
47
+ #
48
+ # @param service [WinRM::WinRMWebService] a winrm web service object
49
+ # @param logger [#debug,#info] an optional logger/ui object that
50
+ # responds to `#debug` and `#info` (default: `nil`)
51
+ # @param closer [ShellCloser] an optional object to automatically
52
+ # close the active open remote shell when CommandExecutor garbarge
53
+ # collects
54
+ def initialize(service, logger = nil, closer = nil)
55
+ @service = service
56
+ @logger = logger
57
+ @closer = closer
58
+ @command_count = 0
59
+ end
60
+
61
+ # Closes the open remote shell session. This method can be called
62
+ # multiple times, even if there is no open session.
63
+ def close
64
+ return if shell.nil?
65
+
66
+ service.close_shell(shell)
67
+ remove_finalizer
68
+ @shell = nil
69
+ end
70
+
71
+ # Opens a remote shell session for reuse. The maxiumum
72
+ # command-per-shell threshold is also determined the first time this
73
+ # method is invoked and cached for later invocations.
74
+ #
75
+ # @return [String] the remote shell session indentifier
76
+ def open
77
+ close
78
+ @shell = service.open_shell
79
+ add_finalizer(shell)
80
+ @command_count = 0
81
+ determine_max_commands unless max_commands
82
+ shell
83
+ end
84
+
85
+ # Runs a CMD command.
86
+ #
87
+ # @param command [String] the command to run on the remote system
88
+ # @param arguments [Array<String>] arguments to the command
89
+ # @yield [stdout, stderr] yields more live access the standard
90
+ # output and standard error streams as they are returns, if
91
+ # streaming behavior is desired
92
+ # @return [WinRM::Output] output object with stdout, stderr, and
93
+ # exit code
94
+ def run_cmd(command, arguments = [], &block)
95
+ reset if command_count_exceeded?
96
+ ensure_open_shell!
97
+
98
+ @command_count += 1
99
+ result = nil
100
+ service.run_command(shell, command, arguments) do |command_id|
101
+ result = service.get_command_output(shell, command_id, &block)
102
+ end
103
+ result
104
+ end
105
+
106
+ # Run a Powershell script that resides on the local box.
107
+ #
108
+ # @param script_file [IO,String] an IO reference for reading the
109
+ # Powershell script or the actual file contents
110
+ # @yield [stdout, stderr] yields more live access the standard
111
+ # output and standard error streams as they are returns, if
112
+ # streaming behavior is desired
113
+ # @return [WinRM::Output] output object with stdout, stderr, and
114
+ # exit code
115
+ def run_powershell_script(script_file, &block)
116
+ # this code looks overly compact in an attempt to limit local
117
+ # variable assignments that may contain large strings and
118
+ # consequently bloat the Ruby VM
119
+ run_cmd(
120
+ "powershell",
121
+ [
122
+ "-encodedCommand",
123
+ ::WinRM::PowershellScript.new(
124
+ script_file.is_a?(IO) ? script_file.read : script_file
125
+ ).encoded
126
+ ],
127
+ &block
128
+ )
129
+ end
130
+
131
+ private
132
+
133
+ # @return [Integer] the default maximum number of commands which can be
134
+ # executed in one remote shell session on "older" versions of Windows
135
+ # @api private
136
+ LEGACY_LIMIT = 15
137
+
138
+ # @return [Integer] the default maximum number of commands which can be
139
+ # executed in one remote shell session on "modern" versions of Windows
140
+ # @api private
141
+ MODERN_LIMIT = 1500
142
+
143
+ # @return [String] the PowerShell command used to determine the version
144
+ # of Windows
145
+ # @api private
146
+ PS1_OS_VERSION = "[environment]::OSVersion.Version.tostring()".freeze
147
+
148
+ # @return [Integer] the number of executed commands on the remote
149
+ # shell session
150
+ # @api private
151
+ attr_accessor :command_count
152
+
153
+ # @return [#debug,#info] the logger
154
+ # @api private
155
+ attr_reader :logger
156
+
157
+ # @return [WinRM::WinRMWebService] a WinRM web service object
158
+ # @api private
159
+ attr_reader :service
160
+
161
+ # Creates a finalizer for this connection which will close the open
162
+ # remote shell session when the object is garabage collected or on
163
+ # Ruby VM shutdown.
164
+ #
165
+ # @param shell_id [String] the remote shell identifier
166
+ # @api private
167
+ def add_finalizer(shell_id)
168
+ ObjectSpace.define_finalizer(self, @closer.for(shell_id)) if @closer
169
+ end
170
+
171
+ # @return [true,false] whether or not the number of exeecuted commands
172
+ # have exceeded the maxiumum threshold
173
+ # @api private
174
+ def command_count_exceeded?
175
+ command_count > max_commands.to_i
176
+ end
177
+
178
+ # Ensures that there is an open remote shell session.
179
+ #
180
+ # @raise [WinRM::WinRMError] if there is no open shell
181
+ # @api private
182
+ def ensure_open_shell!
183
+ if shell.nil?
184
+ raise ::WinRM::WinRMError, "#{self.class}#open must be called " \
185
+ "before any run methods are invoked"
186
+ end
187
+ end
188
+
189
+ # Determines the safe maximum number of commands that can be executed
190
+ # on a remote shell session by interrogating the remote host.
191
+ #
192
+ # @api private
193
+ def determine_max_commands
194
+ os_version = run_powershell_script(PS1_OS_VERSION).stdout.chomp
195
+ @max_commands = os_version < "6.2" ? LEGACY_LIMIT : MODERN_LIMIT
196
+ @max_commands -= 2 # to be safe
197
+ end
198
+
199
+ # Removes any finalizers for this connection.
200
+ #
201
+ # @api private
202
+ def remove_finalizer
203
+ ObjectSpace.undefine_finalizer(self) if @closer
204
+ end
205
+
206
+ # Closes the remote shell session and opens a new one.
207
+ #
208
+ # @api private
209
+ def reset
210
+ debug {
211
+ "Resetting WinRM shell (Max command limit is #{max_commands})"
212
+ }
213
+ open
214
+ end
215
+ end
216
+ end
217
+ end