train 1.4.21 → 1.4.22

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