chef-provisioning-google 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +121 -0
- data/Rakefile +22 -0
- data/lib/chef/provider/google_key_pair.rb +172 -0
- data/lib/chef/provisioning/driver_init/google.rb +3 -0
- data/lib/chef/provisioning/google_driver.rb +5 -0
- data/lib/chef/provisioning/google_driver/client/global_operations.rb +18 -0
- data/lib/chef/provisioning/google_driver/client/google_base.rb +72 -0
- data/lib/chef/provisioning/google_driver/client/google_compute_error.rb +16 -0
- data/lib/chef/provisioning/google_driver/client/instance.rb +64 -0
- data/lib/chef/provisioning/google_driver/client/instances.rb +98 -0
- data/lib/chef/provisioning/google_driver/client/metadata.rb +112 -0
- data/lib/chef/provisioning/google_driver/client/operation.rb +23 -0
- data/lib/chef/provisioning/google_driver/client/operations_base.rb +44 -0
- data/lib/chef/provisioning/google_driver/client/projects.rb +39 -0
- data/lib/chef/provisioning/google_driver/client/zone_operations.rb +18 -0
- data/lib/chef/provisioning/google_driver/credentials.rb +63 -0
- data/lib/chef/provisioning/google_driver/driver.rb +313 -0
- data/lib/chef/provisioning/google_driver/resources.rb +0 -0
- data/lib/chef/provisioning/google_driver/version.rb +7 -0
- data/lib/chef/resource/google_key_pair.rb +50 -0
- data/spec/chef/provisioning/google_driver/client/instances_spec.rb +205 -0
- data/spec/chef/provisioning/google_driver/client/operations_spec.rb +62 -0
- data/spec/chef/provisioning/google_driver/client/projects_spec.rb +162 -0
- data/spec/chef/provisioning/google_driver/client/services_helper.rb +33 -0
- data/spec/spec_helper.rb +0 -0
- metadata +222 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative "google_base"
|
2
|
+
require_relative "metadata"
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
module Provisioning
|
6
|
+
module GoogleDriver
|
7
|
+
module Client
|
8
|
+
# Wraps a Projects service of the GCE API.
|
9
|
+
class Projects < GoogleBase
|
10
|
+
|
11
|
+
def get
|
12
|
+
# The default arguments are already enough for this call.
|
13
|
+
response = make_request(compute.projects.get)
|
14
|
+
raise_if_error(response)
|
15
|
+
Metadata.new(response)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Takes a metadata object retrieved via #get and updates the metadata on GCE
|
19
|
+
# using the projects.set_common_instance_metadata API call.
|
20
|
+
# This fails if the metadata on GCE has been updated since the passed
|
21
|
+
# metadata object has been retrieved via #get.
|
22
|
+
# Note that this omits the API call if the metadata object hasn't changed
|
23
|
+
# locally since it was retrieved via #get.
|
24
|
+
def set_common_instance_metadata(metadata)
|
25
|
+
return nil unless metadata.changed?
|
26
|
+
response = make_request(
|
27
|
+
compute.projects.set_common_instance_metadata,
|
28
|
+
# Default paremeters are sufficient.
|
29
|
+
{},
|
30
|
+
{ items: metadata.items, fingerprint: metadata.fingerprint }
|
31
|
+
)
|
32
|
+
operation_response(response)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative "operations_base"
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
module Provisioning
|
5
|
+
module GoogleDriver
|
6
|
+
module Client
|
7
|
+
# Wraps a ZoneOperations service of the GCE API.
|
8
|
+
class ZoneOperations < OperationsBase
|
9
|
+
|
10
|
+
def operations_service
|
11
|
+
compute.zone_operations
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Chef
|
2
|
+
module Provisioning
|
3
|
+
module GoogleDriver
|
4
|
+
# Access various forms of Google credentials
|
5
|
+
# TODO: load credentials from metadata server when provisioning from a GCE machine
|
6
|
+
class Credentials
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@credentials = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](name)
|
13
|
+
@credentials[name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(name, value)
|
17
|
+
@credentials[name] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.from_hash(h)
|
21
|
+
credentials = self.new
|
22
|
+
h.each do |k, v|
|
23
|
+
credentials[k] = v
|
24
|
+
end
|
25
|
+
credentials.validate!
|
26
|
+
credentials
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validates whether the key settings are present in the credential object and keys are in the correct format.
|
30
|
+
# If no client_email is specified, method will try to load the client_email from the json key.
|
31
|
+
def validate!
|
32
|
+
unless self[:p12_key_path] || self[:json_key_path]
|
33
|
+
raise "Google key path is missing. Options provided: #{self.inspect}"
|
34
|
+
end
|
35
|
+
if self[:json_key_path]
|
36
|
+
json_key_hash = JSON.load(File.open(self[:json_key_path]))
|
37
|
+
|
38
|
+
unless self[:google_client_email]
|
39
|
+
# Try to load client_email from json if not present
|
40
|
+
if json_key_hash["client_email"]
|
41
|
+
self[:google_client_email] = json_key_hash["client_email"]
|
42
|
+
else
|
43
|
+
raise "google_client_email must be specified"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
raise "Invalid Google JSON key, no private key" unless json_key_hash.include?("private_key")
|
48
|
+
elsif self[:p12_key_path]
|
49
|
+
raise "p12 key doesn't exist in the path specified" unless File.exist?(self[:p12_key_path])
|
50
|
+
raise "google_client_email must be specified" unless self[:google_client_email]
|
51
|
+
else
|
52
|
+
raise "json_key_path or p12_key_path is missing. Options provided: #{self}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def load_defaults
|
57
|
+
raise NotImplementedError
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
require "chef/provisioning/driver"
|
2
|
+
require "chef/provisioning/google_driver/credentials"
|
3
|
+
require "chef/provisioning/google_driver/version"
|
4
|
+
require "chef/mixin/deep_merge"
|
5
|
+
require "chef/provisioning/convergence_strategy/install_cached"
|
6
|
+
require "chef/provisioning/convergence_strategy/install_sh"
|
7
|
+
require "chef/provisioning/convergence_strategy/install_msi"
|
8
|
+
require "chef/provisioning/convergence_strategy/no_converge"
|
9
|
+
require "chef/provisioning/transport/ssh"
|
10
|
+
require "chef/provisioning/transport/winrm"
|
11
|
+
require "chef/provisioning/machine/windows_machine"
|
12
|
+
require "chef/provisioning/machine/unix_machine"
|
13
|
+
require "chef/provisioning/machine_spec"
|
14
|
+
|
15
|
+
require_relative "client/instances"
|
16
|
+
require_relative "client/global_operations"
|
17
|
+
require_relative "client/projects"
|
18
|
+
require_relative "client/zone_operations"
|
19
|
+
|
20
|
+
require "google/api_client"
|
21
|
+
require "retryable"
|
22
|
+
require "etc"
|
23
|
+
|
24
|
+
class Chef
|
25
|
+
module Provisioning
|
26
|
+
module GoogleDriver
|
27
|
+
# Provisions machines using the Google SDK
|
28
|
+
# TODO look at the superclass comments for further explanation of the overridden methods in this class
|
29
|
+
class Driver < Chef::Provisioning::Driver
|
30
|
+
|
31
|
+
include Chef::Mixin::DeepMerge
|
32
|
+
|
33
|
+
attr_reader :google, :zone, :project, :instance_client, :global_operations_client, :zone_operations_client, :project_client
|
34
|
+
URL_REGEX = /^google:(.+?):(.+)$/
|
35
|
+
|
36
|
+
# URL scheme:
|
37
|
+
# google:zone
|
38
|
+
def self.from_url(driver_url, config)
|
39
|
+
self.new(driver_url, config)
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(driver_url, config)
|
43
|
+
super
|
44
|
+
|
45
|
+
m = URL_REGEX.match(driver_url)
|
46
|
+
if m.nil?
|
47
|
+
raise "Driver URL [#{driver_url}] must match #{URL_REGEX.inspect}"
|
48
|
+
end
|
49
|
+
# TODO: Move zone into bootstrap_options
|
50
|
+
@zone = m[1]
|
51
|
+
@project = m[2]
|
52
|
+
|
53
|
+
@google = Google::APIClient.new(
|
54
|
+
:application_name => "chef-provisioning-google",
|
55
|
+
:application_version => Chef::Provisioning::GoogleDriver::VERSION
|
56
|
+
)
|
57
|
+
if google_credentials[:p12_key_path]
|
58
|
+
signing_key = Google::APIClient::KeyUtils.load_from_pkcs12(google_credentials[:p12_key_path], "notasecret")
|
59
|
+
elsif google_credentials[:json_key_path]
|
60
|
+
json_private_key = JSON.load(File.open(google_credentials[:json_key_path]))["private_key"]
|
61
|
+
signing_key = Google::APIClient::KeyUtils.load_from_pem(json_private_key, "notasecret")
|
62
|
+
end
|
63
|
+
google.authorization = Signet::OAuth2::Client.new(
|
64
|
+
:token_credential_uri => "https://accounts.google.com/o/oauth2/token",
|
65
|
+
:audience => "https://accounts.google.com/o/oauth2/token",
|
66
|
+
:scope => ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly"],
|
67
|
+
:issuer => google_credentials[:google_client_email],
|
68
|
+
:signing_key => signing_key
|
69
|
+
)
|
70
|
+
google.authorization.fetch_access_token!
|
71
|
+
|
72
|
+
@instance_client = Client::Instances.new(google, project, zone)
|
73
|
+
@project_client = Client::Projects.new(google, project, zone)
|
74
|
+
@global_operations_client =
|
75
|
+
Client::GlobalOperations.new(google, project, zone)
|
76
|
+
@zone_operations_client =
|
77
|
+
Client::ZoneOperations.new(google, project, zone)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.canonicalize_url(driver_url, config)
|
81
|
+
[ driver_url, config ]
|
82
|
+
end
|
83
|
+
|
84
|
+
def allocate_machine(action_handler, machine_spec, machine_options)
|
85
|
+
# TODO how do we handle running `allocate` and there is already a machine in GCE
|
86
|
+
# but no node in chef? We should just start tracking it.
|
87
|
+
if instance_for(machine_spec).nil?
|
88
|
+
name = machine_spec.name
|
89
|
+
operation = nil
|
90
|
+
action_handler.perform_action "creating instance named #{name} in zone #{zone}" do
|
91
|
+
default_options = instance_client.default_create_options(name)
|
92
|
+
options = hash_only_merge(default_options, machine_options[:insert_options])
|
93
|
+
operation = instance_client.create(options)
|
94
|
+
end
|
95
|
+
zone_operations_client.wait_for_done(action_handler, operation)
|
96
|
+
machine_spec.reference = {
|
97
|
+
"driver_version" => Chef::Provisioning::GoogleDriver::VERSION,
|
98
|
+
"allocated_at" => Time.now.utc.to_s,
|
99
|
+
"host_node" => action_handler.host_node,
|
100
|
+
}
|
101
|
+
machine_spec.driver_url = driver_url
|
102
|
+
# %w(is_windows ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
|
103
|
+
%w{ssh_username sudo ssh_gateway key_name}.each do |key|
|
104
|
+
machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def ready_machine(action_handler, machine_spec, machine_options)
|
110
|
+
name = machine_spec.name
|
111
|
+
instance = instance_for(machine_spec)
|
112
|
+
|
113
|
+
if instance.nil?
|
114
|
+
raise "Machine #{name} does not have an instance associated with it, or instance does not exist."
|
115
|
+
end
|
116
|
+
|
117
|
+
if !instance.running?
|
118
|
+
# could be PROVISIONING, STAGING, STOPPING, TERMINATED
|
119
|
+
if %w{STOPPING TERMINATED}.include?(instance.status)
|
120
|
+
action_handler.perform_action "instance named #{name} in zone #{zone} was stopped - starting it" do
|
121
|
+
instance_client.start(name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
instance_client.wait_for_status(action_handler, instance, "RUNNING")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Refresh instance object so we get the new ip address and status
|
128
|
+
instance = instance_for(machine_spec)
|
129
|
+
|
130
|
+
wait_for_transport(action_handler, machine_spec, machine_options, instance)
|
131
|
+
machine_for(machine_spec, machine_options, instance)
|
132
|
+
end
|
133
|
+
|
134
|
+
def destroy_machine(action_handler, machine_spec, machine_options)
|
135
|
+
name = machine_spec.name
|
136
|
+
instance = instance_for(machine_spec)
|
137
|
+
# https://cloud.google.com/compute/docs/instances#checkmachinestatus
|
138
|
+
# TODO Shouldn't we also delete stopped machines?
|
139
|
+
if instance && !%w{STOPPING TERMINATED}.include?(instance.status)
|
140
|
+
operation = nil
|
141
|
+
action_handler.perform_action "destroying instance named #{name} in zone #{zone}" do
|
142
|
+
operation = instance_client.delete(name)
|
143
|
+
end
|
144
|
+
zone_operations_client.wait_for_done(action_handler, operation)
|
145
|
+
end
|
146
|
+
|
147
|
+
strategy = convergence_strategy_for(machine_spec, machine_options)
|
148
|
+
strategy.cleanup_convergence(action_handler, machine_spec)
|
149
|
+
# TODO clean up known_hosts entry
|
150
|
+
end
|
151
|
+
|
152
|
+
def stop_machine(action_handler, machine_spec, machine_options)
|
153
|
+
name = machine_spec.name
|
154
|
+
instance = instance_for(machine_spec)
|
155
|
+
|
156
|
+
if instance.nil?
|
157
|
+
raise "Machine #{name} does not have an instance associated with it, or instance does not exist."
|
158
|
+
end
|
159
|
+
|
160
|
+
unless instance.terminated?
|
161
|
+
unless instance.stopping?
|
162
|
+
action_handler.perform_action "stopping instance named #{name} in zone #{zone}" do
|
163
|
+
instance_client.stop(name)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
instance_client.wait_for_status(action_handler, instance, "TERMINATED")
|
167
|
+
end
|
168
|
+
|
169
|
+
if instance.terminated?
|
170
|
+
Chef::Log.info "Instance #{instance.name} already stopped, nothing to do."
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# TODO make these configurable and find a good place where to put them.
|
175
|
+
def tries
|
176
|
+
Client::GoogleBase::TRIES
|
177
|
+
end
|
178
|
+
|
179
|
+
def sleep
|
180
|
+
Client::GoogleBase::SLEEP_SECONDS
|
181
|
+
end
|
182
|
+
|
183
|
+
def wait_for_transport(action_handler, machine_spec, machine_options, instance)
|
184
|
+
transport = transport_for(machine_spec, machine_options, instance)
|
185
|
+
unless transport.available?
|
186
|
+
if action_handler.should_perform_actions
|
187
|
+
Retryable.retryable(:tries => tries, :sleep => sleep, :matching => /Not done/) do |retries, exception|
|
188
|
+
action_handler.report_progress(" waited #{retries * sleep}/#{tries * sleep}s for instance #{instance.name} to be connectable (transport up and running) ...")
|
189
|
+
raise "Not done" unless transport.available?
|
190
|
+
end
|
191
|
+
action_handler.report_progress "#{machine_spec.name} is now connectable"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def transport_for(machine_spec, machine_options, instance)
|
197
|
+
# TODO winrm
|
198
|
+
# if machine_spec.reference['is_windows']
|
199
|
+
# create_winrm_transport(machine_spec, machine_options, instance)
|
200
|
+
# else
|
201
|
+
create_ssh_transport(machine_spec, machine_options, instance)
|
202
|
+
# end
|
203
|
+
end
|
204
|
+
|
205
|
+
def create_ssh_transport(machine_spec, machine_options, instance)
|
206
|
+
ssh_options = ssh_options_for(machine_spec, machine_options, instance)
|
207
|
+
username = machine_spec.reference["ssh_username"] || machine_options[:ssh_username] || Etc.getlogin
|
208
|
+
if machine_options.has_key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.reference["ssh_username"]
|
209
|
+
Chef::Log.warn("Server #{machine_spec.name} was created with SSH username #{machine_spec.reference['ssh_username']} and machine_options specifies username #{machine_options[:ssh_username]}. Using #{machine_spec.reference['ssh_username']}. Please edit the node and change the chef_provisioning.reference.ssh_username attribute if you want to change it.")
|
210
|
+
end
|
211
|
+
options = {}
|
212
|
+
if machine_spec.reference[:sudo] || (!machine_spec.reference.has_key?(:sudo) && username != "root")
|
213
|
+
options[:prefix] = "sudo "
|
214
|
+
end
|
215
|
+
|
216
|
+
remote_host = instance.determine_remote_host
|
217
|
+
|
218
|
+
#Enable pty by default
|
219
|
+
options[:ssh_pty_enable] = true
|
220
|
+
options[:ssh_gateway] = machine_spec.reference["ssh_gateway"] if machine_spec.reference.has_key?("ssh_gateway")
|
221
|
+
|
222
|
+
Chef::Provisioning::Transport::SSH.new(remote_host, username, ssh_options, options, config)
|
223
|
+
end
|
224
|
+
|
225
|
+
def ssh_options_for(machine_spec, machine_options, instance)
|
226
|
+
result = {
|
227
|
+
:auth_methods => [ "publickey" ],
|
228
|
+
:keys_only => true,
|
229
|
+
:host_key_alias => "#{instance.id}.GOOGLE",
|
230
|
+
}.merge(machine_options[:ssh_options] || {})
|
231
|
+
# TODO right now we only allow keys created for the whole project and specified in the
|
232
|
+
# bootstrap options - look at AWS for other options
|
233
|
+
if machine_options[:key_name]
|
234
|
+
# TODO how do I add keys to config[:private_keys] ?
|
235
|
+
# result[:key_data] = [ get_private_key(machine_options[:key_name]) ]
|
236
|
+
# TODO: what to do if we find multiple valid keys in config[:private_key_paths] ?
|
237
|
+
config[:private_key_paths].each do |path|
|
238
|
+
result[:key_data] = IO.read("#{path}/#{machine_options[:key_name]}") if File.exist?("#{path}/#{machine_options[:key_name]}")
|
239
|
+
end
|
240
|
+
unless result[:key_data]
|
241
|
+
raise "#{machine_options[:key_name]} doesn't exist in private_key_paths:#{config[:private_key_paths]}"
|
242
|
+
end
|
243
|
+
else
|
244
|
+
raise "No key found to connect to #{machine_spec.name} (#{machine_spec.reference.inspect})!"
|
245
|
+
end
|
246
|
+
result
|
247
|
+
end
|
248
|
+
|
249
|
+
def machine_for(machine_spec, machine_options, instance = nil)
|
250
|
+
instance ||= instance_for(machine_spec)
|
251
|
+
|
252
|
+
unless instance
|
253
|
+
raise "Instance for node #{machine_spec.name} has not been created!"
|
254
|
+
end
|
255
|
+
|
256
|
+
# TODO winrm
|
257
|
+
# if machine_spec.reference['is_windows']
|
258
|
+
# Chef::Provisioning::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options))
|
259
|
+
# else
|
260
|
+
Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options))
|
261
|
+
# end
|
262
|
+
end
|
263
|
+
|
264
|
+
def convergence_strategy_for(machine_spec, machine_options)
|
265
|
+
# Tell Ohai that this is an EC2 instance so that it runs the EC2 plugin
|
266
|
+
convergence_options = Cheffish::MergedConfig.new(
|
267
|
+
machine_options[:convergence_options] || {},
|
268
|
+
# TODO what is the right ohai hints file?
|
269
|
+
ohai_hints: { "google" => "" })
|
270
|
+
|
271
|
+
# Defaults
|
272
|
+
unless machine_spec.reference
|
273
|
+
return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(convergence_options, config)
|
274
|
+
end
|
275
|
+
|
276
|
+
# TODO winrm
|
277
|
+
# if machine_spec.reference['is_windows']
|
278
|
+
# Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(convergence_options, config)
|
279
|
+
if machine_options[:cached_installer] == true
|
280
|
+
Chef::Provisioning::ConvergenceStrategy::InstallCached.new(convergence_options, config)
|
281
|
+
else
|
282
|
+
Chef::Provisioning::ConvergenceStrategy::InstallSh.new(convergence_options, config)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def instance_for(machine_spec)
|
287
|
+
if machine_spec.reference
|
288
|
+
if machine_spec.driver_url != driver_url
|
289
|
+
raise "Switching a machine's driver from #{machine_spec.driver_url} to #{driver_url} is not currently supported! Use machine :destroy and then re-create the machine on the new driver."
|
290
|
+
end
|
291
|
+
instance_client.get(machine_spec.name)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
|
297
|
+
# TODO load from file or env variables using common google method
|
298
|
+
# https://cloud.google.com/sdk/gcloud/#gcloud.auth
|
299
|
+
def google_credentials
|
300
|
+
# Grab the list of possible credentials
|
301
|
+
@google_credentials ||= if driver_options[:google_credentials]
|
302
|
+
Credentials.from_hash(driver_options[:google_credentials])
|
303
|
+
else
|
304
|
+
credentials = Credentials.new
|
305
|
+
credentials.load_defaults
|
306
|
+
credentials
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
File without changes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "chef/resource/lwrp_base"
|
2
|
+
|
3
|
+
class Chef::Resource::GoogleKeyPair < Chef::Resource::LWRPBase
|
4
|
+
self.resource_name = self.dsl_name
|
5
|
+
|
6
|
+
# TODO instance specific or project wide?
|
7
|
+
# Right now we only have project wide keys
|
8
|
+
|
9
|
+
provides :google_key_pair
|
10
|
+
|
11
|
+
actions :create, :destroy
|
12
|
+
default_action :create
|
13
|
+
|
14
|
+
# Private key to use as input (will be generated if it does not exist)
|
15
|
+
attribute :private_key_path, :kind_of => String
|
16
|
+
# Public key to use as input (will be generated if it does not exist)
|
17
|
+
attribute :public_key_path, :kind_of => String
|
18
|
+
# List of parameters to the private_key resource used for generation of the key
|
19
|
+
attribute :private_key_options, :kind_of => Hash
|
20
|
+
|
21
|
+
# This applies to both the local keys and the remote key
|
22
|
+
attribute :allow_overwrite, :kind_of => [TrueClass, FalseClass], :default => false
|
23
|
+
|
24
|
+
# TODO: add a `user` attribute which sets the user to login with the key
|
25
|
+
# GCE uses the key user to create a user on the instance, which may duplicate
|
26
|
+
# some logic the user is trying to do with their chef recipes
|
27
|
+
|
28
|
+
def after_created
|
29
|
+
# We default these here so load_current_resource can diff
|
30
|
+
if private_key_path.nil?
|
31
|
+
private_key_path ::File.join(driver.config[:private_key_write_path], "google_default")
|
32
|
+
elsif Pathname.new(private_key_path).relative?
|
33
|
+
private_key_path ::File.join(driver.config[:private_key_write_path], private_key_path)
|
34
|
+
end
|
35
|
+
# TODO you don't actually need to write the private key to disc if it isn't provided
|
36
|
+
# it can be read from the private key, but this code update needs testing
|
37
|
+
if public_key_path.nil?
|
38
|
+
public_key_path ::File.join(driver.config[:private_key_write_path], "google_default.pub")
|
39
|
+
elsif Pathname.new(public_key_path).relative?
|
40
|
+
public_key_path ::File.join(driver.config[:private_key_write_path], public_key_path)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# TODO introduce base class and add this as attribute, like AWS does
|
45
|
+
# Ideally, we won't be creating lots of copies of the same driver object, but it is okay
|
46
|
+
# if we do - they aren't singletons
|
47
|
+
def driver
|
48
|
+
run_context.chef_provisioning.driver_for(run_context.chef_provisioning.current_driver)
|
49
|
+
end
|
50
|
+
end
|