Class: Chef::Provisioning::GoogleDriver::Driver
- Inherits:
-
Driver
- Object
- Driver
- Chef::Provisioning::GoogleDriver::Driver
- Includes:
- Mixin::DeepMerge
- Defined in:
- lib/chef/provisioning/google_driver/driver.rb
Overview
Provisions machines using the Google SDK TODO look at the superclass comments for further explanation of the overridden methods in this class
Constant Summary collapse
- URL_REGEX =
/^google:(.+?):(.+)$/
Instance Attribute Summary collapse
-
#global_operations_client ⇒ Object
readonly
Returns the value of attribute global_operations_client.
-
#google ⇒ Object
readonly
Returns the value of attribute google.
-
#instance_client ⇒ Object
readonly
Returns the value of attribute instance_client.
-
#project ⇒ Object
readonly
Returns the value of attribute project.
-
#project_client ⇒ Object
readonly
Returns the value of attribute project_client.
-
#zone ⇒ Object
readonly
Returns the value of attribute zone.
-
#zone_operations_client ⇒ Object
readonly
Returns the value of attribute zone_operations_client.
Class Method Summary collapse
- .canonicalize_url(driver_url, config) ⇒ Object
-
.from_url(driver_url, config) ⇒ Object
URL scheme: google:zone.
Instance Method Summary collapse
- #allocate_machine(action_handler, machine_spec, machine_options) ⇒ Object
- #convergence_strategy_for(machine_spec, machine_options) ⇒ Object
- #create_ssh_transport(machine_spec, machine_options, instance) ⇒ Object
- #destroy_machine(action_handler, machine_spec, machine_options) ⇒ Object
-
#initialize(driver_url, config) ⇒ Driver
constructor
A new instance of Driver.
- #instance_for(machine_spec) ⇒ Object
- #machine_for(machine_spec, machine_options, instance = nil) ⇒ Object
- #ready_machine(action_handler, machine_spec, machine_options) ⇒ Object
- #sleep ⇒ Object
- #ssh_options_for(machine_spec, machine_options, instance) ⇒ Object
- #stop_machine(action_handler, machine_spec, machine_options) ⇒ Object
- #transport_for(machine_spec, machine_options, instance) ⇒ Object
-
#tries ⇒ Object
TODO make these configurable and find a good place where to put them.
- #wait_for_transport(action_handler, machine_spec, machine_options, instance) ⇒ Object
Constructor Details
#initialize(driver_url, config) ⇒ Driver
Returns a new instance of Driver.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 42 def initialize(driver_url, config) super m = URL_REGEX.match(driver_url) if m.nil? raise "Driver URL [#{driver_url}] must match #{URL_REGEX.inspect}" end # TODO: Move zone into bootstrap_options @zone = m[1] @project = m[2] @google = Google::APIClient.new( :application_name => "chef-provisioning-google", :application_version => Chef::Provisioning::GoogleDriver::VERSION ) if google_credentials[:p12_key_path] signing_key = Google::APIClient::KeyUtils.load_from_pkcs12(google_credentials[:p12_key_path], "notasecret") elsif google_credentials[:json_key_path] json_private_key = JSON.load(File.open(google_credentials[:json_key_path]))["private_key"] signing_key = Google::APIClient::KeyUtils.load_from_pem(json_private_key, "notasecret") end google. = Signet::OAuth2::Client.new( :token_credential_uri => "https://accounts.google.com/o/oauth2/token", :audience => "https://accounts.google.com/o/oauth2/token", :scope => ["https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly"], :issuer => google_credentials[:google_client_email], :signing_key => signing_key ) google..fetch_access_token! @instance_client = Client::Instances.new(google, project, zone) @project_client = Client::Projects.new(google, project, zone) @global_operations_client = Client::GlobalOperations.new(google, project, zone) @zone_operations_client = Client::ZoneOperations.new(google, project, zone) end |
Instance Attribute Details
#global_operations_client ⇒ Object (readonly)
Returns the value of attribute global_operations_client.
33 34 35 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 33 def global_operations_client @global_operations_client end |
#google ⇒ Object (readonly)
Returns the value of attribute google.
33 34 35 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 33 def google @google end |
#instance_client ⇒ Object (readonly)
Returns the value of attribute instance_client.
33 34 35 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 33 def instance_client @instance_client end |
#project ⇒ Object (readonly)
Returns the value of attribute project.
33 34 35 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 33 def project @project end |
#project_client ⇒ Object (readonly)
Returns the value of attribute project_client.
33 34 35 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 33 def project_client @project_client end |
#zone ⇒ Object (readonly)
Returns the value of attribute zone.
33 34 35 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 33 def zone @zone end |
#zone_operations_client ⇒ Object (readonly)
Returns the value of attribute zone_operations_client.
33 34 35 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 33 def zone_operations_client @zone_operations_client end |
Class Method Details
.canonicalize_url(driver_url, config) ⇒ Object
80 81 82 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 80 def self.canonicalize_url(driver_url, config) [ driver_url, config ] end |
.from_url(driver_url, config) ⇒ Object
URL scheme: google:zone
38 39 40 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 38 def self.from_url(driver_url, config) self.new(driver_url, config) end |
Instance Method Details
#allocate_machine(action_handler, machine_spec, machine_options) ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 84 def allocate_machine(action_handler, machine_spec, ) # TODO how do we handle running `allocate` and there is already a machine in GCE # but no node in chef? We should just start tracking it. if instance_for(machine_spec).nil? name = machine_spec.name operation = nil action_handler.perform_action "creating instance named #{name} in zone #{zone}" do = instance_client.(name) = hash_only_merge(, [:insert_options]) operation = instance_client.create() end zone_operations_client.wait_for_done(action_handler, operation) machine_spec.reference = { "driver_version" => Chef::Provisioning::GoogleDriver::VERSION, "allocated_at" => Time.now.utc.to_s, "host_node" => action_handler.host_node, } machine_spec.driver_url = driver_url # %w(is_windows ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key| %w{ssh_username sudo ssh_gateway key_name}.each do |key| machine_spec.reference[key] = [key.to_sym] if [key.to_sym] end end end |
#convergence_strategy_for(machine_spec, machine_options) ⇒ Object
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 264 def convergence_strategy_for(machine_spec, ) # Tell Ohai that this is an EC2 instance so that it runs the EC2 plugin = Cheffish::MergedConfig.new( [:convergence_options] || {}, # TODO what is the right ohai hints file? ohai_hints: { "google" => "" }) # Defaults unless machine_spec.reference return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(, config) end # TODO winrm # if machine_spec.reference['is_windows'] # Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(convergence_options, config) if [:cached_installer] == true Chef::Provisioning::ConvergenceStrategy::InstallCached.new(, config) else Chef::Provisioning::ConvergenceStrategy::InstallSh.new(, config) end end |
#create_ssh_transport(machine_spec, machine_options, instance) ⇒ Object
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 205 def create_ssh_transport(machine_spec, , instance) = (machine_spec, , instance) username = machine_spec.reference["ssh_username"] || [:ssh_username] || Etc.getlogin if .has_key?(:ssh_username) && [:ssh_username] != machine_spec.reference["ssh_username"] Chef::Log.warn("Server #{machine_spec.name} was created with SSH username #{machine_spec.reference['ssh_username']} and machine_options specifies username #{[: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.") end = {} if machine_spec.reference[:sudo] || (!machine_spec.reference.has_key?(:sudo) && username != "root") [:prefix] = "sudo " end remote_host = instance.determine_remote_host #Enable pty by default [:ssh_pty_enable] = true [:ssh_gateway] = machine_spec.reference["ssh_gateway"] if machine_spec.reference.has_key?("ssh_gateway") Chef::Provisioning::Transport::SSH.new(remote_host, username, , , config) end |
#destroy_machine(action_handler, machine_spec, machine_options) ⇒ Object
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 134 def destroy_machine(action_handler, machine_spec, ) name = machine_spec.name instance = instance_for(machine_spec) # https://cloud.google.com/compute/docs/instances#checkmachinestatus # TODO Shouldn't we also delete stopped machines? if instance && !%w{STOPPING TERMINATED}.include?(instance.status) operation = nil action_handler.perform_action "destroying instance named #{name} in zone #{zone}" do operation = instance_client.delete(name) end zone_operations_client.wait_for_done(action_handler, operation) end strategy = convergence_strategy_for(machine_spec, ) strategy.cleanup_convergence(action_handler, machine_spec) # TODO clean up known_hosts entry end |
#instance_for(machine_spec) ⇒ Object
286 287 288 289 290 291 292 293 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 286 def instance_for(machine_spec) if machine_spec.reference if machine_spec.driver_url != driver_url 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." end instance_client.get(machine_spec.name) end end |
#machine_for(machine_spec, machine_options, instance = nil) ⇒ Object
249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 249 def machine_for(machine_spec, , instance = nil) instance ||= instance_for(machine_spec) unless instance raise "Instance for node #{machine_spec.name} has not been created!" end # TODO winrm # if machine_spec.reference['is_windows'] # Chef::Provisioning::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, instance), convergence_strategy_for(machine_spec, machine_options)) # else Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, , instance), convergence_strategy_for(machine_spec, )) # end end |
#ready_machine(action_handler, machine_spec, machine_options) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 109 def ready_machine(action_handler, machine_spec, ) name = machine_spec.name instance = instance_for(machine_spec) if instance.nil? raise "Machine #{name} does not have an instance associated with it, or instance does not exist." end if !instance.running? # could be PROVISIONING, STAGING, STOPPING, TERMINATED if %w{STOPPING TERMINATED}.include?(instance.status) action_handler.perform_action "instance named #{name} in zone #{zone} was stopped - starting it" do instance_client.start(name) end end instance_client.wait_for_status(action_handler, instance, "RUNNING") end # Refresh instance object so we get the new ip address and status instance = instance_for(machine_spec) wait_for_transport(action_handler, machine_spec, , instance) machine_for(machine_spec, , instance) end |
#sleep ⇒ Object
179 180 181 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 179 def sleep Client::GoogleBase::SLEEP_SECONDS end |
#ssh_options_for(machine_spec, machine_options, instance) ⇒ Object
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 225 def (machine_spec, , instance) result = { :auth_methods => [ "publickey" ], :keys_only => true, :host_key_alias => "#{instance.id}.GOOGLE", }.merge([:ssh_options] || {}) # TODO right now we only allow keys created for the whole project and specified in the # bootstrap options - look at AWS for other options if [:key_name] # TODO how do I add keys to config[:private_keys] ? # result[:key_data] = [ get_private_key(machine_options[:key_name]) ] # TODO: what to do if we find multiple valid keys in config[:private_key_paths] ? config[:private_key_paths].each do |path| result[:key_data] = IO.read("#{path}/#{[:key_name]}") if File.exist?("#{path}/#{[:key_name]}") end unless result[:key_data] raise "#{[:key_name]} doesn't exist in private_key_paths:#{config[:private_key_paths]}" end else raise "No key found to connect to #{machine_spec.name} (#{machine_spec.reference.inspect})!" end result end |
#stop_machine(action_handler, machine_spec, machine_options) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 152 def stop_machine(action_handler, machine_spec, ) name = machine_spec.name instance = instance_for(machine_spec) if instance.nil? raise "Machine #{name} does not have an instance associated with it, or instance does not exist." end unless instance.terminated? unless instance.stopping? action_handler.perform_action "stopping instance named #{name} in zone #{zone}" do instance_client.stop(name) end end instance_client.wait_for_status(action_handler, instance, "TERMINATED") end if instance.terminated? Chef::Log.info "Instance #{instance.name} already stopped, nothing to do." end end |
#transport_for(machine_spec, machine_options, instance) ⇒ Object
196 197 198 199 200 201 202 203 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 196 def transport_for(machine_spec, , instance) # TODO winrm # if machine_spec.reference['is_windows'] # create_winrm_transport(machine_spec, machine_options, instance) # else create_ssh_transport(machine_spec, , instance) # end end |
#tries ⇒ Object
TODO make these configurable and find a good place where to put them.
175 176 177 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 175 def tries Client::GoogleBase::TRIES end |
#wait_for_transport(action_handler, machine_spec, machine_options, instance) ⇒ Object
183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/chef/provisioning/google_driver/driver.rb', line 183 def wait_for_transport(action_handler, machine_spec, , instance) transport = transport_for(machine_spec, , instance) unless transport.available? if action_handler.should_perform_actions Retryable.retryable(:tries => tries, :sleep => sleep, :matching => /Not done/) do |retries, exception| action_handler.report_progress(" waited #{retries * sleep}/#{tries * sleep}s for instance #{instance.name} to be connectable (transport up and running) ...") raise "Not done" unless transport.available? end action_handler.report_progress "#{machine_spec.name} is now connectable" end end end |