train 1.4.21 → 1.4.22

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c4862d3b58025d9911ae676ff682f54a09865ebb909549781c6b830c0088ff1
4
- data.tar.gz: fb096faa3df3d6676ac4cf9e29a0c6da2d7b4ea45d81796e2a4012afa037fe23
3
+ metadata.gz: 455f5a9ff1827140f4f890be1b536a30261120db86836ee266b15eb418fe2900
4
+ data.tar.gz: e4d8c9dba2799532849ed9699cf11e813ec44669abc519f5e52de9ec9c5bd507
5
5
  SHA512:
6
- metadata.gz: eca750c91b7bb535029cf29b03f240c7bb25c119438ec11480f113e98d1989d447422d5786b2980b544a722e30169a0ce305248f349386b82462ad0fc35e3645
7
- data.tar.gz: 8a1857619fca8f3289b497a1fc49afc02c6e5b1296fc883d115ffe7bb4247d33b843ad48c9e102389cbcdef47bca8deb2fa9ef5931bb6cfc45e1398dfbe1fb98
6
+ metadata.gz: 8403b5941b3596a6aff77c830332e21f4b9a878eeb65b39c84fc598467060dae99b69c7968f9697379534d2fd5867b990343783a971658b257a21110ceeca044
7
+ data.tar.gz: 0dd9693a229a54b80c6bdaf9fe91f9d80ef3809bd2d860f5fa77ca7438f349df3d6951a0516057672651942251fa1eea432570774172b2b9afe3b49f100b973b
data/CHANGELOG.md CHANGED
@@ -1,19 +1,25 @@
1
- <!-- latest_release 1.4.21 -->
2
- ## [v1.4.21](https://github.com/inspec/train/tree/v1.4.21) (2018-07-05)
1
+ <!-- latest_release 1.4.22 -->
2
+ ## [v1.4.22](https://github.com/inspec/train/tree/v1.4.22) (2018-07-12)
3
3
 
4
4
  #### Merged Pull Requests
5
- - Modify `WindowsPipeRunner` stderr to use String [#320](https://github.com/inspec/train/pull/320) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
5
+ - Add VMware transport [#321](https://github.com/inspec/train/pull/321) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
6
6
  <!-- latest_release -->
7
7
 
8
- <!-- release_rollup since=1.4.19 -->
9
- ### Changes since 1.4.19 release
8
+ <!-- release_rollup since=1.4.21 -->
9
+ ### Changes since 1.4.21 release
10
10
 
11
11
  #### Merged Pull Requests
12
- - Modify `WindowsPipeRunner` stderr to use String [#320](https://github.com/inspec/train/pull/320) ([jerryaldrichiii](https://github.com/jerryaldrichiii)) <!-- 1.4.21 -->
13
- - Remove the delivery cookbook [#317](https://github.com/inspec/train/pull/317) ([tas50](https://github.com/tas50)) <!-- 1.4.20 -->
12
+ - Add VMware transport [#321](https://github.com/inspec/train/pull/321) ([jerryaldrichiii](https://github.com/jerryaldrichiii)) <!-- 1.4.22 -->
14
13
  <!-- release_rollup -->
15
14
 
16
15
  <!-- latest_stable_release -->
16
+ ## [v1.4.21](https://github.com/inspec/train/tree/v1.4.21) (2018-07-05)
17
+
18
+ #### Merged Pull Requests
19
+ - Remove the delivery cookbook [#317](https://github.com/inspec/train/pull/317) ([tas50](https://github.com/tas50))
20
+ - Modify `WindowsPipeRunner` stderr to use String [#320](https://github.com/inspec/train/pull/320) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
21
+ <!-- latest_stable_release -->
22
+
17
23
  ## [v1.4.19](https://github.com/inspec/train/tree/v1.4.19) (2018-06-29)
18
24
 
19
25
  #### Merged Pull Requests
@@ -21,7 +27,6 @@
21
27
  - Adding proper bastion support [#310](https://github.com/inspec/train/pull/310) ([frezbo](https://github.com/frezbo))
22
28
  - Remove github_changelog_generator [#313](https://github.com/inspec/train/pull/313) ([tas50](https://github.com/tas50))
23
29
  - Remove the deploy config from Travis [#315](https://github.com/inspec/train/pull/315) ([tas50](https://github.com/tas50))
24
- <!-- latest_stable_release -->
25
30
 
26
31
  ## [v1.4.15](https://github.com/inspec/train/tree/v1.4.15) (2018-06-14)
27
32
 
data/README.md CHANGED
@@ -22,6 +22,7 @@ Train supports:
22
22
  * Mock (for testing and debugging)
23
23
  * AWS as an API
24
24
  * Azure as an API
25
+ * VMware via PowerCLI
25
26
 
26
27
  # Examples
27
28
 
@@ -79,6 +80,20 @@ require 'train'
79
80
  train = Train.create('aws')
80
81
  ```
81
82
 
83
+ **VMware**
84
+
85
+ ```ruby
86
+ require 'train'
87
+ Train.create('vmware', viserver: '10.0.0.10', user: 'demouser', password: 'securepassword')
88
+ ```
89
+
90
+ You may also use environment variables by setting `VISERVER`, `VISERVER__USERNAME`, and `VISERVER_PASSWORD`
91
+
92
+ ```ruby
93
+ require 'train'
94
+ Train.create('vmware')
95
+ ```
96
+
82
97
  ## Configuration
83
98
 
84
99
  To get a list of available options for a plugin:
@@ -151,7 +166,7 @@ bundle exec ruby -I .\test\windows\ .\test\windows\local_test.rb
151
166
 
152
167
  Train is heavily based on the work of:
153
168
 
154
- * [test-kitchen](https://github.com/test-kitchen/test-kitchen)
169
+ * [test-kitchen](https://github.com/test-kitchen/test-kitchen)
155
170
 
156
171
  by [Fletcher Nichol](fnichol@nichol.ca)
157
172
  and [a great community of contributors](https://github.com/test-kitchen/test-kitchen/graphs/contributors)
@@ -11,6 +11,8 @@ module Train::Platforms::Detect::Specifications
11
11
  plat.name('aws').in_family('cloud')
12
12
  plat.name('azure').in_family('cloud')
13
13
  plat.name('gcp').in_family('cloud')
14
+ plat.name('vmware').in_family('cloud')
15
+
14
16
  plat.family('iaas').in_family('api')
15
17
  plat.name('oneview').in_family('iaas')
16
18
  end
@@ -0,0 +1,190 @@
1
+ # encoding: utf-8
2
+ require 'train/plugins'
3
+ require 'open3'
4
+ require 'ostruct'
5
+ require 'json'
6
+ require 'mkmf'
7
+
8
+ module Train::Transports
9
+ class VMware < Train.plugin(1)
10
+ name 'vmware'
11
+ option :viserver, default: ENV['VISERVER']
12
+ option :username, default: ENV['VISERVER_USERNAME']
13
+ option :password, default: ENV['VISERVER_PASSWORD']
14
+ option :insecure, default: false
15
+
16
+ def connection(_ = nil)
17
+ @connection ||= Connection.new(@options)
18
+ end
19
+
20
+ class Connection < BaseConnection # rubocop:disable ClassLength
21
+ POWERSHELL_PROMPT_REGEX = /PS\s.*> $/
22
+
23
+ def initialize(options)
24
+ super(options)
25
+
26
+ options[:viserver] = options[:viserver] || options[:host]
27
+ options[:username] = options[:username] || options[:user]
28
+
29
+ @username = options[:username]
30
+ @viserver = options[:viserver]
31
+ @session = nil
32
+ @stdout_buffer = ''
33
+ @stderr_buffer = ''
34
+
35
+ @powershell_binary = detect_powershell_binary
36
+
37
+ if @powershell_binary == :powershell
38
+ require 'train/transports/local'
39
+ @powershell = Train::Transports::Local::Connection.new(options)
40
+ end
41
+
42
+ if options[:insecure] == true
43
+ run_command_via_connection('Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$False')
44
+ end
45
+
46
+ @platform_details = {
47
+ release: "vmware-powercli-#{powercli_version}",
48
+ }
49
+
50
+ connect
51
+ end
52
+
53
+ def connect
54
+ login_command = "Connect-VIServer #{options[:viserver]} -User #{options[:username]} -Password #{options[:password]} | Out-Null"
55
+ result = run_command_via_connection(login_command)
56
+
57
+ if result.exit_status != 0
58
+ message = "Unable to connect to VIServer at #{options[:viserver]}. "
59
+ case result.stderr
60
+ when /Invalid server certificate/
61
+ message += 'Certification verification failed. Please use `--insecure` or set `Set-PowerCLIConfiguration -InvalidCertificateAction Ignore` in PowerShell'
62
+ when /incorrect user name or password/
63
+ message += 'Incorrect username or password'
64
+ else
65
+ message += result.stderr.gsub(/-Password .*\s/, '-Password REDACTED')
66
+ end
67
+
68
+ raise message
69
+ end
70
+ end
71
+
72
+ def local?
73
+ true
74
+ end
75
+
76
+ def platform
77
+ direct_platform('vmware', @platform_details)
78
+ end
79
+
80
+ def run_command_via_connection(cmd)
81
+ if @powershell_binary == :pwsh
82
+ result = parse_pwsh_output(cmd)
83
+
84
+ # Attach exit status to result
85
+ exit_status = parse_pwsh_output('echo $?').stdout.chomp
86
+ result.exit_status = exit_status == 'True' ? 0 : 1
87
+
88
+ result
89
+ else
90
+ @powershell.run_command(cmd)
91
+ end
92
+ end
93
+
94
+ def unique_identifier
95
+ uuid_command = '(Get-VMHost | Get-View).hardware.systeminfo.uuid'
96
+ run_command_via_connection(uuid_command).stdout.chomp
97
+ end
98
+
99
+ def uri
100
+ "vmware://#{@username}@#{@viserver}"
101
+ end
102
+
103
+ private
104
+
105
+ def detect_powershell_binary
106
+ if find_executable0('pwsh')
107
+ :pwsh
108
+ elsif find_executable0('powershell')
109
+ :powershell
110
+ else
111
+ raise 'Cannot find PowerShell binary, is `pwsh` installed?'
112
+ end
113
+ end
114
+
115
+ # Read from stdout pipe until prompt is received
116
+ def flush_stdout(pipe)
117
+ while @stdout_buffer !~ POWERSHELL_PROMPT_REGEX
118
+ @stdout_buffer += pipe.read_nonblock(1)
119
+ end
120
+ @stdout_buffer
121
+ rescue IO::EAGAINWaitReadable
122
+ # We cannot know when the stdout pipe is finished so we keep reading
123
+ retry
124
+ ensure
125
+ @stdout_buffer = ''
126
+ end
127
+
128
+ # This must be called after `flush_stdout` to ensure buffer is full
129
+ def flush_stderr(pipe)
130
+ loop do
131
+ @stderr_buffer += pipe.read_nonblock(1)
132
+ end
133
+ rescue IO::EAGAINWaitReadable
134
+ # If `flush_stderr` is ran after reading stdout we know that all of
135
+ # stderr is in the pipe. Thus, we can return the buffer once the pipe
136
+ # is unreadable.
137
+ @stderr_buffer
138
+ ensure
139
+ @stderr_buffer = ''
140
+ end
141
+
142
+ def parse_pwsh_output(cmd)
143
+ session.stdin.puts(cmd)
144
+
145
+ stdout = flush_stdout(session.stdout)
146
+
147
+ # Remove stdin from stdout (including trailing newline)
148
+ stdout.slice!(0, cmd.length+1)
149
+
150
+ # Remove prompt from stdout
151
+ stdout.gsub!(POWERSHELL_PROMPT_REGEX, '')
152
+
153
+ # Grab stderr
154
+ stderr = flush_stderr(session.stderr)
155
+
156
+ CommandResult.new(
157
+ stdout,
158
+ stderr,
159
+ nil # exit_status is attached in `run_command_via_connection`
160
+ )
161
+ end
162
+
163
+ def powercli_version
164
+ version_command = '[string](Get-Module -Name VMware.PowerCLI -ListAvailable | Select -ExpandProperty Version)'
165
+ result = run_command_via_connection(version_command)
166
+ if result.stdout.empty? || result.exit_status != 0
167
+ raise 'Unable to determine PowerCLI Module version, is it installed?'
168
+ end
169
+
170
+ result.stdout.chomp
171
+ end
172
+
173
+ def session
174
+ return @session unless @session.nil?
175
+
176
+ stdin, stdout, stderr = Open3.popen3('pwsh')
177
+
178
+ # Remove leading prompt and intro text
179
+ flush_stdout(stdout)
180
+
181
+ @session = OpenStruct.new
182
+ @session.stdin = stdin
183
+ @session.stdout = stdout
184
+ @session.stderr = stderr
185
+
186
+ @session
187
+ end
188
+ end
189
+ end
190
+ end
data/lib/train/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  # Author:: Dominik Richter (<dominik.richter@gmail.com>)
4
4
 
5
5
  module Train
6
- VERSION = '1.4.21'.freeze
6
+ VERSION = '1.4.22'.freeze
7
7
  end
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+
4
+ describe 'Train::Transports::VMware::Connection' do
5
+ def add_stubs(stub_options)
6
+ Train::Transports::VMware::Connection.any_instance
7
+ .stubs(:detect_powershell_binary)
8
+ .returns(stub_options[:powershell_binary] || :pwsh)
9
+ Train::Transports::VMware::Connection.any_instance
10
+ .stubs(:powercli_version)
11
+ .returns('10.1.1.8827525')
12
+ Train::Transports::VMware::Connection.any_instance
13
+ .stubs(:run_command_via_connection)
14
+ .with('(Get-VMHost | Get-View).hardware.systeminfo.uuid')
15
+ .returns(stub_options[:mock_uuid_result] || nil)
16
+ if stub_options[:mock_connect_result]
17
+ Train::Transports::VMware::Connection.any_instance
18
+ .expects(:run_command_via_connection)
19
+ .with('Connect-VIServer 10.0.0.10 -User testuser -Password supersecurepassword | Out-Null')
20
+ .returns(stub_options[:mock_connect_result])
21
+ else
22
+ Train::Transports::VMware::Connection.any_instance
23
+ .stubs(:connect)
24
+ .returns(nil)
25
+ end
26
+ end
27
+
28
+ def create_transport(options = {})
29
+ ENV['VISERVER'] = '10.0.0.10'
30
+ ENV['VISERVER_USERNAME'] = 'testuser'
31
+ ENV['VISERVER_PASSWORD'] = 'supersecurepassword'
32
+
33
+ # Need to require this here as it captures the ENV variables on load
34
+ require 'train/transports/vmware'
35
+ add_stubs(options[:stub_options] || {})
36
+ Train::Transports::VMware.new(options[:transport_options])
37
+ end
38
+
39
+ describe '#initialize' do
40
+ it 'defaults to ENV options' do
41
+ options = create_transport.connection.instance_variable_get(:@options)
42
+ options[:viserver].must_equal '10.0.0.10'
43
+ options[:username].must_equal 'testuser'
44
+ options[:password].must_equal 'supersecurepassword'
45
+ options[:insecure].must_equal false
46
+ end
47
+
48
+ it 'allows for overriding options' do
49
+ transport = create_transport(
50
+ transport_options: {
51
+ viserver: '10.1.1.1',
52
+ username: 'anotheruser',
53
+ password: 'notsecurepassword',
54
+ insecure: false,
55
+ }
56
+ )
57
+ options = transport.connection.instance_variable_get(:@options)
58
+ options[:viserver].must_equal '10.1.1.1'
59
+ options[:username].must_equal 'anotheruser'
60
+ options[:password].must_equal 'notsecurepassword'
61
+ options[:insecure].must_equal false
62
+ end
63
+
64
+ it 'ignores certificate validation if --insecure is used' do
65
+ Train::Transports::VMware::Connection.any_instance
66
+ .expects(:run_command_via_connection)
67
+ .with('Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$False')
68
+ .returns(nil)
69
+ transport = create_transport(transport_options: { insecure: true })
70
+ options = transport.connection.instance_variable_get(:@options)
71
+ options[:insecure].must_equal true
72
+ end
73
+
74
+ it 'uses the Local connection when Windows PowerShell is found' do
75
+ require 'train/transports/local'
76
+ Train::Transports::Local::Connection.expects(:new)
77
+ create_transport(
78
+ stub_options: {
79
+ powershell_binary: :powershell
80
+ }
81
+ ).connection
82
+ end
83
+ end
84
+
85
+ describe '#connect' do
86
+ def mock_connect_result(stderr, exit_status)
87
+ OpenStruct.new(stderr: stderr, exit_status: exit_status)
88
+ end
89
+
90
+ it 'raises certificate error when stderr matches regular expression' do
91
+ e = proc {
92
+ create_transport(
93
+ stub_options: {
94
+ mock_connect_result: mock_connect_result(
95
+ 'Invalid server certificate', 1
96
+ )
97
+ }
98
+ ).connection
99
+ }.must_raise(RuntimeError)
100
+ e.message.must_match /Unable to connect.*Please use `--insecure`/
101
+ end
102
+
103
+ it 'raises auth error when stderr matches regular expression' do
104
+ e = proc {
105
+ create_transport(
106
+ stub_options: {
107
+ mock_connect_result: mock_connect_result(
108
+ 'incorrect user name or password', 1
109
+ )
110
+ }
111
+ ).connection
112
+ }.must_raise(RuntimeError)
113
+ e.message.must_match /Unable to connect.*Incorrect username/
114
+ end
115
+
116
+ it 'redacts the password when an unspecified error is raised' do
117
+ e = proc {
118
+ create_transport(
119
+ stub_options: {
120
+ mock_connect_result: mock_connect_result(
121
+ 'something unexpected -Password supersecret -AnotherOption', 1
122
+ )
123
+ }
124
+ ).connection
125
+ }.must_raise(RuntimeError)
126
+ e.message.must_match /-Password REDACTED/
127
+ end
128
+ end
129
+
130
+ describe '#local' do
131
+ it 'returns true' do
132
+ create_transport.connection.local?.must_equal true
133
+ end
134
+ end
135
+
136
+ describe '#platform' do
137
+ it 'returns correct platform details' do
138
+ platform = create_transport.connection.platform
139
+ platform.clean_name.must_equal 'vmware'
140
+ platform.family_hierarchy.must_equal ['cloud', 'api']
141
+ platform.platform.must_equal(release: 'vmware-powercli-10.1.1.8827525')
142
+ platform.vmware?.must_equal true
143
+ end
144
+ end
145
+
146
+ describe '#unique_identifier' do
147
+ it 'returns the correct unique identifier' do
148
+ uuid = '1f261432-e23e-6911-841c-94c6911a02dd'
149
+ mock_uuid_result = OpenStruct.new(
150
+ stdout: uuid + "\n"
151
+ )
152
+ connection = create_transport(
153
+ stub_options: { mock_uuid_result: mock_uuid_result }
154
+ ).connection
155
+
156
+ connection.unique_identifier.must_equal(uuid)
157
+ end
158
+ end
159
+
160
+ describe '#uri' do
161
+ it 'returns the correct URI' do
162
+ create_transport.connection.uri.must_equal 'vmware://testuser@10.0.0.10'
163
+ end
164
+ end
165
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: train
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.21
4
+ version: 1.4.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-05 00:00:00.000000000 Z
11
+ date: 2018-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -271,6 +271,7 @@ files:
271
271
  - lib/train/transports/mock.rb
272
272
  - lib/train/transports/ssh.rb
273
273
  - lib/train/transports/ssh_connection.rb
274
+ - lib/train/transports/vmware.rb
274
275
  - lib/train/transports/winrm.rb
275
276
  - lib/train/transports/winrm_connection.rb
276
277
  - lib/train/version.rb
@@ -337,6 +338,7 @@ files:
337
338
  - test/unit/transports/local_test.rb
338
339
  - test/unit/transports/mock_test.rb
339
340
  - test/unit/transports/ssh_test.rb
341
+ - test/unit/transports/vmware_test.rb
340
342
  - test/unit/version_test.rb
341
343
  - test/windows/local_test.rb
342
344
  - test/windows/winrm_test.rb
@@ -429,6 +431,7 @@ test_files:
429
431
  - test/unit/transports/local_test.rb
430
432
  - test/unit/transports/mock_test.rb
431
433
  - test/unit/transports/ssh_test.rb
434
+ - test/unit/transports/vmware_test.rb
432
435
  - test/unit/version_test.rb
433
436
  - test/windows/local_test.rb
434
437
  - test/windows/winrm_test.rb