knife-google 1.3.1 → 2.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.
Files changed (111) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +1 -0
  3. data/.travis.yml +15 -3
  4. data/CHANGELOG.md +4 -6
  5. data/Gemfile +3 -9
  6. data/README.md +208 -355
  7. data/RELEASE_NOTES.md +8 -17
  8. data/Rakefile +8 -49
  9. data/knife-google.gemspec +20 -17
  10. data/lib/chef/knife/cloud/google_service.rb +491 -0
  11. data/lib/chef/knife/cloud/google_service_helpers.rb +62 -0
  12. data/lib/chef/knife/cloud/google_service_options.rb +58 -0
  13. data/lib/chef/knife/google_disk_create.rb +40 -44
  14. data/lib/chef/knife/google_disk_delete.rb +22 -40
  15. data/lib/chef/knife/google_disk_list.rb +57 -51
  16. data/lib/chef/knife/google_project_quotas.rb +59 -0
  17. data/lib/chef/knife/google_region_list.rb +43 -102
  18. data/lib/chef/knife/google_region_quotas.rb +77 -0
  19. data/lib/chef/knife/google_server_create.rb +224 -505
  20. data/lib/chef/knife/google_server_delete.rb +20 -78
  21. data/lib/chef/knife/google_server_list.rb +42 -53
  22. data/lib/chef/knife/google_server_show.rb +44 -0
  23. data/lib/chef/knife/google_zone_list.rb +39 -50
  24. data/lib/knife-google/version.rb +3 -2
  25. data/spec/cloud/google_service_helpers_spec.rb +120 -0
  26. data/spec/cloud/google_service_spec.rb +832 -0
  27. data/spec/google_disk_create_spec.rb +72 -0
  28. data/spec/google_disk_delete_spec.rb +64 -0
  29. data/spec/google_disk_list_spec.rb +93 -0
  30. data/spec/google_project_quotas_spec.rb +63 -0
  31. data/spec/google_region_list_spec.rb +65 -0
  32. data/spec/google_region_quotas_spec.rb +108 -0
  33. data/spec/google_server_create_spec.rb +177 -0
  34. data/spec/google_server_delete_spec.rb +39 -0
  35. data/spec/google_server_list_spec.rb +77 -0
  36. data/spec/google_server_show_spec.rb +60 -0
  37. data/spec/google_zone_list_spec.rb +59 -0
  38. metadata +91 -114
  39. data/CONTRIB.md +0 -64
  40. data/lib/chef/knife/google_base.rb +0 -76
  41. data/lib/chef/knife/google_project_list.rb +0 -178
  42. data/lib/chef/knife/google_setup.rb +0 -31
  43. data/lib/google/compute.rb +0 -47
  44. data/lib/google/compute/client.rb +0 -216
  45. data/lib/google/compute/config.rb +0 -23
  46. data/lib/google/compute/creatable_resource_collection.rb +0 -55
  47. data/lib/google/compute/deletable_resource_collection.rb +0 -51
  48. data/lib/google/compute/disk.rb +0 -38
  49. data/lib/google/compute/exception.rb +0 -30
  50. data/lib/google/compute/firewall.rb +0 -65
  51. data/lib/google/compute/global_operation.rb +0 -60
  52. data/lib/google/compute/image.rb +0 -29
  53. data/lib/google/compute/listable_resource_collection.rb +0 -33
  54. data/lib/google/compute/machine_type.rb +0 -36
  55. data/lib/google/compute/mixins/utils.rb +0 -58
  56. data/lib/google/compute/network.rb +0 -29
  57. data/lib/google/compute/project.rb +0 -76
  58. data/lib/google/compute/region.rb +0 -31
  59. data/lib/google/compute/region_operation.rb +0 -62
  60. data/lib/google/compute/resource.rb +0 -81
  61. data/lib/google/compute/resource_collection.rb +0 -78
  62. data/lib/google/compute/server.rb +0 -88
  63. data/lib/google/compute/server/attached_disk.rb +0 -39
  64. data/lib/google/compute/server/network_interface.rb +0 -38
  65. data/lib/google/compute/server/network_interface/access_config.rb +0 -35
  66. data/lib/google/compute/server/serial_port_output.rb +0 -31
  67. data/lib/google/compute/snapshot.rb +0 -30
  68. data/lib/google/compute/version.rb +0 -19
  69. data/lib/google/compute/zone.rb +0 -34
  70. data/lib/google/compute/zone_operation.rb +0 -62
  71. data/spec/chef/knife/google_base_spec.rb +0 -46
  72. data/spec/chef/knife/google_disk_create_spec.rb +0 -37
  73. data/spec/chef/knife/google_disk_delete_spec.rb +0 -64
  74. data/spec/chef/knife/google_disk_list_spec.rb +0 -36
  75. data/spec/chef/knife/google_region_list_spec.rb +0 -32
  76. data/spec/chef/knife/google_server_create_spec.rb +0 -138
  77. data/spec/chef/knife/google_server_delete_spec.rb +0 -127
  78. data/spec/chef/knife/google_server_list_spec.rb +0 -39
  79. data/spec/chef/knife/google_setup_spec.rb +0 -24
  80. data/spec/chef/knife/google_zone_list_spec.rb +0 -32
  81. data/spec/data/client.json +0 -14
  82. data/spec/data/compute-v1.json +0 -6734
  83. data/spec/data/disk.json +0 -14
  84. data/spec/data/firewall.json +0 -13
  85. data/spec/data/global_operation.json +0 -36
  86. data/spec/data/image.json +0 -12
  87. data/spec/data/machine_type.json +0 -24
  88. data/spec/data/network.json +0 -10
  89. data/spec/data/project.json +0 -21
  90. data/spec/data/region.json +0 -23
  91. data/spec/data/serial_port_output.json +0 -5
  92. data/spec/data/server.json +0 -46
  93. data/spec/data/snapshot.json +0 -12
  94. data/spec/data/zone.json +0 -22
  95. data/spec/data/zone_operation.json +0 -36
  96. data/spec/google/compute/disk_spec.rb +0 -115
  97. data/spec/google/compute/firewall_spec.rb +0 -129
  98. data/spec/google/compute/global_operation_spec.rb +0 -62
  99. data/spec/google/compute/image_spec.rb +0 -75
  100. data/spec/google/compute/machine_type_spec.rb +0 -53
  101. data/spec/google/compute/network_spec.rb +0 -68
  102. data/spec/google/compute/project_spec.rb +0 -71
  103. data/spec/google/compute/region_spec.rb +0 -51
  104. data/spec/google/compute/server_spec.rb +0 -118
  105. data/spec/google/compute/snapshot_spec.rb +0 -57
  106. data/spec/google/compute/zone_operation_spec.rb +0 -62
  107. data/spec/google/compute/zone_spec.rb +0 -51
  108. data/spec/spec_helper.rb +0 -45
  109. data/spec/support/mocks.rb +0 -62
  110. data/spec/support/resource_examples.rb +0 -70
  111. data/spec/support/spec_google_base.rb +0 -60
data/RELEASE_NOTES.md CHANGED
@@ -6,22 +6,13 @@ Example Note:
6
6
  ## Example Heading
7
7
  Details about the thing that changed that needs to get included in the Release Notes in markdown.
8
8
  -->
9
- # knife-google 1.3.1 Release Notes :
10
- This release of knife-google contains a fix for an issue where access tokens
11
- could expire during long-running operations such as `knife google server
12
- create`. If you've experienced intermittent failures with your usage of the
13
- knife-google plug-in, you should consider upgrading to this version.
9
+ # knife-google 2.0.0 Release Notes :
10
+ In this release of knife-google the option names have been changed to conform to the long option names.
11
+ For example the `zone` option has been changed to `--gce-zone` as per the long option.
12
+ Also, support has been added so that the options can be set from knife.rb file.
14
13
 
15
- Thanks go to Eric Johnson at Google for the fix.
14
+ ## Issues fixed in knife-google 2.0.0
16
15
 
17
- ## knife-google on RubyGems and Github
18
- https://rubygems.org/gems/knife-google
19
- https://github.com/opscode/knife-google
20
-
21
- ## Issues fixed in knife-google 1.3.1
22
-
23
- * KNIFE-473: knife-google should refresh access token
24
-
25
- ## knife-google Breaking Changes:
26
-
27
- None.
16
+ * [knife-google #70] (https://github.com/chef/knife-google/pull/70) Fix for --bootstrap-version command line option
17
+ * [knife-google #76] (https://github.com/chef/knife-google/pull/76) Fix for picking options from knife.rb
18
+ * [knife-google #77] (https://github.com/chef/knife-google/pull/77) Changed option names according to long options
data/Rakefile CHANGED
@@ -1,53 +1,12 @@
1
- # Copyright 2013, Google, Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- #
15
- # encoding: utf-8
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
16
3
 
17
- require 'rubygems'
18
- require 'rake'
19
- GEM_NAME = "knife-google"
4
+ RSpec::Core::RakeTask.new(:spec)
20
5
 
21
- spec = eval(File.read(GEM_NAME+".gemspec"))
22
-
23
- require 'rubygems/package_task'
24
-
25
- Gem::PackageTask.new(spec) do |pkg|
26
- pkg.gem_spec = spec
27
- end
28
-
29
- require 'rspec/core'
30
- require 'rspec/core/rake_task'
31
- RSpec::Core::RakeTask.new(:spec) do |spec|
32
- spec.pattern = FileList['spec/**/*_spec.rb']
33
- end
34
-
35
- task :default => :spec
36
-
37
- task :install => :package do
38
- sh %{gem install pkg/#{GEM_NAME}-#{Knife::Google::VERSION} --no-rdoc --no-ri}
39
- end
40
-
41
- task :uninstall do
42
- sh %{gem uninstall #{GEM_NAME} -x -v #{Knife::Google::VERSION} }
6
+ require "chefstyle"
7
+ require "rubocop/rake_task"
8
+ RuboCop::RakeTask.new(:style) do |task|
9
+ task.options << "--display-cop-names"
43
10
  end
44
11
 
45
- require 'rdoc/task'
46
- Rake::RDocTask.new do |rdoc|
47
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
-
49
- rdoc.rdoc_dir = 'rdoc'
50
- rdoc.title = "#{GEM_NAME} #{version}"
51
- rdoc.rdoc_files.include('README*', 'LICENSE', 'CONTRIB*')
52
- rdoc.rdoc_files.include('lib/**/*.rb')
53
- end
12
+ task default: [:spec, :style]
data/knife-google.gemspec CHANGED
@@ -1,27 +1,30 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
- require 'knife-google/version'
3
+ require "knife-google/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = 'knife-google'
6
+ s.name = "knife-google"
7
7
  s.version = Knife::Google::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Chiraq Jog", "Ranjib Dey", "James Tucker", "Paul Rossman", "Eric Johnson"]
10
- s.email = "paulrossman@google.com"
9
+ s.authors = ["Chiraq Jog", "Ranjib Dey", "James Tucker", "Paul Rossman", "Eric Johnson", "Chef Partner Engineering"]
10
+ s.license = "Apache-2.0"
11
+ s.email = ["paulrossman@google.com", "partnereng@chef.io"]
11
12
  s.has_rdoc = true
12
- s.extra_rdoc_files = ["README.md", "LICENSE", "CONTRIB.md" ]
13
- s.summary = "Manage Google Compute Engine servers, disks, and zones"
14
- s.description = "Google Compute Engine Support for Chef's Knife Command"
15
- s.homepage = "http://wiki.opscode.com/display/chef"
13
+ s.extra_rdoc_files = ["README.md", "LICENSE"]
14
+ s.summary = "Google Compute Engine Support for Chef's Knife Command"
15
+ s.description = s.summary
16
+ s.homepage = "https://github.com/chef/knife-google"
16
17
 
17
- s.add_dependency "chef", ">= 0.10.0"
18
- s.add_dependency "google-api-client"
19
- s.add_dependency "multi_json"
20
- s.add_dependency "mixlib-config"
21
- s.files = `git ls-files`.split($/)
22
- #s.files = Dir['CONTRIB.md', 'Gemfile', 'LICENSE', 'README.md', 'Rakefile', 'knife-google.gemspec', 'lib/**/*', 'spec/**/*']
18
+ s.add_dependency "chef", "~> 12.0"
19
+ s.add_dependency "knife-cloud", "~> 1.2.0"
20
+ s.add_dependency "google-api-client", "~> 0.9.0"
21
+ s.add_dependency "gcewinpass", "~> 1.0"
23
22
 
24
- s.add_development_dependency "rspec"
25
- s.add_development_dependency "rake"
26
- s.add_development_dependency "simplecov"
23
+ s.files = `git ls-files -z`.split("\x0")
24
+
25
+ s.add_development_dependency "rake", "~> 10.0"
26
+ s.add_development_dependency "rspec", "~> 3.1"
27
+ s.add_development_dependency "simplecov", "~> 0.9"
28
+ s.add_development_dependency "rubocop"
29
+ s.add_development_dependency "pry"
27
30
  end
@@ -0,0 +1,491 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2016 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
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
+
19
+ require "chef/knife/cloud/exceptions"
20
+ require "chef/knife/cloud/service"
21
+ require "chef/knife/cloud/helpers"
22
+ require "chef/knife/cloud/google_service_helpers"
23
+ require "google/apis/compute_v1"
24
+ require "ipaddr"
25
+
26
+ class Chef::Knife::Cloud
27
+ class GoogleService < Service
28
+ include Chef::Knife::Cloud::GoogleServiceHelpers
29
+
30
+ attr_reader :project, :zone, :wait_time, :refresh_rate, :max_pages, :max_page_size
31
+
32
+ def initialize(options = {})
33
+ @project = options[:project]
34
+ @zone = options[:zone]
35
+ @wait_time = options[:wait_time]
36
+ @refresh_rate = options[:refresh_rate]
37
+ @max_pages = options[:max_pages]
38
+ @max_page_size = options[:max_page_size]
39
+ end
40
+
41
+ def connection
42
+ return @connection unless @connection.nil?
43
+
44
+ @connection = Google::Apis::ComputeV1::ComputeService.new
45
+ @connection.authorization = authorization
46
+
47
+ @connection
48
+ end
49
+
50
+ def authorization
51
+ @authorization ||= Google::Auth.get_application_default(
52
+ [
53
+ "https://www.googleapis.com/auth/cloud-platform",
54
+ "https://www.googleapis.com/auth/compute",
55
+ ]
56
+ )
57
+ end
58
+
59
+ def create_server(options = {})
60
+ validate_server_create_options!(options)
61
+
62
+ ui.msg("Creating instance...")
63
+
64
+ instance_object = instance_object_for(options)
65
+ wait_for_operation(connection.insert_instance(project, zone, instance_object))
66
+ wait_for_status("RUNNING") { get_server(options[:name]) }
67
+
68
+ ui.msg("Instance created!")
69
+
70
+ get_server(options[:name])
71
+ end
72
+
73
+ def delete_server(instance_name)
74
+ begin
75
+ instance = get_server(instance_name)
76
+ rescue Google::Apis::ClientError
77
+ ui.warn("Unable to locate instance #{instance_name} in project #{project}, zone #{zone}")
78
+ return
79
+ end
80
+
81
+ server_summary(instance)
82
+ ui.confirm("Do you really want to delete this instance")
83
+
84
+ ui.msg("Deleting instance #{instance_name}...")
85
+
86
+ wait_for_operation(connection.delete_instance(project, zone, instance_name))
87
+
88
+ ui.msg("Instance #{instance_name} deleted successfully.")
89
+ end
90
+
91
+ def get_server(instance_name)
92
+ connection.get_instance(project, zone, instance_name)
93
+ end
94
+
95
+ def create_disk(name, size, type, source_image = nil)
96
+ disk = Google::Apis::ComputeV1::Disk.new
97
+ disk.name = name
98
+ disk.size_gb = size
99
+ disk.type = disk_type_url_for(type)
100
+
101
+ ui.msg("Creating a #{size} GB disk named #{name}...")
102
+
103
+ wait_for_operation(connection.insert_disk(project, zone, disk, source_image: source_image))
104
+
105
+ ui.msg("Waiting for disk to be ready...")
106
+
107
+ wait_for_status("READY") { connection.get_disk(project, zone, name) }
108
+
109
+ ui.msg("Disk created successfully.")
110
+ end
111
+
112
+ def delete_disk(name)
113
+ begin
114
+ connection.get_disk(project, zone, name)
115
+ rescue Google::Apis::ClientError
116
+ ui.warn("Unable to locate disk #{name} in project #{project}, zone #{zone}")
117
+ return
118
+ end
119
+
120
+ ui.confirm("Do you really want to delete disk #{name}")
121
+
122
+ ui.msg("Deleting disk #{name}...")
123
+ wait_for_operation(connection.delete_disk(project, zone, name))
124
+ ui.msg("Disk #{name} deleted successfully.")
125
+ end
126
+
127
+ def list_servers
128
+ instances = paginated_results(:list_instances, :items, project, zone)
129
+ return [] if instances.nil?
130
+
131
+ instances.each_with_object([]) do |instance, memo|
132
+ memo << OpenStruct.new(
133
+ name: instance.name,
134
+ status: instance.status,
135
+ machine_type: machine_type_for(instance),
136
+ network: network_for(instance),
137
+ private_ip: private_ip_for(instance),
138
+ public_ip: public_ip_for(instance)
139
+ )
140
+ end
141
+ end
142
+
143
+ def list_zones
144
+ paginated_results(:list_zones, :items, project) || []
145
+ end
146
+
147
+ def list_disks
148
+ paginated_results(:list_disks, :items, project, zone) || []
149
+ end
150
+
151
+ def list_regions
152
+ paginated_results(:list_regions, :items, project) || []
153
+ end
154
+
155
+ def list_project_quotas
156
+ connection.get_project(project).quotas || []
157
+ end
158
+
159
+ def validate_server_create_options!(options)
160
+ raise "Invalid machine type: #{options[:machine_type]}" unless valid_machine_type?(options[:machine_type])
161
+ raise "Invalid network: #{options[:network]}" unless valid_network?(options[:network])
162
+ raise "Invalid Public IP setting: #{options[:public_ip]}" unless valid_public_ip_setting?(options[:public_ip])
163
+ 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?
164
+ end
165
+
166
+ def check_api_call(&block)
167
+ block.call
168
+ rescue Google::Apis::ClientError
169
+ false
170
+ else
171
+ true
172
+ end
173
+
174
+ def valid_machine_type?(machine_type)
175
+ return false if machine_type.nil?
176
+ check_api_call { connection.get_machine_type(project, zone, machine_type) }
177
+ end
178
+
179
+ def valid_network?(network)
180
+ return false if network.nil?
181
+ check_api_call { connection.get_network(project, network) }
182
+ end
183
+
184
+ def image_exist?(image_project, image_name)
185
+ check_api_call { connection.get_image(image_project, image_name) }
186
+ end
187
+
188
+ def valid_public_ip_setting?(public_ip)
189
+ public_ip.downcase! if public_ip.respond_to?(:downcase)
190
+
191
+ if public_ip.nil? || public_ip == "ephemeral" || public_ip == "none"
192
+ true
193
+ elsif valid_ip_address?(public_ip)
194
+ true
195
+ else
196
+ false
197
+ end
198
+ end
199
+
200
+ def valid_ip_address?(ip_address)
201
+ IPAddr.new(ip_address)
202
+ rescue IPAddr::InvalidAddressError
203
+ false
204
+ else
205
+ true
206
+ end
207
+
208
+ def instance_object_for(options)
209
+ inst_obj = Google::Apis::ComputeV1::Instance.new
210
+ inst_obj.name = options[:name]
211
+ inst_obj.can_ip_forward = options[:can_ip_forward]
212
+ inst_obj.disks = instance_disks_for(options)
213
+ inst_obj.machine_type = machine_type_url_for(options[:machine_type])
214
+ inst_obj.metadata = instance_metadata_for(options[:metadata])
215
+ inst_obj.network_interfaces = instance_network_interfaces_for(options)
216
+ inst_obj.scheduling = instance_scheduling_for(options)
217
+ inst_obj.service_accounts = instance_service_accounts_for(options) unless instance_service_accounts_for(options).nil?
218
+ inst_obj.tags = instance_tags_for(options[:tags])
219
+
220
+ inst_obj
221
+ end
222
+
223
+ def instance_disks_for(options)
224
+ disks = []
225
+ disks << instance_boot_disk_for(options)
226
+ options[:additional_disks].each do |disk_name|
227
+ begin
228
+ disk = connection.get_disk(project, zone, disk_name)
229
+ rescue Google::Apis::ClientError => e
230
+ ui.error("Unable to attach disk #{disk_name} to the instance: #{e.message}")
231
+ raise
232
+ end
233
+
234
+ disks << Google::Apis::ComputeV1::AttachedDisk.new.tap { |x| x.source = disk.self_link }
235
+ end
236
+
237
+ disks
238
+ end
239
+
240
+ def instance_boot_disk_for(options)
241
+ disk = Google::Apis::ComputeV1::AttachedDisk.new
242
+ params = Google::Apis::ComputeV1::AttachedDiskInitializeParams.new
243
+
244
+ disk.boot = true
245
+ disk.auto_delete = options[:boot_disk_autodelete]
246
+ params.disk_name = boot_disk_name_for(options)
247
+ params.disk_size_gb = options[:boot_disk_size]
248
+ params.disk_type = disk_type_url_for(boot_disk_type_for(options))
249
+ params.source_image = image_search_for(options[:image], options[:image_project])
250
+
251
+ disk.initialize_params = params
252
+ disk
253
+ end
254
+
255
+ def boot_disk_type_for(options)
256
+ options[:boot_disk_ssd] ? "pd-ssd" : "pd-standard"
257
+ end
258
+
259
+ def image_search_for(image, image_project)
260
+ # if the user provided an image_project, assume they want it, no questions asked
261
+ return image_url_for(image_project, image) unless image_project.nil?
262
+
263
+ # no image project has been provided. We'll first check the user's project for the image.
264
+ url = image_url_for(project, image)
265
+ return url unless url.nil?
266
+
267
+ # Image not found in user's project. Is there a public project this image might exist in?
268
+ public_project = public_project_for_image(image)
269
+ if public_project
270
+ return image_url_for(public_project, image)
271
+ end
272
+
273
+ # No image in user's project or public project, so it doesn't exist.
274
+ nil
275
+ end
276
+
277
+ def image_url_for(image_project, image_name)
278
+ return "projects/#{image_project}/global/images/#{image_name}" if image_exist?(image_project, image_name)
279
+ end
280
+
281
+ def boot_disk_name_for(options)
282
+ options[:boot_disk_name].nil? ? options[:name] : options[:boot_disk_name]
283
+ end
284
+
285
+ def machine_type_url_for(machine_type)
286
+ "zones/#{zone}/machineTypes/#{machine_type}"
287
+ end
288
+
289
+ def instance_metadata_for(metadata)
290
+ return if metadata.nil? || metadata.empty?
291
+
292
+ metadata_obj = Google::Apis::ComputeV1::Metadata.new
293
+ metadata_obj.items = metadata.each_with_object([]) do |(k, v), memo|
294
+ metadata_item = Google::Apis::ComputeV1::Metadata::Item.new
295
+ metadata_item.key = k
296
+ metadata_item.value = v
297
+
298
+ memo << metadata_item
299
+ end
300
+
301
+ metadata_obj
302
+ end
303
+
304
+ def instance_network_interfaces_for(options)
305
+ interface = Google::Apis::ComputeV1::NetworkInterface.new
306
+ interface.network = network_url_for(options[:network])
307
+ interface.access_configs = instance_access_configs_for(options[:public_ip])
308
+
309
+ Array(interface)
310
+ end
311
+
312
+ def instance_access_configs_for(public_ip)
313
+ return [] if public_ip.nil? || public_ip == "NONE"
314
+
315
+ access_config = Google::Apis::ComputeV1::AccessConfig.new
316
+ access_config.name = "External NAT"
317
+ access_config.type = "ONE_TO_ONE_NAT"
318
+ access_config.nat_ip = public_ip if valid_ip_address?(public_ip)
319
+
320
+ Array(access_config)
321
+ end
322
+
323
+ def network_url_for(network)
324
+ "projects/#{project}/global/networks/#{network}"
325
+ end
326
+
327
+ def instance_scheduling_for(options)
328
+ scheduling = Google::Apis::ComputeV1::Scheduling.new
329
+ scheduling.automatic_restart = options[:auto_restart].to_s
330
+ scheduling.on_host_maintenance = migrate_setting_for(options[:auto_migrate])
331
+
332
+ scheduling
333
+ end
334
+
335
+ def migrate_setting_for(auto_migrate)
336
+ auto_migrate ? "MIGRATE" : "TERMINATE"
337
+ end
338
+
339
+ def instance_service_accounts_for(options)
340
+ return if options[:service_account_scopes].nil? || options[:service_account_scopes].empty?
341
+
342
+ service_account = Google::Apis::ComputeV1::ServiceAccount.new
343
+ service_account.email = options[:service_account_name]
344
+ service_account.scopes = options[:service_account_scopes].map { |scope| "https://www.googleapis.com/auth/#{scope}" unless scope.start_with?("https://www.googleapis.com/auth/") }
345
+
346
+ Array(service_account)
347
+ end
348
+
349
+ def instance_tags_for(tags)
350
+ return if tags.nil? || tags.empty?
351
+
352
+ tag_obj = Google::Apis::ComputeV1::Tags.new
353
+ tag_obj.items = tags
354
+
355
+ tag_obj
356
+ end
357
+
358
+ def network_for(instance)
359
+ instance.network_interfaces.first.network.split("/").last
360
+ rescue NoMethodError
361
+ "unknown"
362
+ end
363
+
364
+ def machine_type_for(instance)
365
+ instance.machine_type.split("/").last
366
+ end
367
+
368
+ def server_summary(server, _columns_with_info = nil)
369
+ msg_pair("Instance Name", server.name)
370
+ msg_pair("Status", server.status)
371
+ msg_pair("Machine Type", machine_type_for(server))
372
+ msg_pair("Project", project)
373
+ msg_pair("Zone", zone)
374
+ msg_pair("Network", network_for(server))
375
+ msg_pair("Private IP", private_ip_for(server))
376
+ msg_pair("Public IP", public_ip_for(server))
377
+ end
378
+
379
+ def public_project_for_image(image)
380
+ case image
381
+ when /centos/
382
+ "centos-cloud"
383
+ when /container-vm/
384
+ "google-containers"
385
+ when /coreos/
386
+ "coreos-cloud"
387
+ when /debian/
388
+ "debian-cloud"
389
+ when /opensuse-cloud/
390
+ "opensuse-cloud"
391
+ when /rhel/
392
+ "rhel-cloud"
393
+ when /sles/
394
+ "suse-cloud"
395
+ when /ubuntu/
396
+ "ubuntu-os-cloud"
397
+ when /windows/
398
+ "windows-cloud"
399
+ end
400
+ end
401
+
402
+ def disk_type_url_for(type)
403
+ "zones/#{zone}/diskTypes/#{type}"
404
+ end
405
+
406
+ def paginated_results(api_method, items_method, *args)
407
+ items = []
408
+ next_token = nil
409
+ loop_num = 0
410
+
411
+ loop do
412
+ loop_num += 1
413
+
414
+ response = connection.send(api_method.to_sym, *args, max_results: max_page_size, page_token: next_token)
415
+ response_items = response.send(items_method.to_sym)
416
+
417
+ break if response_items.nil?
418
+
419
+ items += response_items
420
+
421
+ next_token = response.next_page_token
422
+ break if next_token.nil?
423
+
424
+ if loop_num >= max_pages
425
+ ui.warn("Max pages (#{max_pages}) reached, but more results exist - truncating results...")
426
+ break
427
+ end
428
+ end
429
+
430
+ items
431
+ end
432
+
433
+ def wait_for_status(requested_status, &block)
434
+ last_status = ""
435
+
436
+ begin
437
+ Timeout.timeout(wait_time) do
438
+ loop do
439
+ item = block.call
440
+ current_status = item.status
441
+
442
+ if current_status == requested_status
443
+ print "\n"
444
+ break
445
+ end
446
+
447
+ if last_status == current_status
448
+ print "."
449
+ else
450
+ last_status = current_status
451
+ print "\n"
452
+ print "Current status: #{current_status}."
453
+ end
454
+
455
+ sleep refresh_rate
456
+ end
457
+ end
458
+ rescue Timeout::Error
459
+ ui.msg("")
460
+ ui.error("Request did not complete in #{wait_time} seconds. Check the Google Cloud Console for more info.")
461
+ exit 1
462
+ end
463
+ end
464
+
465
+ def wait_for_operation(operation)
466
+ operation_name = operation.name
467
+
468
+ wait_for_status("DONE") { zone_operation(operation_name) }
469
+
470
+ errors = operation_errors(operation_name)
471
+ return if errors.empty?
472
+
473
+ errors.each do |error|
474
+ ui.error("#{ui.color(error.code, :bold)}: #{error.message}")
475
+ end
476
+
477
+ raise "Operation #{operation_name} failed."
478
+ end
479
+
480
+ def zone_operation(operation_name)
481
+ connection.get_zone_operation(project, zone, operation_name)
482
+ end
483
+
484
+ def operation_errors(operation_name)
485
+ operation = zone_operation(operation_name)
486
+ return [] if operation.error.nil?
487
+
488
+ operation.error.errors
489
+ end
490
+ end
491
+ end