kitchen-google 2.8.0 → 3.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.
- checksums.yaml +4 -4
- data/lib/kitchen/driver/gce/windows_password.rb +288 -0
- data/lib/kitchen/driver/gce.rb +7 -7
- data/lib/kitchen/driver/gce_version.rb +1 -1
- metadata +74 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c6f8ac82e7287b315c4983229aad826ffc6dc2616d7b94c0f1887c3f3fbfdd4
|
|
4
|
+
data.tar.gz: 15e6f00245977ef5f27fa4c9fb09ee81e320534e2b2757ab6f812cb43f2d5e03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 813a82a35180d748b4d629b71e9ff9204a31a4482443e70e49830a1457ff545d7b7542ecfea6644991a864a5052f3cf3ef905276cfd171e0248b62327072d6f0
|
|
7
|
+
data.tar.gz: ddbdb11f5bf6420395f772189ce8145f65120c01aee09a065702c735fae641f75a809875713dd6a525eaf0d1ecb53724d5fe5fe48fc246ca21d9268393f0235f
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2016, Chef Software, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
|
|
18
|
+
require "base64" unless defined?(Base64)
|
|
19
|
+
require "date" unless defined?(DateTime)
|
|
20
|
+
require "json" unless defined?(JSON)
|
|
21
|
+
require "openssl" unless defined?(OpenSSL)
|
|
22
|
+
require "timeout" unless defined?(Timeout)
|
|
23
|
+
require "google/apis/compute_v1"
|
|
24
|
+
require "kitchen"
|
|
25
|
+
|
|
26
|
+
module Kitchen
|
|
27
|
+
module Driver
|
|
28
|
+
class Gce < Kitchen::Driver::Base
|
|
29
|
+
# Resets the password of a Windows account on a GCE instance.
|
|
30
|
+
#
|
|
31
|
+
# Google does not expose Windows credentials through the API. Instead, an
|
|
32
|
+
# agent inside the guest watches the instance's `windows-keys` metadata
|
|
33
|
+
# for an RSA public key, resets the named account, and writes the new
|
|
34
|
+
# password back to the instance's serial port, encrypted with that key.
|
|
35
|
+
#
|
|
36
|
+
# This class performs the client half of that exchange: it publishes a
|
|
37
|
+
# freshly generated public key, watches the serial port for the matching
|
|
38
|
+
# response, and decrypts the password with the private key, which never
|
|
39
|
+
# leaves this process.
|
|
40
|
+
#
|
|
41
|
+
# It borrows the driver's authorised API client, project, zone and
|
|
42
|
+
# operation handling rather than establishing its own.
|
|
43
|
+
#
|
|
44
|
+
# @see https://cloud.google.com/compute/docs/instances/windows/automate-pw-generation
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# WindowsPassword.new(driver,
|
|
48
|
+
# instance_name: "tk-win-1",
|
|
49
|
+
# email: "user@example.com",
|
|
50
|
+
# username: "Administrator").new_password #=> "hR2$k9..."
|
|
51
|
+
class WindowsPassword
|
|
52
|
+
# Seconds to wait for the in-guest agent when no timeout is configured.
|
|
53
|
+
#
|
|
54
|
+
# @return [Integer] the default timeout
|
|
55
|
+
DEFAULT_TIMEOUT = 120
|
|
56
|
+
|
|
57
|
+
# Serial port the Windows agent writes its response to.
|
|
58
|
+
#
|
|
59
|
+
# @return [Integer] the serial port number
|
|
60
|
+
SERIAL_PORT = 4
|
|
61
|
+
|
|
62
|
+
# Instance metadata key the agent watches for a public key.
|
|
63
|
+
#
|
|
64
|
+
# @return [String] the metadata key
|
|
65
|
+
METADATA_KEY = "windows-keys".freeze
|
|
66
|
+
|
|
67
|
+
# Size, in bits, of the RSA key generated for the exchange.
|
|
68
|
+
#
|
|
69
|
+
# @return [Integer] the key size
|
|
70
|
+
KEY_SIZE = 2048
|
|
71
|
+
|
|
72
|
+
# How long, in seconds, the published key remains valid.
|
|
73
|
+
#
|
|
74
|
+
# @return [Integer] the key lifetime
|
|
75
|
+
KEY_TTL = 300
|
|
76
|
+
|
|
77
|
+
# @return [Kitchen::Driver::Gce] the driver this request runs through
|
|
78
|
+
attr_reader :driver
|
|
79
|
+
|
|
80
|
+
# @return [String] the GCE instance whose password is being reset
|
|
81
|
+
attr_reader :instance_name
|
|
82
|
+
|
|
83
|
+
# @return [String] the email address of the GCE user making the request
|
|
84
|
+
attr_reader :email
|
|
85
|
+
|
|
86
|
+
# @return [String] the Windows account being reset
|
|
87
|
+
attr_reader :username
|
|
88
|
+
|
|
89
|
+
# @return [Integer] seconds to wait for the in-guest agent
|
|
90
|
+
attr_reader :timeout
|
|
91
|
+
|
|
92
|
+
# @param driver [Kitchen::Driver::Gce] driver supplying the API client,
|
|
93
|
+
# project, zone, operation handling and logging
|
|
94
|
+
# @param instance_name [String] the GCE instance to reset a password on
|
|
95
|
+
# @param email [String] email address of the GCE user making the request
|
|
96
|
+
# @param username [String, nil] the Windows account, defaulting to
|
|
97
|
+
# `Administrator`
|
|
98
|
+
# @param timeout [Integer, nil] seconds to wait for the in-guest agent,
|
|
99
|
+
# defaulting to {DEFAULT_TIMEOUT}
|
|
100
|
+
# @raise [ArgumentError] if the instance name or email is missing
|
|
101
|
+
def initialize(driver, instance_name:, email:, username: nil, timeout: nil)
|
|
102
|
+
raise ArgumentError, "Instance name not specified" if instance_name.nil?
|
|
103
|
+
raise ArgumentError, "Email address of GCE user not specified" if email.nil?
|
|
104
|
+
|
|
105
|
+
@driver = driver
|
|
106
|
+
@instance_name = instance_name
|
|
107
|
+
@email = email
|
|
108
|
+
@username = username || "Administrator"
|
|
109
|
+
@timeout = (timeout || DEFAULT_TIMEOUT).to_i
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Runs the full exchange and returns the new password.
|
|
113
|
+
#
|
|
114
|
+
# @return [String] the plaintext password the agent generated
|
|
115
|
+
# @raise [RuntimeError] if the instance is missing, or the agent reports
|
|
116
|
+
# that it could not reset the password
|
|
117
|
+
# @raise [Timeout::Error] if the agent does not respond within {#timeout}
|
|
118
|
+
def new_password
|
|
119
|
+
publish_public_key
|
|
120
|
+
decrypt(await_response)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Replaces the instance's `windows-keys` metadata with our public key,
|
|
124
|
+
# leaving every other metadata entry untouched, and waits for the update
|
|
125
|
+
# to take effect.
|
|
126
|
+
#
|
|
127
|
+
# @return [void]
|
|
128
|
+
def publish_public_key
|
|
129
|
+
metadata = instance_metadata
|
|
130
|
+
items = Array(metadata.items).reject { |item| item.key == METADATA_KEY }
|
|
131
|
+
items << key_metadata_item
|
|
132
|
+
metadata.items = items
|
|
133
|
+
|
|
134
|
+
driver.debug("Publishing a Windows password key to #{instance_name} for #{username}")
|
|
135
|
+
driver.wait_for_operation(
|
|
136
|
+
driver.connection.set_instance_metadata(driver.project, driver.zone, instance_name, metadata)
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The instance's current metadata.
|
|
141
|
+
#
|
|
142
|
+
# @return [Google::Apis::ComputeV1::Metadata] the metadata
|
|
143
|
+
# @raise [RuntimeError] if the instance cannot be found
|
|
144
|
+
def instance_metadata
|
|
145
|
+
driver.server_instance(instance_name).metadata
|
|
146
|
+
rescue Google::Apis::ClientError
|
|
147
|
+
raise "Unable to locate instance #{instance_name} in project #{driver.project}, zone #{driver.zone}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The metadata entry describing the key exchange request.
|
|
151
|
+
#
|
|
152
|
+
# @return [Google::Apis::ComputeV1::Metadata::Item] the entry
|
|
153
|
+
def key_metadata_item
|
|
154
|
+
Google::Apis::ComputeV1::Metadata::Item.new(
|
|
155
|
+
key: METADATA_KEY,
|
|
156
|
+
value: key_request.to_json
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The payload the in-guest agent reads to perform the reset.
|
|
161
|
+
#
|
|
162
|
+
# @return [Hash] the request, in the shape the agent expects
|
|
163
|
+
def key_request
|
|
164
|
+
{
|
|
165
|
+
"userName" => username,
|
|
166
|
+
"modulus" => modulus,
|
|
167
|
+
"exponent" => exponent,
|
|
168
|
+
"email" => email,
|
|
169
|
+
"expireOn" => expiration,
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# When the published key stops being valid.
|
|
174
|
+
#
|
|
175
|
+
# @return [String] an RFC 3339 timestamp
|
|
176
|
+
def expiration
|
|
177
|
+
(Time.now + KEY_TTL).to_datetime.rfc3339
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Polls the serial port until the agent answers our key.
|
|
181
|
+
#
|
|
182
|
+
# @return [Hash] the agent's response
|
|
183
|
+
# @raise [Timeout::Error] if the agent does not respond in time
|
|
184
|
+
def await_response
|
|
185
|
+
Timeout.timeout(timeout) do
|
|
186
|
+
loop do
|
|
187
|
+
response = response_from_serial_port
|
|
188
|
+
return response unless response.nil?
|
|
189
|
+
|
|
190
|
+
driver.debug("No password response yet for #{instance_name}, waiting...")
|
|
191
|
+
sleep driver.refresh_rate
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
rescue Timeout::Error
|
|
195
|
+
raise Timeout::Error, "Timed out after #{timeout} seconds waiting for the GCE agent " \
|
|
196
|
+
"to reset the password for #{username} on #{instance_name}"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Scans the serial port output for a response matching our key.
|
|
200
|
+
#
|
|
201
|
+
# The port carries arbitrary boot logging, so every line that is not
|
|
202
|
+
# JSON, or is JSON but not an object, is skipped. The newest lines are
|
|
203
|
+
# examined first.
|
|
204
|
+
#
|
|
205
|
+
# @return [Hash, nil] the matching response, or nil if none is present
|
|
206
|
+
def response_from_serial_port
|
|
207
|
+
serial_port_output.to_s.lines.reverse_each do |line|
|
|
208
|
+
event = parse_event(line)
|
|
209
|
+
next if event.nil?
|
|
210
|
+
|
|
211
|
+
return event if event["modulus"] == modulus && event["exponent"] == exponent
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
nil
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# The current contents of the instance's serial port.
|
|
218
|
+
#
|
|
219
|
+
# @return [String, nil] the serial port output
|
|
220
|
+
def serial_port_output
|
|
221
|
+
driver.connection.get_instance_serial_port_output(
|
|
222
|
+
driver.project, driver.zone, instance_name, port: SERIAL_PORT
|
|
223
|
+
).contents
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Parses one line of serial port output.
|
|
227
|
+
#
|
|
228
|
+
# @param line [String] the line to parse
|
|
229
|
+
# @return [Hash, nil] the parsed object, or nil if the line is not a
|
|
230
|
+
# JSON object
|
|
231
|
+
def parse_event(line)
|
|
232
|
+
event = JSON.parse(line.strip)
|
|
233
|
+
event.is_a?(Hash) ? event : nil
|
|
234
|
+
rescue JSON::ParserError
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Decrypts the password from the agent's response.
|
|
239
|
+
#
|
|
240
|
+
# OpenSSL hands back binary-tagged bytes. The agent sends UTF-8, so the
|
|
241
|
+
# result is retagged rather than left as ASCII-8BIT, which would other-
|
|
242
|
+
# wise be written into the state file as a binary blob.
|
|
243
|
+
#
|
|
244
|
+
# @param response [Hash] the agent's response
|
|
245
|
+
# @return [String] the plaintext password, encoded as UTF-8
|
|
246
|
+
# @raise [RuntimeError] if the agent reported a failed reset
|
|
247
|
+
def decrypt(response)
|
|
248
|
+
unless response["passwordFound"]
|
|
249
|
+
raise "The GCE agent could not reset the password for #{username} on #{instance_name}"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
private_key.private_decrypt(
|
|
253
|
+
Base64.strict_decode64(response["encryptedPassword"]),
|
|
254
|
+
OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
|
255
|
+
).force_encoding(Encoding::UTF_8)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The private key for this exchange, which never leaves the process.
|
|
259
|
+
#
|
|
260
|
+
# @return [OpenSSL::PKey::RSA] the key pair
|
|
261
|
+
def private_key
|
|
262
|
+
@private_key ||= OpenSSL::PKey::RSA.new(KEY_SIZE)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# The public half of {#private_key}.
|
|
266
|
+
#
|
|
267
|
+
# @return [OpenSSL::PKey::RSA] the public key
|
|
268
|
+
def public_key
|
|
269
|
+
private_key.public_key
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# The public key's modulus, as the agent expects it.
|
|
273
|
+
#
|
|
274
|
+
# @return [String] the Base64-encoded big-endian modulus
|
|
275
|
+
def modulus
|
|
276
|
+
@modulus ||= Base64.strict_encode64(public_key.n.to_s(2))
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# The public key's exponent, as the agent expects it.
|
|
280
|
+
#
|
|
281
|
+
# @return [String] the Base64-encoded big-endian exponent
|
|
282
|
+
def exponent
|
|
283
|
+
@exponent ||= Base64.strict_encode64(public_key.e.to_s(2))
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
data/lib/kitchen/driver/gce.rb
CHANGED
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
# See the License for the specific language governing permissions and
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
|
|
19
|
-
require "gcewinpass"
|
|
20
19
|
require "google/apis/compute_v1"
|
|
21
20
|
require "kitchen"
|
|
22
21
|
require_relative "gce_version"
|
|
22
|
+
require_relative "gce/windows_password"
|
|
23
23
|
require "securerandom" unless defined?(SecureRandom)
|
|
24
24
|
require "timeout" unless defined?(Timeout)
|
|
25
25
|
|
|
@@ -437,6 +437,8 @@ module Kitchen
|
|
|
437
437
|
#
|
|
438
438
|
# @param server_name [String] the GCE instance name
|
|
439
439
|
# @return [void]
|
|
440
|
+
# @raise [RuntimeError] if the in-guest agent could not reset the password
|
|
441
|
+
# @raise [Timeout::Error] if the agent does not respond in time
|
|
440
442
|
def update_windows_password(server_name)
|
|
441
443
|
return unless winrm_transport?
|
|
442
444
|
|
|
@@ -444,15 +446,13 @@ module Kitchen
|
|
|
444
446
|
|
|
445
447
|
info("Resetting the Windows password for user #{username} on #{server_name}...")
|
|
446
448
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
zone: zone,
|
|
449
|
+
state[:password] = WindowsPassword.new(
|
|
450
|
+
self,
|
|
450
451
|
instance_name: server_name,
|
|
451
452
|
email: config[:email],
|
|
452
453
|
username: username,
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
state[:password] = GoogleComputeWindowsPassword.new(**opts).new_password
|
|
454
|
+
timeout: config[:winpass_timeout]
|
|
455
|
+
).new_password
|
|
456
456
|
|
|
457
457
|
info("Password reset complete on #{server_name}.")
|
|
458
458
|
end
|
metadata
CHANGED
|
@@ -1,63 +1,119 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-google
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Test Kitchen Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: google-apis-compute_v1
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '0.75'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '0.75'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: test-kitchen
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0
|
|
33
|
+
version: '3.0'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '5.0'
|
|
34
37
|
type: :runtime
|
|
35
38
|
prerelease: false
|
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
40
|
requirements:
|
|
38
41
|
- - ">="
|
|
39
42
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0
|
|
43
|
+
version: '3.0'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.0'
|
|
41
47
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
48
|
+
name: base64
|
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
|
44
50
|
requirements:
|
|
45
51
|
- - ">="
|
|
46
52
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
48
|
-
|
|
53
|
+
version: '0.1'
|
|
54
|
+
type: :runtime
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
49
59
|
- !ruby/object:Gem::Version
|
|
50
|
-
version: '
|
|
60
|
+
version: '0.1'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: date
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.2'
|
|
51
68
|
type: :runtime
|
|
52
69
|
prerelease: false
|
|
53
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
54
71
|
requirements:
|
|
55
72
|
- - ">="
|
|
56
73
|
- !ruby/object:Gem::Version
|
|
57
|
-
version:
|
|
58
|
-
|
|
74
|
+
version: '3.2'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: json
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
59
80
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '5
|
|
81
|
+
version: '2.5'
|
|
82
|
+
type: :runtime
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '2.5'
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: securerandom
|
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0.1'
|
|
96
|
+
type: :runtime
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0.1'
|
|
103
|
+
- !ruby/object:Gem::Dependency
|
|
104
|
+
name: timeout
|
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.2'
|
|
110
|
+
type: :runtime
|
|
111
|
+
prerelease: false
|
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.2'
|
|
61
117
|
description: A Test-Kitchen driver for Google Compute Engine
|
|
62
118
|
email:
|
|
63
119
|
- help@sous-chefs.org
|
|
@@ -67,6 +123,7 @@ extra_rdoc_files: []
|
|
|
67
123
|
files:
|
|
68
124
|
- LICENSE
|
|
69
125
|
- lib/kitchen/driver/gce.rb
|
|
126
|
+
- lib/kitchen/driver/gce/windows_password.rb
|
|
70
127
|
- lib/kitchen/driver/gce_version.rb
|
|
71
128
|
homepage: https://github.com/test-kitchen/kitchen-google
|
|
72
129
|
licenses:
|