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
@@ -1,4 +1,8 @@
1
- # Copyright 2013 Google Inc. All Rights Reserved.
1
+ #
2
+ # Author:: Paul Rossman (<paulrossman@google.com>)
3
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright 2015-2016 Google Inc., Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
4
8
  # you may not use this file except in compliance with the License.
@@ -11,115 +15,52 @@
11
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
16
  # See the License for the specific language governing permissions and
13
17
  # limitations under the License.
14
- #
15
- require 'chef/knife/google_base'
16
- require 'time'
17
18
 
18
- class Chef
19
- class Knife
20
- class GoogleRegionList < Knife
19
+ require "chef/knife"
20
+ require "chef/knife/cloud/list_resource_command"
21
+ require "chef/knife/cloud/google_service"
22
+ require "chef/knife/cloud/google_service_helpers"
23
+ require "chef/knife/cloud/google_service_options"
24
+
25
+ class Chef::Knife::Cloud
26
+ class GoogleRegionList < ResourceListCommand
27
+ include GoogleServiceHelpers
28
+ include GoogleServiceOptions
29
+
30
+ banner "knife google zone list"
21
31
 
22
- include Knife::GoogleBase
32
+ def validate_params!
33
+ check_for_missing_config_values!
34
+ super
35
+ end
23
36
 
24
- banner "knife google region list (options)"
37
+ def before_exec_command
38
+ @columns_with_info = [
39
+ { label: "Region", key: "name" },
40
+ { label: "Status", key: "status", value_callback: method(:format_status_value) },
41
+ { label: "Zones", key: "zones", value_callback: method(:format_zones) },
42
+ ]
25
43
 
26
- option :limits,
27
- :short => "-L",
28
- :long => "--with-limits",
29
- :description => "Additionally print the quota limit for each metric",
30
- :required => false,
31
- :boolean => true,
32
- :default => false
44
+ @sort_by_field = "name"
45
+ end
33
46
 
34
- def run
35
- $stdout.sync = true
47
+ def query_resource
48
+ service.list_regions
49
+ end
36
50
 
37
- region_list = [
38
- ui.color("name", :bold),
39
- ui.color('status', :bold),
40
- ui.color('deprecation', :bold),
41
- ui.color('cpus', :bold),
42
- ui.color('disks-total-gb', :bold),
43
- ui.color('in-use-addresses', :bold),
44
- ui.color('static-addresses', :bold)].flatten.compact
51
+ def format_status_value(status)
52
+ status = status.downcase
53
+ status_color = if status == "up"
54
+ :green
55
+ else
56
+ :red
57
+ end
45
58
 
46
- output_column_count = region_list.length
59
+ ui.color(status, status_color)
60
+ end
47
61
 
48
- client.regions.list.each do |region|
49
- region_list << region.name
50
- region_list << begin
51
- status = region.status.downcase
52
- case status
53
- when 'up'
54
- ui.color(status, :green)
55
- else
56
- ui.color(status, :red)
57
- end
58
- end
59
- deprecation_state = "-"
60
- if region.deprecated.respond_to?('state')
61
- deprecation_state = region.deprecated.state
62
- end
63
- region_list << deprecation_state
64
- cpu_usage = "0"
65
- cpu_limit = "0"
66
- region.quotas.each do |quota|
67
- if quota["metric"] == "CPUS"
68
- cpu_usage = "#{quota["usage"].to_i}"
69
- cpu_limit = "#{quota["limit"].to_i}"
70
- end
71
- end
72
- if config[:limits] == true
73
- cpu_quota = "#{cpu_usage}/#{cpu_limit}"
74
- else
75
- cpu_quota = "#{cpu_usage}"
76
- end
77
- region_list << cpu_quota
78
- disk_usage = "0"
79
- disk_limit = "0"
80
- region.quotas.each do |quota|
81
- if quota["metric"] == "DISKS_TOTAL_GB"
82
- disk_usage = "#{quota["usage"].to_i}"
83
- disk_limit = "#{quota["limit"].to_i}"
84
- end
85
- end
86
- if config[:limits] == true
87
- disk_quota = "#{disk_usage}/#{disk_limit}"
88
- else
89
- disk_quota = "#{disk_usage}"
90
- end
91
- region_list << disk_quota
92
- inuse_usage = "0"
93
- inuse_limit = "0"
94
- region.quotas.each do |quota|
95
- if quota["metric"] == "IN_USE_ADDRESSES"
96
- inuse_usage = "#{quota["usage"].to_i}"
97
- inuse_limit = "#{quota["limit"].to_i}"
98
- end
99
- end
100
- if config[:limits] == true
101
- inuse_quota = "#{inuse_usage}/#{inuse_limit}"
102
- else
103
- inuse_quota = "#{inuse_usage}"
104
- end
105
- region_list << inuse_quota
106
- static_usage = "0"
107
- static_limit = "0"
108
- region.quotas.each do |quota|
109
- if quota["metric"] == "STATIC_ADDRESSES"
110
- static_usage = "#{quota["usage"].to_i}"
111
- static_limit = "#{quota["limit"].to_i}"
112
- end
113
- end
114
- if config[:limits] == true
115
- static_quota = "#{static_usage}/#{static_limit}"
116
- else
117
- static_quota = "#{static_usage}"
118
- end
119
- region_list << static_quota
120
- end
121
- ui.info(ui.list(region_list, :uneven_columns_across, output_column_count))
122
- end
62
+ def format_zones(zones)
63
+ zones.map { |zone| zone.split("/").last }.sort.join(", ")
123
64
  end
124
65
  end
125
66
  end
@@ -0,0 +1,77 @@
1
+ #
2
+ # Author:: Paul Rossman (<paulrossman@google.com>)
3
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright 2015-2016 Google Inc., Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require "chef/knife"
20
+ require "chef/knife/cloud/list_resource_command"
21
+ require "chef/knife/cloud/google_service"
22
+ require "chef/knife/cloud/google_service_helpers"
23
+ require "chef/knife/cloud/google_service_options"
24
+
25
+ class Chef::Knife::Cloud
26
+ class GoogleRegionQuotas < Command
27
+ include GoogleServiceHelpers
28
+ include GoogleServiceOptions
29
+
30
+ banner "knife google region quotas"
31
+
32
+ def validate_params!
33
+ check_for_missing_config_values!
34
+ super
35
+ end
36
+
37
+ def execute_command
38
+ service.list_regions.each do |region|
39
+ ui.msg(ui.color("Region: #{region.name}", :bold))
40
+
41
+ quotas = region.quotas
42
+ if quotas.nil? || quotas.empty?
43
+ ui.warn("No quota information available for this region.")
44
+ ui.msg("")
45
+ next
46
+ end
47
+
48
+ output = []
49
+ output << table_header
50
+ quotas.each do |quota|
51
+ output << format_name(quota.metric)
52
+ output << format_number(quota.limit)
53
+ output << format_number(quota.usage)
54
+ end
55
+
56
+ ui.msg(ui.list(output.flatten, :uneven_columns_across, table_header.size))
57
+ ui.msg("")
58
+ end
59
+ end
60
+
61
+ def table_header
62
+ [
63
+ ui.color("Quota", :bold),
64
+ ui.color("Limit", :bold),
65
+ ui.color("Usage", :bold),
66
+ ]
67
+ end
68
+
69
+ def format_name(name)
70
+ name.split("_").map { |x| x.capitalize }.join(" ")
71
+ end
72
+
73
+ def format_number(number)
74
+ number % 1 == 0 ? number.to_i.to_s : number.to_s
75
+ end
76
+ end
77
+ end
@@ -1,4 +1,8 @@
1
- # Copyright 2013 Google Inc. All Rights Reserved.
1
+ #
2
+ # Author:: Paul Rossman (<paulrossman@google.com>)
3
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright 2015-2016 Google Inc., Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
2
6
  #
3
7
  # Licensed under the Apache License, Version 2.0 (the "License");
4
8
  # you may not use this file except in compliance with the License.
@@ -11,525 +15,240 @@
11
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
16
  # See the License for the specific language governing permissions and
13
17
  # limitations under the License.
14
- #
15
- require 'timeout'
16
- require 'chef/knife/google_base'
17
18
 
18
- class Chef
19
- class Knife
20
- class GoogleServerCreate < Knife
19
+ require "chef/knife"
20
+ require "chef/knife/cloud/server/create_command"
21
+ require "chef/knife/cloud/server/create_options"
22
+ require "chef/knife/cloud/google_service"
23
+ require "chef/knife/cloud/google_service_helpers"
24
+ require "chef/knife/cloud/google_service_options"
25
+
26
+ class Chef::Knife::Cloud
27
+ class GoogleServerCreate < ServerCreateCommand
28
+ include GoogleServiceOptions
29
+ include GoogleServiceHelpers
30
+ include ServerCreateOptions
31
+
32
+ banner "knife google server create NAME -m MACHINE_TYPE -I IMAGE (options)"
33
+
34
+ option :machine_type,
35
+ short: "-m MACHINE_TYPE",
36
+ long: "--gce-machine-type MACHINE_TYPE",
37
+ description: "The machine type of server (n1-highcpu-2, n1-highcpu-2-d, etc)"
38
+
39
+ option :image,
40
+ short: "-I IMAGE",
41
+ long: "--gce-image IMAGE",
42
+ description: "The Image for the server"
43
+
44
+ option :image_project,
45
+ long: "--gce-image-project IMAGE_PROJECT",
46
+ description: "The project-id containing the Image (debian-cloud, centos-cloud, etc)"
47
+
48
+ option :boot_disk_name,
49
+ long: "--gce-boot-disk-name DISK",
50
+ description: "Name of persistent boot disk; default is to use the server name"
51
+
52
+ option :boot_disk_size,
53
+ long: "--gce-boot-disk-size SIZE",
54
+ description: "Size of the persistent boot disk between 10 and 10000 GB, specified in GB; default is '10' GB",
55
+ default: "10"
56
+
57
+ option :boot_disk_ssd,
58
+ long: "--[no-]gce-boot-disk-ssd",
59
+ description: "Use pd-ssd boot disk; default is pd-standard boot disk",
60
+ boolean: true,
61
+ default: false
62
+
63
+ option :boot_disk_autodelete,
64
+ long: "--[no-]gce-boot-disk-autodelete",
65
+ description: "Delete boot disk when server is deleted.",
66
+ boolean: true,
67
+ default: true
68
+
69
+ option :additional_disks,
70
+ long: "--gce-additional-disks DISKS",
71
+ short: "-D DISKS",
72
+ description: "Names of additional disks, comma-separated, to attach to this server (NOTE: this will not create them)",
73
+ proc: Proc.new { |disks| disks.split(",") },
74
+ default: []
75
+
76
+ option :auto_restart,
77
+ long: "--[no-]gce-auto-server-restart",
78
+ description: "GCE can automatically restart your server if it is terminated for non-user-initiated reasons; enabled by default.",
79
+ boolean: true,
80
+ default: true
81
+
82
+ option :auto_migrate,
83
+ long: "--[no-]gce-auto-server-migrate",
84
+ description: "GCE can migrate your server to other hardware without downtime prior to periodic infrastructure maintenance, otherwise the server is terminated; enabled by default.",
85
+ boolean: true,
86
+ default: true
87
+
88
+ option :can_ip_forward,
89
+ long: "--[no-]gce-can-ip-forward",
90
+ description: "Allow server network forwarding",
91
+ boolean: true,
92
+ default: false
93
+
94
+ option :network,
95
+ long: "--gce-network NETWORK",
96
+ description: "The network for this server; default is 'default'",
97
+ default: "default"
98
+
99
+ option :tags,
100
+ short: "-T TAG1,TAG2,TAG3",
101
+ long: "--gce-tags TAG1,TAG2,TAG3",
102
+ description: "Tags for this server",
103
+ proc: Proc.new { |tags| tags.split(",") },
104
+ default: []
105
+
106
+ option :metadata,
107
+ long: "--gce-metadata Key=Value[,Key=Value...]",
108
+ description: "Additional metadata for this server",
109
+ proc: Proc.new { |metadata| metadata.split(",") },
110
+ default: []
111
+
112
+ option :service_account_scopes,
113
+ long: "--gce-service-account-scopes SCOPE1,SCOPE2,SCOPE3",
114
+ proc: Proc.new { |service_account_scopes| service_account_scopes.split(",") },
115
+ description: "Service account scopes for this server",
116
+ default: []
117
+
118
+ option :service_account_name,
119
+ long: "--gce-service-account-name NAME",
120
+ description: "Service account name for this server, typically in the form of '123845678986@project.gserviceaccount.com'; default is 'default'",
121
+ default: "default"
122
+
123
+ option :use_private_ip,
124
+ long: "--gce-use-private-ip",
125
+ description: "if used, Chef will attempt to bootstrap the device using the private IP; default is disabled (use public IP)",
126
+ boolean: true,
127
+ default: false
128
+
129
+ option :public_ip,
130
+ long: "--gce-public-ip IP_ADDRESS",
131
+ description: "EPHEMERAL or static IP address or NONE; default is 'EPHEMERAL'",
132
+ default: "EPHEMERAL"
133
+
134
+ option :gce_email,
135
+ long: "--gce-email EMAIL_ADDRESS",
136
+ description: "email address of the logged-in Google Cloud user; required for bootstrapping windows hosts"
137
+
138
+ deps do
139
+ require "gcewinpass"
140
+ end
21
141
 
22
- include Knife::GoogleBase
142
+ def before_exec_command
143
+ super
144
+
145
+ @create_options = {
146
+ name: instance_name,
147
+ image: locate_config_value(:image),
148
+ image_project: locate_config_value(:image_project),
149
+ network: locate_config_value(:network),
150
+ public_ip: locate_config_value(:public_ip),
151
+ auto_migrate: locate_config_value(:auto_migrate),
152
+ auto_restart: locate_config_value(:auto_restart),
153
+ boot_disk_autodelete: locate_config_value(:boot_disk_autodelete),
154
+ boot_disk_name: locate_config_value(:boot_disk_name),
155
+ boot_disk_size: boot_disk_size,
156
+ boot_disk_ssd: locate_config_value(:boot_disk_ssd),
157
+ additional_disks: locate_config_value(:additional_disks),
158
+ can_ip_forward: locate_config_value(:can_ip_forward),
159
+ machine_type: locate_config_value(:machine_type),
160
+ service_account_scopes: locate_config_value(:service_account_scopes),
161
+ service_account_name: locate_config_value(:service_account_name),
162
+ metadata: metadata,
163
+ tags: locate_config_value(:tags),
164
+ }
165
+ end
23
166
 
24
- deps do
25
- require 'google/compute'
26
- require 'chef/json_compat'
27
- require 'chef/knife/bootstrap'
28
- Chef::Knife::Bootstrap.load_deps
29
- end
167
+ def set_default_config
168
+ # dumb hack for knife-cloud, which expects the user to pass in the WinRM password to use when bootstrapping.
169
+ # We won't know the password until the instance is created and we forceably reset it.
170
+ config[:winrm_password] = "will_change_this_later"
171
+ end
30
172
 
31
- banner "knife google server create NAME -m MACHINE_TYPE -I IMAGE -Z ZONE (options)"
32
-
33
- attr_accessor :initial_sleep_delay
34
- attr_reader :instance
35
-
36
- option :machine_type,
37
- :short => "-m MACHINE_TYPE",
38
- :long => "--gce-machine-type MACHINE_TYPE",
39
- :description => "The machine type of server (n1-highcpu-2, n1-highcpu-2-d, etc)",
40
- :required => true
41
-
42
- option :image,
43
- :short => "-I IMAGE",
44
- :long => "--gce-image IMAGE",
45
- :description => "The Image for the server",
46
- :required => true
47
-
48
- option :image_project_id,
49
- :long => "--gce-image-project-id IMAGE_PROJECT_ID",
50
- :description => "The project-id containing the Image (debian-cloud, centos-cloud, etc)",
51
- :default => ""
52
-
53
- option :zone,
54
- :short => "-Z ZONE",
55
- :long => "--gce-zone ZONE",
56
- :description => "The Zone for this server"
57
-
58
- option :boot_disk_name,
59
- :long => "--gce-boot-disk-name DISK",
60
- :description => "Name of persistent boot disk; default is to use the server name",
61
- :default => ""
62
-
63
- option :boot_disk_size,
64
- :long => "--gce-boot-disk-size SIZE",
65
- :description => "Size of the persistent boot disk between 10 and 10000 GB, specified in GB; default is '10' GB",
66
- :default => "10"
67
-
68
- option :auto_restart,
69
- :long => "--[no-]gce-auto-server-restart",
70
- :description => "Compute Engine can automatically restart your VM instance if it is terminated for non-user-initiated reasons; enabled by default.",
71
- :boolean => true,
72
- :default => true
73
-
74
- option :auto_migrate,
75
- :long => "--[no-]gce-auto-server-migrate",
76
- :description => "Compute Engine can migrate your VM instance to other hardware without downtime prior to periodic infrastructure maintenance, otherwise the server is terminated; enabled by default.",
77
- :boolean => true,
78
- :default => true
79
-
80
- option :network,
81
- :short => "-n NETWORK",
82
- :long => "--gce-network NETWORK",
83
- :description => "The network for this server; default is 'default'",
84
- :default => "default"
85
-
86
- option :tags,
87
- :short => "-T TAG1,TAG2,TAG3",
88
- :long => "--gce-tags TAG1,TAG2,TAG3",
89
- :description => "Tags for this server",
90
- :proc => Proc.new { |tags| tags.split(',') },
91
- :default => []
92
-
93
- option :metadata,
94
- :long => "--gce-metadata Key=Value[,Key=Value...]",
95
- :description => "Additional metadata for this server",
96
- :proc => Proc.new { |metadata| metadata.split(',') },
97
- :default => []
98
-
99
- option :service_account_scopes,
100
- :long => "--gce-service-account-scopes SCOPE1,SCOPE2,SCOPE3",
101
- :proc => Proc.new { |service_account_scopes| service_account_scopes.split(',') },
102
- :description => "Service account scopes for this server",
103
- :default => []
104
-
105
- # GCE documentation uses the term 'service account name', the api uses the term 'email'
106
- option :service_account_name,
107
- :long => "--gce-service-account-name NAME",
108
- :description => "Service account name for this server, typically in the form of '123845678986@project.gserviceaccount.com'; default is 'default'",
109
- :default => "default"
110
-
111
- option :instance_connect_ip,
112
- :long => "--gce-server-connect-ip INTERFACE",
113
- :description => "Whether to use PUBLIC or PRIVATE interface/address to connect; default is 'PUBLIC'",
114
- :default => 'PUBLIC'
115
-
116
- option :public_ip,
117
- :long=> "--gce-public-ip IP_ADDRESS",
118
- :description => "EPHEMERAL or static IP address or NONE; default is 'EPHEMERAL'",
119
- :default => "EPHEMERAL"
120
-
121
- option :chef_node_name,
122
- :short => "-N NAME",
123
- :long => "--node-name NAME",
124
- :description => "The Chef node name for your new node"
125
-
126
- option :ssh_user,
127
- :short => "-x USERNAME",
128
- :long => "--ssh-user USERNAME",
129
- :description => "The ssh username; default is 'root'",
130
- :default => "root"
131
-
132
- option :ssh_password,
133
- :short => "-P PASSWORD",
134
- :long => "--ssh-password PASSWORD",
135
- :description => "The ssh password"
136
-
137
- option :ssh_port,
138
- :short => "-p PORT",
139
- :long => "--ssh-port PORT",
140
- :description => "The ssh port; default is '22'",
141
- :default => "22"
142
-
143
- option :ssh_gateway,
144
- :short => "-w GATEWAY",
145
- :long => "--ssh-gateway GATEWAY",
146
- :description => "The ssh gateway server"
147
-
148
- option :identity_file,
149
- :short => "-i IDENTITY_FILE",
150
- :long => "--identity-file IDENTITY_FILE",
151
- :description => "The SSH identity file used for authentication"
152
-
153
- option :prerelease,
154
- :long => "--prerelease",
155
- :description => "Install the pre-release chef gems"
156
-
157
- option :bootstrap_version,
158
- :long => "--bootstrap-version VERSION",
159
- :description => "The version of Chef to install"
160
-
161
- option :distro,
162
- :short => "-d DISTRO",
163
- :long => "--distro DISTRO",
164
- :description => "Bootstrap a distro using a template; default is 'chef-full'",
165
- :default => 'chef-full'
166
-
167
- option :template_file,
168
- :long => "--template-file TEMPLATE",
169
- :description => "Full path to location of template to use",
170
- :default => false
171
-
172
- option :run_list,
173
- :short => "-r RUN_LIST",
174
- :long => "--run-list RUN_LIST",
175
- :description => "Comma separated list of roles/recipes to apply",
176
- :proc => lambda { |o| o.split(/[\s,]+/) }
177
-
178
- option :json_attributes,
179
- :short => "-j JSON",
180
- :long => "--json-attributes JSON",
181
- :description => "A JSON string to be added to the first run of chef-client",
182
- :proc => lambda { |o| JSON.parse(o) }
183
-
184
- option :host_key_verify,
185
- :long => "--[no-]host-key-verify",
186
- :description => "Verify host key, enabled by default.",
187
- :boolean => true,
188
- :default => true
189
-
190
- option :compute_user_data,
191
- :long => "--user-data USER_DATA_FILE",
192
- :short => "-u USER_DATA_FILE",
193
- :description => "The Google Compute User Data file to provision the server with"
194
-
195
- option :hint,
196
- :long => "--hint HINT_NAME[=HINT_FILE]",
197
- :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
198
- :proc => Proc.new { |h|
199
- Chef::Config[:knife][:hints] ||= {}
200
- name, path = h.split("=")
201
- Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
202
- }
203
-
204
- option :secret,
205
- :short => "-s SECRET",
206
- :long => "--secret ",
207
- :description => "The secret key to use to encrypt data bag item values",
208
- :proc => lambda { |s| Chef::Config[:knife][:secret] = s }
209
-
210
- option :secret_file,
211
- :long => "--secret-file SECRET_FILE",
212
- :description => "A file containing the secret key to use to encrypt data bag item values",
213
- :proc => lambda { |sf| Chef::Config[:knife][:secret_file] = sf }
214
-
215
- def tcp_test_ssh(hostname, ssh_port)
216
- tcp_socket = TCPSocket.new(hostname, ssh_port)
217
- readable = IO.select([tcp_socket], nil, nil, 5)
218
- if readable
219
- Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
220
- yield
221
- true
222
- else
223
- false
224
- end
225
- rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
226
- sleep 2
227
- false
228
- rescue Errno::EPERM, Errno::ETIMEDOUT
229
- false
230
- ensure
231
- tcp_socket && tcp_socket.close
232
- end
173
+ def validate_params!
174
+ check_for_missing_config_values!(:machine_type, :image, :boot_disk_size, :network)
175
+ raise "You must supply an instance name." if @name_args.first.nil?
176
+ raise "Boot disk size must be between 10 and 10,000" unless valid_disk_size?(boot_disk_size)
177
+ raise "Please provide your Google Cloud console email address via --gce-email. " \
178
+ "It is required when resetting passwords on Windows hosts." if locate_config_value(:bootstrap_protocol) == "winrm" && locate_config_value(:gce_email).nil?
233
179
 
234
- def wait_for_sshd(hostname)
235
- config[:ssh_gateway] ? wait_for_tunnelled_sshd(hostname) : wait_for_direct_sshd(hostname, config[:ssh_port])
236
- end
180
+ super
181
+ end
237
182
 
238
- def wait_for_tunnelled_sshd(hostname)
239
- print(".")
240
- print(".") until tunnel_test_ssh(hostname) {
241
- sleep @initial_sleep_delay ||= 40
242
- puts("done")
243
- }
244
- end
183
+ def before_bootstrap
184
+ super
245
185
 
246
- def tunnel_test_ssh(hostname, &block)
247
- gw_host, gw_user = config[:ssh_gateway].split('@').reverse
248
- gw_host, gw_port = gw_host.split(':')
249
- gateway = Net::SSH::Gateway.new(gw_host, gw_user, :port => gw_port || 22)
250
- status = false
251
- gateway.open(hostname, config[:ssh_port]) do |local_tunnel_port|
252
- status = tcp_test_ssh('localhost', local_tunnel_port, &block)
253
- end
254
- status
255
- rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
256
- sleep 2
257
- false
258
- rescue Errno::EPERM, Errno::ETIMEDOUT
259
- false
260
- end
186
+ config[:chef_node_name] = locate_config_value(:chef_node_name) ? locate_config_value(:chef_node_name) : instance_name
187
+ config[:bootstrap_ip_address] = ip_address_for_bootstrap
261
188
 
262
- def wait_for_direct_sshd(hostname, ssh_port)
263
- print(".") until tcp_test_ssh(ssh_connect_host, ssh_port) {
264
- sleep @initial_sleep_delay ||= 40
265
- puts("done")
266
- }
189
+ if locate_config_value(:bootstrap_protocol) == "winrm"
190
+ ui.msg("Resetting the Windows login password so the bootstrap can continue...")
191
+ config[:winrm_password] = reset_windows_password
267
192
  end
193
+ end
268
194
 
269
- def ssh_connect_host
270
- @ssh_connect_host ||= if config[:instance_connect_ip] == 'PUBLIC'
271
- public_ips(@instance).first
272
- else
273
- private_ips(@instance).first
274
- end
275
- end
195
+ # overriding this from Chef::Knife::Cloud::ServerCreateCommand.
196
+ #
197
+ # This gets called in validate_params! in that class before our #before_bootstrap
198
+ # is called, in which it randomly generates a node name, which means we never default
199
+ # to the instance name in our #before_bootstrap method. Instead, we'll just nil this
200
+ # and allow our class here to do The Right Thing.
201
+ def get_node_name(_name, _prefix)
202
+ nil
203
+ end
276
204
 
277
- def disk_exists(disk, zone)
278
- # if client.disks.get errors with a Google::Compute::ResourceNotFound
279
- # then the disk does not exist and can be created
280
- client.disks.get(:disk => disk, :zone => selflink2name(zone))
281
- rescue Google::Compute::ResourceNotFound
282
- # disk does not exist
283
- # continue provisioning
284
- false
285
- else
286
- true
287
- end
205
+ def project
206
+ locate_config_value(:gce_project)
207
+ end
288
208
 
289
- def wait_for_disk(disk, operation, zone)
290
- Timeout::timeout(300) do
291
- until disk.status == 'DONE'
292
- ui.info(".")
293
- sleep 1
294
- disk = client.zoneOperations.get(:name => disk,
295
- :operation => operation,
296
- :zone => selflink2name(zone))
297
- end
298
- disk.target_link
299
- end
300
- rescue Timeout::Error
301
- ui.error("Timeout exceeded with disk status: " + disk.status)
302
- exit 1
303
- end
209
+ def zone
210
+ locate_config_value(:gce_zone)
211
+ end
304
212
 
305
- def bootstrap_for_node(instance, ssh_host)
306
- bootstrap = Chef::Knife::Bootstrap.new
307
- bootstrap.name_args = [ssh_host]
308
- bootstrap.config[:run_list] = config[:run_list]
309
- bootstrap.config[:ssh_user] = config[:ssh_user]
310
- bootstrap.config[:ssh_port] = config[:ssh_port]
311
- bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
312
- bootstrap.config[:identity_file] = config[:identity_file]
313
- bootstrap.config[:chef_node_name] = config[:chef_node_name] || instance.name
314
- bootstrap.config[:prerelease] = config[:prerelease]
315
- bootstrap.config[:bootstrap_version] = config[:bootstrap_version]
316
- bootstrap.config[:first_boot_attributes] = config[:json_attributes]
317
- bootstrap.config[:distro] = config[:distro]
318
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
319
- bootstrap.config[:template_file] = config[:template_file]
320
- bootstrap.config[:environment] = config[:environment]
321
- bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:encrypted_data_bag_secret)
322
- bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:encrypted_data_bag_secret_file)
323
- bootstrap.config[:secret] = locate_config_value(:secret)
324
- bootstrap.config[:secret_file] = locate_config_value(:secret_file)
325
-
326
- # may be needed for vpc_mode
327
- bootstrap.config[:host_key_verify] = config[:host_key_verify]
328
- # Modify global configuration state to ensure hint gets set by
329
- # knife-bootstrap
330
- Chef::Config[:knife][:hints] ||= {}
331
- Chef::Config[:knife][:hints]["gce"] ||= {}
332
- Chef::Config[:knife][:hints]["google"] ||= {}
333
- bootstrap
334
- end
213
+ def email
214
+ locate_config_value(:gce_email)
215
+ end
335
216
 
336
- def run
337
- $stdout.sync = true
338
- unless @name_args.size > 0
339
- ui.error("Please provide the name of the new server.")
340
- exit 1
341
- end
342
-
343
- begin
344
- zone = client.zones.get(config[:zone] || Chef::Config[:knife][:gce_zone]).self_link
345
- rescue Google::Compute::ResourceNotFound
346
- ui.error("Zone '#{config[:zone] || Chef::Config[:knife][:gce_zone]}' not found.")
347
- exit 1
348
- rescue Google::Compute::ParameterValidation
349
- ui.error("Must specify zone in knife config file or in command line as an option. Try --help.")
350
- exit 1
351
- end
352
-
353
- begin
354
- machine_type = client.machine_types.get(:name => config[:machine_type],
355
- :zone => selflink2name(zone)).self_link
356
- rescue Google::Compute::ResourceNotFound
357
- ui.error("MachineType '#{config[:machine_type]}' not found")
358
- exit 1
359
- end
360
-
361
- # this parameter is a string during the post and boolean otherwise
362
- if config[:auto_restart] then
363
- auto_restart = 'true'
364
- else
365
- auto_restart = 'false'
366
- end
367
-
368
- if config[:auto_migrate] then
369
- auto_migrate = 'MIGRATE'
370
- else
371
- auto_migrate = 'TERMINATE'
372
- end
373
-
374
- (checked_custom, checked_all) = false
375
- begin
376
- image_project = config[:image_project_id]
377
- # use zone url to determine project name
378
- zone =~ Regexp.new('/projects/(.*?)/')
379
- project = $1
380
- if image_project.to_s.empty?
381
- unless checked_custom
382
- checked_custom = true
383
- ui.info("Looking for Image '#{config[:image]}' in Project '#{project}'")
384
- image = client.images.get(:project=>project, :name=>config[:image]).self_link
385
- else
386
- case config[:image].downcase
387
- when /debian/
388
- project = 'debian-cloud'
389
- ui.info("Looking for Image '#{config[:image]}' in Project '#{project}'")
390
- when /centos/
391
- project = 'centos-cloud'
392
- ui.info("Looking for Image '#{config[:image]}' in Project '#{project}'")
393
- end
394
- checked_all = true
395
- image = client.images.get(:project=>project, :name=>config[:image]).self_link
396
- end
397
- else
398
- checked_all = true
399
- project = image_project
400
- image = client.images.get(:project=>project, :name=>config[:image]).self_link
401
- end
402
- ui.info("Found Image '#{config[:image]}' in Project '#{project}'")
403
- rescue Google::Compute::ResourceNotFound
404
- unless checked_all then
405
- retry
406
- else
407
- ui.error("Image '#{config[:image]}' not found")
408
- exit 1
409
- end
410
- end
411
-
412
- begin
413
- boot_disk_size = config[:boot_disk_size].to_i
414
- raise if !boot_disk_size.between?(10, 10000)
415
- rescue
416
- ui.error("Size of the persistent boot disk must be between 10 and 10000 GB.")
417
- exit 1
418
- end
419
-
420
- if config[:boot_disk_name].to_s.empty? then
421
- boot_disk_name = @name_args.first
422
- else
423
- boot_disk_name = config[:boot_disk_name]
424
- end
425
-
426
- ui.info("Waiting for the disk insert operation to complete")
427
- boot_disk_insert = client.disks.insert(:sourceImage => image,
428
- :zone => selflink2name(zone),
429
- :name => boot_disk_name,
430
- :sizeGb => boot_disk_size)
431
- boot_disk_target_link = wait_for_disk(boot_disk_insert, boot_disk_insert.name, zone)
432
-
433
- begin
434
- network = client.networks.get(config[:network]).self_link
435
- rescue Google::Compute::ResourceNotFound
436
- ui.error("Network '#{config[:network]}' not found")
437
- exit 1
438
- end
439
-
440
- metadata = config[:metadata].collect{|pair| Hash[*pair.split('=')] }
441
- network_interface = {'network'=>network}
442
-
443
- if config[:public_ip] == 'EPHEMERAL'
444
- network_interface.merge!('accessConfigs' =>[{"name"=>"External NAT",
445
- "type"=> "ONE_TO_ONE_NAT"}])
446
- elsif config[:public_ip] =~ /\d+\.\d+\.\d+\.\d+/
447
- network_interface.merge!('accessConfigs' =>[{"name"=>"External NAT",
448
- "type"=>"ONE_TO_ONE_NAT", "natIP"=>config[:public_ip] }])
449
- elsif config[:public_ip] == 'NONE'
450
- # do nothing
451
- else
452
- ui.error("Invalid public ip value : #{config[:public_ip]}")
453
- exit 1
454
- end
455
-
456
- ui.info("Waiting for the create server operation to complete")
457
- if !config[:service_account_scopes].any?
458
- zone_operation = client.instances.create(:name => @name_args.first,
459
- :zone => selflink2name(zone),
460
- :machineType => machine_type,
461
- :disks => [{
462
- 'boot' => true,
463
- 'type' => 'PERSISTENT',
464
- 'mode' => 'READ_WRITE',
465
- 'deviceName' => selflink2name(boot_disk_target_link),
466
- 'source' => boot_disk_target_link
467
- }],
468
- :networkInterfaces => [network_interface],
469
- :scheduling => {
470
- 'automaticRestart' => auto_restart,
471
- 'onHostMaintenance' => auto_migrate
472
- },
473
- :metadata => { 'items' => metadata },
474
- :tags => { 'items' => config[:tags] }
475
- )
476
- else
477
- zone_operation = client.instances.create(:name => @name_args.first,
478
- :zone=> selflink2name(zone),
479
- :machineType => machine_type,
480
- :disks => [{
481
- 'boot' => true,
482
- 'type' => 'PERSISTENT',
483
- 'mode' => 'READ_WRITE',
484
- 'deviceName' => selflink2name(boot_disk_target_link),
485
- 'source' => boot_disk_target_link
486
- }],
487
- :networkInterfaces => [network_interface],
488
- :serviceAccounts => [{
489
- 'kind' => 'compute#serviceAccount',
490
- 'email' => config[:service_account_name],
491
- 'scopes' => config[:service_account_scopes]
492
- }],
493
- :scheduling => {
494
- 'automaticRestart' => auto_restart,
495
- 'onHostMaintenance' => auto_migrate
496
- },
497
- :metadata => { 'items'=>metadata },
498
- :tags => { 'items' => config[:tags] }
499
- )
500
- end
501
-
502
- until zone_operation.progress.to_i == 100
503
- ui.info(".")
504
- sleep 1
505
- zone_operation = client.zoneOperations.get(:name=>zone_operation, :operation=>zone_operation.name, :zone=>selflink2name(zone))
506
- end
507
-
508
- ui.info("Waiting for the servers to be in running state")
509
-
510
- @instance = client.instances.get(:name=>@name_args.first, :zone=>selflink2name(zone))
511
- msg_pair("Instance Name", @instance.name)
512
- msg_pair("Machine Type", selflink2name(@instance.machine_type))
513
- msg_pair("Image", selflink2name(config[:image]))
514
- msg_pair("Zone", selflink2name(@instance.zone))
515
- msg_pair("Tags", @instance.tags.has_key?("items") ? @instance.tags["items"].join(",") : "None")
516
- until @instance.status == "RUNNING"
517
- sleep 3
518
- msg_pair("Status", @instance.status.downcase)
519
- @instance = client.instances.get(:name=>@name_args.first, :zone=>selflink2name(zone))
520
- end
521
-
522
- msg_pair("Public IP Address", public_ips(@instance)) unless public_ips(@instance).empty?
523
- msg_pair("Private IP Address", private_ips(@instance))
524
- ui.info("\n#{ui.color("Waiting for server", :magenta)}")
525
-
526
- ui.info("\n")
527
- ui.info(ui.color("Waiting for sshd", :magenta))
528
- wait_for_sshd(ssh_connect_host)
529
- bootstrap_for_node(@instance,ssh_connect_host).run
530
- ui.info("\n")
531
- ui.info("Complete!!")
217
+ def ip_address_for_bootstrap
218
+ ip = locate_config_value(:use_private_ip) ? private_ip_for(server) : public_ip_for(server)
219
+
220
+ raise "Unable to determine instance IP address for bootstrapping" if ip == "unknown"
221
+ ip
222
+ end
223
+
224
+ def instance_name
225
+ @name_args.first
226
+ end
227
+
228
+ def metadata
229
+ locate_config_value(:metadata).each_with_object({}) do |item, memo|
230
+ key, value = item.split("=")
231
+ memo[key] = value
532
232
  end
533
233
  end
234
+
235
+ def boot_disk_size
236
+ locate_config_value(:boot_disk_size).to_i
237
+ end
238
+
239
+ def reset_windows_password
240
+ GoogleComputeWindowsPassword.new(
241
+ project: project,
242
+ zone: zone,
243
+ instance_name: instance_name,
244
+ email: email,
245
+ username: locate_config_value(:winrm_user),
246
+ debug: gcewinpass_debug_mode
247
+ ).new_password
248
+ end
249
+
250
+ def gcewinpass_debug_mode
251
+ Chef::Config[:log_level] == :debug
252
+ end
534
253
  end
535
254
  end