knife-google 2.1.0 → 2.2.0

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
  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