knife-google 1.3.1 → 2.0.0

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