knife-google 2.1.0 → 2.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c84f9e43200cfc6b1555609603026d09b38810a0
4
- data.tar.gz: 9f6abcc11414666ce8a37b8fa2ffc5c1dcd177c5
3
+ metadata.gz: 7fb67127a3cc6cd2859f32160e8b08612eeff968
4
+ data.tar.gz: 99833b9f9980664e97fb5e2d243fd5ed541f7ee2
5
5
  SHA512:
6
- metadata.gz: e72acb4b9f20c123a7da556700216f9c21d4469623585c9a5d9f9266160c5c21e1f9aaf3a57777a3c715973d156dd65ad675a3b355edd99424314221ac673afa
7
- data.tar.gz: 817ef80036e1a6dce5a2a8400a54d54b2caa2f3c102cc60665427c0509dacaf2186f493c503681c3c0a50d7aeffc1e22a12e6a4f3208fe14a0573e10f14f2b65
6
+ metadata.gz: bf712c5c7f3b7cc0a4086b04cf5d7db3c27c9f110bfcb56f8e5234faf5688e05711a1fd59d000de4035b8621c7787ff9a87195024fd5122ddc196978b668dd66
7
+ data.tar.gz: 051a36a7ce5831cbedf78dbcf0179d779a8e18aab4fe9ae7d720443172b7da1df1b132f444ac938927cb06b50b768484e724c98a10f597b07d52b74ba0d42652
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # knife-google Change Log
2
2
 
3
- ## v2.1.0 (2016-03-04
3
+ ## v2.2.0 (2016-03-17)
4
+
5
+ * [pr#102](https://github.com/chef/knife-google/pull/102) Added support for preemptible instances
6
+ * [pr#103](https://github.com/chef/knife-google/pull/103) Added support for deploying instances on subnetworks
7
+ * [pr#103](https://github.com/chef/knife-google/pull/104) Added gcloud-style image aliases (i.e. image "centos-7" will get you the latest CentOS 7 disk image)
8
+
9
+ ## v2.1.0 (2016-03-04)
4
10
  * [pr#99](https://github.com/chef/knife-google/pull/99) Support service account scope aliases, similar to the gcloud SDK
5
11
  * [pr#101](https://github.com/chef/knife-google/pull/101) Set the application name and version on the API object for proper user-agent formatting
6
12
 
data/README.md CHANGED
@@ -185,7 +185,8 @@ See the [SSH Keys](#ssh-keys) section for more information.
185
185
  * **INSTANCE_NAME**: required. The name to use when creating the instance.
186
186
  * **--gce-machine-type**: required. The machine type to use when creating the server, such as `n1-standard-2` or `n1-highcpu-2-d`.
187
187
  * **--gce-network**: The name of the network to which your instance will be attached. Defaults to "default".
188
- * **--gce-image**: required. The name of the disk image to use when creating the server. knife-google will search your current project for this disk image. If the image cannot be found but looks like a common public image, the public image project will be searched as well.
188
+ * **--gce-subnet**: The name of the subnet to which your instance will be attached. Only applies to custom networks.
189
+ * **--gce-image**: required. The name of the disk image to use when creating the server. knife-google will search your current project for this disk image. If the image cannot be found but looks like a common public image, the public image project will be searched as well. Additionally, this parameter supports the same image aliases that `gcloud compute instances create` supports. See the output of `gcloud compute instances create --help` for a full list of aliases.
189
190
  * Example: if you supply a gce-image of `centos-7-v20160219`, knife-google will first look for an image with that name in your currently-configured project. If it cannot be found, it will look in the `centos-cloud` project.
190
191
  * This behavior can be overridden with the `--gce-image-project` parameter.
191
192
  * **--gce-image-project**: optional. The name of the GCP project that contains the image specified with the `--gce-image` flag. If this is specified, knife-google will not search any known public projects for your image.
@@ -52,6 +52,27 @@ class Chef::Knife::Cloud
52
52
  "userinfo-email" => "userinfo.email",
53
53
  }
54
54
 
55
+ IMAGE_ALIAS_MAP = {
56
+ "centos-6" => { project: "centos-cloud", prefix: "centos-6" },
57
+ "centos-7" => { project: "centos-cloud", prefix: "centos-7" },
58
+ "container-vm" => { project: "google-containers", prefix: "container-vm" },
59
+ "coreos" => { project: "coreos-cloud", prefix: "coreos-stable" },
60
+ "debian-7" => { project: "debian-cloud", prefix: "debian-7-wheezy" },
61
+ "debian-7-backports" => { project: "debian-cloud", prefix: "backports-debian-7-wheezy" },
62
+ "debian-8" => { project: "debian-cloud", prefix: "debian-8-jessie" },
63
+ "opensuse-13" => { project: "opensuse-cloud", prefix: "opensuse-13" },
64
+ "rhel-6" => { project: "rhel-cloud", prefix: "rhel-6" },
65
+ "rhel-7" => { project: "rhel-cloud", prefix: "rhel-7" },
66
+ "sles-11" => { project: "suse-cloud", prefix: "sles-11" },
67
+ "sles-12" => { project: "suse-cloud", prefix: "sles-12" },
68
+ "ubuntu-12-04" => { project: "ubuntu-os-cloud", prefix: "ubuntu-1204-precise" },
69
+ "ubuntu-14-04" => { project: "ubuntu-os-cloud", prefix: "ubuntu-1404-trusty" },
70
+ "ubuntu-15-04" => { project: "ubuntu-os-cloud", prefix: "ubuntu-1504-vivid" },
71
+ "ubuntu-15-10" => { project: "ubuntu-os-cloud", prefix: "ubuntu-1510-wily" },
72
+ "windows-2008-r2" => { project: "windows-cloud", prefix: "windows-server-2008-r2" },
73
+ "windows-2012-r2" => { project: "windows-cloud", prefix: "windows-server-2012-r2" },
74
+ }
75
+
55
76
  def initialize(options = {})
56
77
  @project = options[:project]
57
78
  @zone = options[:zone]
@@ -186,12 +207,13 @@ class Chef::Knife::Cloud
186
207
  def validate_server_create_options!(options)
187
208
  raise "Invalid machine type: #{options[:machine_type]}" unless valid_machine_type?(options[:machine_type])
188
209
  raise "Invalid network: #{options[:network]}" unless valid_network?(options[:network])
210
+ raise "Invalid subnet: #{options[:subnet]}" if options[:subnet] && !valid_subnet?(options[:subnet])
189
211
  raise "Invalid Public IP setting: #{options[:public_ip]}" unless valid_public_ip_setting?(options[:public_ip])
190
- raise "Invalid image: #{options[:image]} - check your image name, or set an image project if needed" if image_search_for(options[:image], options[:image_project]).nil?
212
+ raise "Invalid image: #{options[:image]} - check your image name, or set an image project if needed" if boot_disk_source_image(options[:image], options[:image_project]).nil?
191
213
  end
192
214
 
193
- def check_api_call(&block)
194
- block.call
215
+ def check_api_call
216
+ yield
195
217
  rescue Google::Apis::ClientError
196
218
  false
197
219
  else
@@ -208,6 +230,11 @@ class Chef::Knife::Cloud
208
230
  check_api_call { connection.get_network(project, network) }
209
231
  end
210
232
 
233
+ def valid_subnet?(subnet)
234
+ return false if subnet.nil?
235
+ check_api_call { connection.get_subnetwork(project, region, subnet) }
236
+ end
237
+
211
238
  def image_exist?(image_project, image_name)
212
239
  check_api_call { connection.get_image(image_project, image_name) }
213
240
  end
@@ -232,6 +259,10 @@ class Chef::Knife::Cloud
232
259
  true
233
260
  end
234
261
 
262
+ def region
263
+ @region ||= connection.get_zone(project, zone).region.split("/").last
264
+ end
265
+
235
266
  def instance_object_for(options)
236
267
  inst_obj = Google::Apis::ComputeV1::Instance.new
237
268
  inst_obj.name = options[:name]
@@ -273,7 +304,7 @@ class Chef::Knife::Cloud
273
304
  params.disk_name = boot_disk_name_for(options)
274
305
  params.disk_size_gb = options[:boot_disk_size]
275
306
  params.disk_type = disk_type_url_for(boot_disk_type_for(options))
276
- params.source_image = image_search_for(options[:image], options[:image_project])
307
+ params.source_image = boot_disk_source_image(options[:image], options[:image_project])
277
308
 
278
309
  disk.initialize_params = params
279
310
  disk
@@ -283,21 +314,40 @@ class Chef::Knife::Cloud
283
314
  options[:boot_disk_ssd] ? "pd-ssd" : "pd-standard"
284
315
  end
285
316
 
317
+ def boot_disk_source_image(image, image_project)
318
+ @boot_disk_source ||= image_search_for(image, image_project)
319
+ end
320
+
286
321
  def image_search_for(image, image_project)
287
322
  # if the user provided an image_project, assume they want it, no questions asked
288
- return image_url_for(image_project, image) unless image_project.nil?
323
+ unless image_project.nil?
324
+ ui.msg("Searching project #{image_project} for image #{image}")
325
+ return image_url_for(image_project, image)
326
+ end
327
+
328
+ # no image project has been provided. Check to see if the image is a known alias.
329
+ alias_url = image_alias_url(image)
330
+ unless alias_url.nil?
331
+ ui.msg("image #{image} is a known alias - using image URL: #{alias_url}")
332
+ return alias_url
333
+ end
289
334
 
290
- # no image project has been provided. We'll first check the user's project for the image.
335
+ # Doesn't match an alias. Let's check the user's project for the image.
291
336
  url = image_url_for(project, image)
292
- return url unless url.nil?
337
+ unless url.nil?
338
+ ui.msg("Located image #{image} in project #{project} - using image URL: #{url}")
339
+ return url
340
+ end
293
341
 
294
342
  # Image not found in user's project. Is there a public project this image might exist in?
295
343
  public_project = public_project_for_image(image)
296
344
  if public_project
345
+ ui.msg("Searching public image project #{public_project} for image #{image}")
297
346
  return image_url_for(public_project, image)
298
347
  end
299
348
 
300
349
  # No image in user's project or public project, so it doesn't exist.
350
+ ui.error("Image search failed for image #{image} - no suitable image located")
301
351
  nil
302
352
  end
303
353
 
@@ -305,6 +355,22 @@ class Chef::Knife::Cloud
305
355
  return "projects/#{image_project}/global/images/#{image_name}" if image_exist?(image_project, image_name)
306
356
  end
307
357
 
358
+ def image_alias_url(image_alias)
359
+ return unless IMAGE_ALIAS_MAP.key?(image_alias)
360
+
361
+ image_project = IMAGE_ALIAS_MAP[image_alias][:project]
362
+ image_prefix = IMAGE_ALIAS_MAP[image_alias][:prefix]
363
+
364
+ latest_image = connection.list_images(image_project).items
365
+ .select { |image| image.name.start_with?(image_prefix) }
366
+ .sort { |a, b| a.name <=> b.name }
367
+ .last
368
+
369
+ return if latest_image.nil?
370
+
371
+ latest_image.self_link
372
+ end
373
+
308
374
  def boot_disk_name_for(options)
309
375
  options[:boot_disk_name].nil? ? options[:name] : options[:boot_disk_name]
310
376
  end
@@ -331,6 +397,7 @@ class Chef::Knife::Cloud
331
397
  def instance_network_interfaces_for(options)
332
398
  interface = Google::Apis::ComputeV1::NetworkInterface.new
333
399
  interface.network = network_url_for(options[:network])
400
+ interface.subnetwork = subnet_url_for(options[:subnet]) if options[:subnet]
334
401
  interface.access_configs = instance_access_configs_for(options[:public_ip])
335
402
 
336
403
  Array(interface)
@@ -351,10 +418,15 @@ class Chef::Knife::Cloud
351
418
  "projects/#{project}/global/networks/#{network}"
352
419
  end
353
420
 
421
+ def subnet_url_for(subnet)
422
+ "projects/#{project}/regions/#{region}/subnetworks/#{subnet}"
423
+ end
424
+
354
425
  def instance_scheduling_for(options)
355
426
  scheduling = Google::Apis::ComputeV1::Scheduling.new
356
- scheduling.automatic_restart = options[:auto_restart].to_s
427
+ scheduling.automatic_restart = options[:auto_restart].to_s
357
428
  scheduling.on_host_maintenance = migrate_setting_for(options[:auto_migrate])
429
+ scheduling.preemptible = options[:preemptible].to_s
358
430
 
359
431
  scheduling
360
432
  end
@@ -466,13 +538,13 @@ class Chef::Knife::Cloud
466
538
  items
467
539
  end
468
540
 
469
- def wait_for_status(requested_status, &block)
541
+ def wait_for_status(requested_status)
470
542
  last_status = ""
471
543
 
472
544
  begin
473
545
  Timeout.timeout(wait_time) do
474
546
  loop do
475
- item = block.call
547
+ item = yield
476
548
  current_status = item.status
477
549
 
478
550
  if current_status == requested_status
@@ -85,6 +85,12 @@ class Chef::Knife::Cloud
85
85
  boolean: true,
86
86
  default: true
87
87
 
88
+ option :preemptible,
89
+ long: "--[no-]gce-preemptible",
90
+ description: "Create the instance as a preemptible instance, allowing GCE to shut it down at any time. Defaults to false.",
91
+ boolean: true,
92
+ default: false
93
+
88
94
  option :can_ip_forward,
89
95
  long: "--[no-]gce-can-ip-forward",
90
96
  description: "Allow server network forwarding",
@@ -96,6 +102,10 @@ class Chef::Knife::Cloud
96
102
  description: "The network for this server; default is 'default'",
97
103
  default: "default"
98
104
 
105
+ option :subnet,
106
+ long: "--gce-subnet SUBNET",
107
+ description: "The name of the subnet in the network on which to deploy the instance"
108
+
99
109
  option :tags,
100
110
  short: "-T TAG1,TAG2,TAG3",
101
111
  long: "--gce-tags TAG1,TAG2,TAG3",
@@ -147,9 +157,11 @@ class Chef::Knife::Cloud
147
157
  image: locate_config_value(:image),
148
158
  image_project: locate_config_value(:image_project),
149
159
  network: locate_config_value(:network),
160
+ subnet: locate_config_value(:subnet),
150
161
  public_ip: locate_config_value(:public_ip),
151
- auto_migrate: locate_config_value(:auto_migrate),
152
- auto_restart: locate_config_value(:auto_restart),
162
+ auto_migrate: auto_migrate?,
163
+ auto_restart: auto_restart?,
164
+ preemptible: preemptible?,
153
165
  boot_disk_autodelete: locate_config_value(:boot_disk_autodelete),
154
166
  boot_disk_name: locate_config_value(:boot_disk_name),
155
167
  boot_disk_size: boot_disk_size,
@@ -177,6 +189,8 @@ class Chef::Knife::Cloud
177
189
  raise "Please provide your Google Cloud console email address via --gce-email. " \
178
190
  "It is required when resetting passwords on Windows hosts." if locate_config_value(:bootstrap_protocol) == "winrm" && locate_config_value(:gce_email).nil?
179
191
 
192
+ ui.warn("Auto-migrate disabled for preemptible instance") if preemptible? && locate_config_value(:auto_migrate)
193
+ ui.warn("Auto-restart disabled for preemptible instance") if preemptible? && locate_config_value(:auto_restart)
180
194
  super
181
195
  end
182
196
 
@@ -214,6 +228,18 @@ class Chef::Knife::Cloud
214
228
  locate_config_value(:gce_email)
215
229
  end
216
230
 
231
+ def preemptible?
232
+ locate_config_value(:preemptible)
233
+ end
234
+
235
+ def auto_migrate?
236
+ preemptible? ? false : locate_config_value(:auto_migrate)
237
+ end
238
+
239
+ def auto_restart?
240
+ preemptible? ? false : locate_config_value(:auto_restart)
241
+ end
242
+
217
243
  def ip_address_for_bootstrap
218
244
  ip = locate_config_value(:use_private_ip) ? private_ip_for(server) : public_ip_for(server)
219
245
 
@@ -14,7 +14,7 @@
14
14
  #
15
15
  module Knife
16
16
  module Google
17
- VERSION = "2.1.0".freeze
17
+ VERSION = "2.2.0".freeze
18
18
  MAJOR, MINOR, TINY = VERSION.split(".")
19
19
  end
20
20
  end
@@ -61,6 +61,7 @@ describe Chef::Knife::Cloud::GoogleService do
61
61
  before do
62
62
  service.ui = Chef::Knife::UI.new($stdout, $stderr, $stdin, {})
63
63
  allow(service.ui).to receive(:msg)
64
+ allow(service.ui).to receive(:error)
64
65
  allow(service).to receive(:connection).and_return(connection)
65
66
  end
66
67
 
@@ -189,6 +190,7 @@ describe Chef::Knife::Cloud::GoogleService do
189
190
  {
190
191
  machine_type: "test_type",
191
192
  network: "test_network",
193
+ subnet: "test_subnet",
192
194
  public_ip: "public_ip",
193
195
  image: "test_image",
194
196
  image_project: "test_image_project",
@@ -198,6 +200,7 @@ describe Chef::Knife::Cloud::GoogleService do
198
200
  before do
199
201
  allow(service).to receive(:valid_machine_type?).and_return(true)
200
202
  allow(service).to receive(:valid_network?).and_return(true)
203
+ allow(service).to receive(:valid_subnet?).and_return(true)
201
204
  allow(service).to receive(:valid_public_ip_setting?).and_return(true)
202
205
  allow(service).to receive(:image_search_for).and_return(true)
203
206
  end
@@ -216,6 +219,11 @@ describe Chef::Knife::Cloud::GoogleService do
216
219
  expect { service.validate_server_create_options!(options) }.to raise_error(RuntimeError)
217
220
  end
218
221
 
222
+ it "raises an exception if the network is not valid" do
223
+ expect(service).to receive(:valid_subnet?).with("test_subnet").and_return(false)
224
+ expect { service.validate_server_create_options!(options) }.to raise_error(RuntimeError)
225
+ end
226
+
219
227
  it "raises an exception if the public ip setting is not valid" do
220
228
  expect(service).to receive(:valid_public_ip_setting?).with("public_ip").and_return(false)
221
229
  expect { service.validate_server_create_options!(options) }.to raise_error(RuntimeError)
@@ -267,6 +275,20 @@ describe Chef::Knife::Cloud::GoogleService do
267
275
  end
268
276
  end
269
277
 
278
+ describe '#valid_subnet?' do
279
+ it "returns false if no subnet was specified" do
280
+ expect(service.valid_subnet?(nil)).to eq(false)
281
+ end
282
+
283
+ it "checks the network using check_api_call" do
284
+ expect(service).to receive(:region).and_return("test_region")
285
+ expect(connection).to receive(:get_subnetwork).with(project, "test_region", "test_subnet")
286
+ expect(service).to receive(:check_api_call).and_call_original
287
+
288
+ service.valid_subnet?("test_subnet")
289
+ end
290
+ end
291
+
270
292
  describe '#image_exist?' do
271
293
  it "checks the image using check_api_call" do
272
294
  expect(connection).to receive(:get_image).with("image_project", "image_name")
@@ -312,6 +334,14 @@ describe Chef::Knife::Cloud::GoogleService do
312
334
  end
313
335
  end
314
336
 
337
+ describe '#region' do
338
+ it "returns the region for a given zone" do
339
+ zone_obj = double("zone_obj", region: "/path/to/test_region")
340
+ expect(connection).to receive(:get_zone).with(project, zone).and_return(zone_obj)
341
+ expect(service.region).to eq("test_region")
342
+ end
343
+ end
344
+
315
345
  describe '#instance_object_for' do
316
346
  let(:instance_object) { double("instance_object") }
317
347
  let(:options) do
@@ -453,31 +483,44 @@ describe Chef::Knife::Cloud::GoogleService do
453
483
  end
454
484
 
455
485
  context "when the user does not supply an image project" do
456
- context "when the image exists in the user's project" do
457
- it "returns the image URL" do
458
- expect(service).to receive(:image_url_for).with(project, "test_image").and_return("image_url")
459
- expect(service.image_search_for("test_image", nil)).to eq("image_url")
486
+ context "when the image provided is an alias" do
487
+ it "returns the alias URL" do
488
+ expect(service).to receive(:image_alias_url).with("test_image").and_return("image_alias_url")
489
+ expect(service.image_search_for("test_image", nil)).to eq("image_alias_url")
460
490
  end
461
491
  end
462
492
 
463
- context "when the image does not exist in the user's project" do
493
+ context "when the image provided is not an alias" do
464
494
  before do
465
- expect(service).to receive(:image_url_for).with(project, "test_image").and_return(nil)
495
+ expect(service).to receive(:image_alias_url).and_return(nil)
466
496
  end
467
497
 
468
- context "when the image matches a known public project" do
469
- it "returns the image URL from the public project" do
470
- expect(service).to receive(:public_project_for_image).with("test_image").and_return("public_project")
471
- expect(service).to receive(:image_url_for).with("public_project", "test_image").and_return("image_url")
498
+ context "when the image exists in the user's project" do
499
+ it "returns the image URL" do
500
+ expect(service).to receive(:image_url_for).with(project, "test_image").and_return("image_url")
472
501
  expect(service.image_search_for("test_image", nil)).to eq("image_url")
473
502
  end
474
503
  end
475
504
 
476
- context "when the image does not match a known project" do
477
- it "returns nil" do
478
- expect(service).to receive(:public_project_for_image).with("test_image").and_return(nil)
479
- expect(service).not_to receive(:image_url_for)
480
- expect(service.image_search_for("test_image", nil)).to eq(nil)
505
+ context "when the image does not exist in the user's project" do
506
+ before do
507
+ expect(service).to receive(:image_url_for).with(project, "test_image").and_return(nil)
508
+ end
509
+
510
+ context "when the image matches a known public project" do
511
+ it "returns the image URL from the public project" do
512
+ expect(service).to receive(:public_project_for_image).with("test_image").and_return("public_project")
513
+ expect(service).to receive(:image_url_for).with("public_project", "test_image").and_return("image_url")
514
+ expect(service.image_search_for("test_image", nil)).to eq("image_url")
515
+ end
516
+ end
517
+
518
+ context "when the image does not match a known project" do
519
+ it "returns nil" do
520
+ expect(service).to receive(:public_project_for_image).with("test_image").and_return(nil)
521
+ expect(service).not_to receive(:image_url_for)
522
+ expect(service.image_search_for("test_image", nil)).to eq(nil)
523
+ end
481
524
  end
482
525
  end
483
526
  end
@@ -496,6 +539,50 @@ describe Chef::Knife::Cloud::GoogleService do
496
539
  end
497
540
  end
498
541
 
542
+ describe '#image_alias_url' do
543
+ context "when the image_alias is not a valid alias" do
544
+ it "returns nil" do
545
+ expect(service.image_alias_url("fake_alias")).to eq(nil)
546
+ end
547
+ end
548
+
549
+ context "when the image_alias is a valid alias" do
550
+ before do
551
+ allow(connection).to receive(:list_images).and_return(response)
552
+ end
553
+
554
+ context "when the response contains no images" do
555
+ let(:response) { double("response", items: []) }
556
+
557
+ it "returns nil" do
558
+ expect(service.image_alias_url("centos-7")).to eq(nil)
559
+ end
560
+ end
561
+
562
+ context "when the response contains images but none match the name" do
563
+ let(:image1) { double("image1", name: "centos-6") }
564
+ let(:image2) { double("image2", name: "centos-6") }
565
+ let(:image3) { double("image3", name: "ubuntu-14") }
566
+ let(:response) { double("response", items: [ image1, image2, image3 ]) }
567
+
568
+ it "returns nil" do
569
+ expect(service.image_alias_url("centos-7")).to eq(nil)
570
+ end
571
+ end
572
+
573
+ context "when the response contains images that match the name" do
574
+ let(:image1) { double("image1", name: "centos-7-v20160201", self_link: "image1_selflink") }
575
+ let(:image2) { double("image2", name: "centos-7-v20160301", self_link: "image2_selflink") }
576
+ let(:image3) { double("image3", name: "centos-6", self_link: "image3_selflink") }
577
+ let(:response) { double("response", items: [ image1, image2, image3 ]) }
578
+
579
+ it "returns the link for image2 which is the most recent image" do
580
+ expect(service.image_alias_url("centos-7")).to eq("image2_selflink")
581
+ end
582
+ end
583
+ end
584
+ end
585
+
499
586
  describe '#boot_disk_name_for' do
500
587
  it "returns the boot disk name if supplied by the user" do
501
588
  options = { name: "instance_name", boot_disk_name: "disk_name" }
@@ -543,18 +630,50 @@ describe Chef::Knife::Cloud::GoogleService do
543
630
  end
544
631
 
545
632
  describe '#instance_network_interfaces_for' do
546
- it "returns an array containing a properly-formatted interface" do
547
- interface = double("interface")
548
- options = { network: "test_network", public_ip: "public_ip" }
633
+ let(:interface) { double("interface" ) }
634
+ let(:options) { { network: "test_network", public_ip: "public_ip" } }
549
635
 
550
- expect(service).to receive(:network_url_for).with("test_network").and_return("network_url")
551
- expect(service).to receive(:instance_access_configs_for).with("public_ip").and_return("access_configs")
636
+ before do
637
+ allow(service).to receive(:network_url_for)
638
+ allow(service).to receive(:subnet_url_for)
639
+ allow(service).to receive(:instance_access_configs_for)
640
+ allow(Google::Apis::ComputeV1::NetworkInterface).to receive(:new).and_return(interface)
641
+ allow(interface).to receive(:network=)
642
+ allow(interface).to receive(:subnetwork=)
643
+ allow(interface).to receive(:access_configs=)
644
+ end
552
645
 
646
+ it "creates a network interface object and returns it" do
553
647
  expect(Google::Apis::ComputeV1::NetworkInterface).to receive(:new).and_return(interface)
648
+ expect(service.instance_network_interfaces_for(options)).to eq([interface])
649
+ end
650
+
651
+ it "sets the network" do
652
+ expect(service).to receive(:network_url_for).with("test_network").and_return("network_url")
554
653
  expect(interface).to receive(:network=).with("network_url")
654
+ service.instance_network_interfaces_for(options)
655
+ end
656
+
657
+ it "sets the access configs" do
658
+ expect(service).to receive(:instance_access_configs_for).with("public_ip").and_return("access_configs")
555
659
  expect(interface).to receive(:access_configs=).with("access_configs")
660
+ service.instance_network_interfaces_for(options)
661
+ end
556
662
 
557
- expect(service.instance_network_interfaces_for(options)).to eq([interface])
663
+ it "does not set a subnetwork" do
664
+ expect(service).not_to receive(:subnet_url_for)
665
+ expect(interface).not_to receive(:subnetwork=)
666
+ service.instance_network_interfaces_for(options)
667
+ end
668
+
669
+ context "when a subnet exists" do
670
+ let(:options) { { network: "test_network", subnet: "test_subnet", public_ip: "public_ip" } }
671
+
672
+ it "sets the subnetwork" do
673
+ expect(service).to receive(:subnet_url_for).with("test_subnet").and_return("subnet_url")
674
+ expect(interface).to receive(:subnetwork=).with("subnet_url")
675
+ service.instance_network_interfaces_for(options)
676
+ end
558
677
  end
559
678
  end
560
679
 
@@ -564,15 +683,23 @@ describe Chef::Knife::Cloud::GoogleService do
564
683
  end
565
684
  end
566
685
 
686
+ describe '#subnet_url_for' do
687
+ it "returns a properly-formatted subnet URL" do
688
+ expect(service).to receive(:region).and_return("test_region")
689
+ expect(service.subnet_url_for("test_subnet")).to eq("projects/test_project/regions/test_region/subnetworks/test_subnet")
690
+ end
691
+ end
692
+
567
693
  describe '#instance_scheduling_for' do
568
694
  it "returns a properly-formatted scheduling object" do
569
695
  scheduling = double("scheduling")
570
- options = { auto_restart: "auto_restart", auto_migrate: "auto_migrate" }
696
+ options = { auto_restart: "auto_restart", auto_migrate: "auto_migrate", preemptible: "preempt" }
571
697
 
572
698
  expect(service).to receive(:migrate_setting_for).with("auto_migrate").and_return("host_maintenance")
573
699
  expect(Google::Apis::ComputeV1::Scheduling).to receive(:new).and_return(scheduling)
574
700
  expect(scheduling).to receive(:automatic_restart=).with("auto_restart")
575
701
  expect(scheduling).to receive(:on_host_maintenance=).with("host_maintenance")
702
+ expect(scheduling).to receive(:preemptible=).with("preempt")
576
703
 
577
704
  expect(service.instance_scheduling_for(options)).to eq(scheduling)
578
705
  end
@@ -38,8 +38,13 @@ describe Chef::Knife::Cloud::GoogleServerCreate do
38
38
  allow(command).to receive(:check_for_missing_config_values!)
39
39
  allow(command).to receive(:valid_disk_size?).and_return(true)
40
40
  allow(command).to receive(:boot_disk_size)
41
- command.config[:bootstrap_protocol] = "ssh"
42
- command.config[:identity_file] = "/path/to/file"
41
+ allow(command).to receive(:locate_config_value).with(:bootstrap_protocol).and_return("ssh")
42
+ allow(command).to receive(:locate_config_value).with(:identity_file).and_return("/path/to/file")
43
+ allow(command).to receive(:locate_config_value).with(:auto_migrate)
44
+ allow(command).to receive(:locate_config_value).with(:auto_restart)
45
+ allow(command).to receive(:locate_config_value).with(:chef_node_name)
46
+ allow(command).to receive(:locate_config_value).with(:chef_node_name_prefix)
47
+ allow(command).to receive(:preemptible?).and_return(false)
43
48
  end
44
49
 
45
50
  context "when no instance name has been provided" do
@@ -66,6 +71,20 @@ describe Chef::Knife::Cloud::GoogleServerCreate do
66
71
  expect(command).to receive(:locate_config_value).with(:gce_email).and_return(nil)
67
72
  expect { command.validate_params! }.to raise_error(RuntimeError)
68
73
  end
74
+
75
+ it "prints a warning if auto-migrate is true for a preemptible instance" do
76
+ allow(command).to receive(:preemptible?).and_return(true)
77
+ allow(command).to receive(:locate_config_value).with(:auto_migrate).and_return(true)
78
+ expect(command.ui).to receive(:warn).with("Auto-migrate disabled for preemptible instance")
79
+ command.validate_params!
80
+ end
81
+
82
+ it "prints a warning if auto-restart is true for a preemptible instance" do
83
+ allow(command).to receive(:preemptible?).and_return(true)
84
+ allow(command).to receive(:locate_config_value).with(:auto_restart).and_return(true)
85
+ expect(command.ui).to receive(:warn).with("Auto-restart disabled for preemptible instance")
86
+ command.validate_params!
87
+ end
69
88
  end
70
89
 
71
90
  describe '#before_bootstrap' do
@@ -133,6 +152,39 @@ describe Chef::Knife::Cloud::GoogleServerCreate do
133
152
  end
134
153
  end
135
154
 
155
+ describe '#preemptible?' do
156
+ it "returns the preemptible setting from the config" do
157
+ expect(command).to receive(:locate_config_value).with(:preemptible).and_return("test_preempt")
158
+ expect(command.preemptible?).to eq("test_preempt")
159
+ end
160
+ end
161
+
162
+ describe '#auto_migrate?' do
163
+ it "returns false if the instance is preemptible" do
164
+ expect(command).to receive(:preemptible?).and_return(true)
165
+ expect(command.auto_migrate?).to eq(false)
166
+ end
167
+
168
+ it "returns the setting from the config if preemptible is false" do
169
+ expect(command).to receive(:preemptible?).and_return(false)
170
+ expect(command).to receive(:locate_config_value).with(:auto_migrate).and_return("test_migrate")
171
+ expect(command.auto_migrate?).to eq("test_migrate")
172
+ end
173
+ end
174
+
175
+ describe '#auto_restart?' do
176
+ it "returns false if the instance is preemptible" do
177
+ expect(command).to receive(:preemptible?).and_return(true)
178
+ expect(command.auto_restart?).to eq(false)
179
+ end
180
+
181
+ it "returns the setting from the config if preemptible is false" do
182
+ expect(command).to receive(:preemptible?).and_return(false)
183
+ expect(command).to receive(:locate_config_value).with(:auto_restart).and_return("test_restart")
184
+ expect(command.auto_restart?).to eq("test_restart")
185
+ end
186
+ end
187
+
136
188
  describe '#ip_address_for_bootstrap' do
137
189
  it "returns the public IP by default" do
138
190
  expect(command).to receive(:locate_config_value).with(:use_private_ip).and_return(false)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-google
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chiraq Jog
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2016-03-04 00:00:00.000000000 Z
16
+ date: 2016-03-17 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: chef