class Kitchen::Driver::Gce
Google Compute Engine driver for Test Kitchen
@author Andrew Leonard <andy@hurricane-ridge.com>
Constants
- SCOPE_ALIAS_MAP
Attributes
state[RW]
Public Instance Methods
auto_migrate?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 437 def auto_migrate? preemptible? ? false : config[:auto_migrate] end
auto_restart?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 441 def auto_restart? preemptible? ? false : config[:auto_restart] end
boot_disk(server_name)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 339 def boot_disk(server_name) disk = Google::Apis::ComputeV1::AttachedDisk.new params = Google::Apis::ComputeV1::AttachedDiskInitializeParams.new disk.boot = true disk.auto_delete = config[:autodelete_disk] params.disk_name = server_name params.disk_size_gb = config[:disk_size] params.disk_type = disk_type_url_for(config[:disk_type]) params.source_image = boot_disk_source_image disk.initialize_params = params disk end
boot_disk_source_image()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 358 def boot_disk_source_image @boot_disk_source ||= image_url end
check_api_call() { || ... }
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 205 def check_api_call(&block) yield rescue Google::Apis::ClientError => e debug("API error: #{e.message}") false else true end
connection()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 161 def connection return @connection unless @connection.nil? @connection = Google::Apis::ComputeV1::ComputeService.new @connection.authorization = authorization @connection.client_options = Google::Apis::ClientOptions.new.tap do |opts| opts.application_name = "kitchen-google" opts.application_version = Kitchen::Driver::GCE_VERSION end @connection end
create(state)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 88 def create(state) @state = state return if state[:server_name] validate! server_name = generate_server_name info("Creating GCE instance <#{server_name}> in project #{project}, zone #{zone}...") operation = connection.insert_instance(project, zone, create_instance_object(server_name)) info("Zone operation #{operation.name} created. Waiting for it to complete...") wait_for_operation(operation) server = server_instance(server_name) state[:server_name] = server_name state[:hostname] = ip_address_for(server) state[:zone] = zone info("Server <#{server_name}> created.") update_windows_password(server_name) info("Waiting for server <#{server_name}> to be ready...") wait_for_server info("GCE instance <#{server_name}> created and ready.") rescue => e error("Error encountered during server creation: #{e.class}: #{e.message}") destroy(state) raise end
create_instance_object(server_name)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 314 def create_instance_object(server_name) inst_obj = Google::Apis::ComputeV1::Instance.new inst_obj.name = server_name inst_obj.disks = [boot_disk(server_name)] inst_obj.machine_type = machine_type_url inst_obj.metadata = instance_metadata inst_obj.network_interfaces = instance_network_interfaces inst_obj.scheduling = instance_scheduling inst_obj.service_accounts = instance_service_accounts unless instance_service_accounts.nil? inst_obj.tags = instance_tags inst_obj end
destroy(state)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 121 def destroy(state) @state = state server_name = state[:server_name] return if server_name.nil? unless server_exist?(server_name) info("GCE instance <#{server_name}> does not exist - assuming it has been already destroyed.") return end info("Destroying GCE instance <#{server_name}>...") wait_for_operation(connection.delete_instance(project, zone, server_name)) info("GCE instance <#{server_name}> destroyed.") state.delete(:server_name) state.delete(:hostname) state.delete(:zone) end
disk_type_url_for(type)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 354 def disk_type_url_for(type) "zones/#{zone}/diskTypes/#{type}" end
env_user()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 392 def env_user ENV["USER"] || "unknown" end
find_zone()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 280 def find_zone zone = zones_in_region.sample raise "Unable to find a suitable zone in #{region}" if zone.nil? zone.name end
generate_server_name()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 328 def generate_server_name name = "tk-#{instance.name.downcase}-#{SecureRandom.hex(3)}" if name.length > 63 warn("The TK instance name (#{instance.name}) has been removed from the GCE instance name due to size limitations. Consider setting shorter platform or suite names.") name = "tk-#{SecureRandom.uuid}" end name.gsub(/([^-a-z0-9])/, "-") end
image_exist?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 248 def image_exist? check_api_call { connection.get_image(image_project, image_name) } end
image_name()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 260 def image_name @image_name ||= config[:image_name] || image_name_for_family(config[:image_family]) end
image_name_for_family(image_family)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 366 def image_name_for_family(image_family) image = connection.get_image_from_family(image_project, image_family) image.name end
image_project()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 264 def image_project config[:image_project].nil? ? project : config[:image_project] end
image_url()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 362 def image_url return "projects/#{image_project}/global/images/#{image_name}" if image_exist? end
instance_metadata()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 375 def instance_metadata metadata = { "created-by" => "test-kitchen", "test-kitchen-instance" => instance.name, "test-kitchen-user" => env_user, } Google::Apis::ComputeV1::Metadata.new.tap do |metadata_obj| metadata_obj.items = metadata.each_with_object([]) do |(k, v), memo| memo << Google::Apis::ComputeV1::Metadata::Item.new.tap do |item| item.key = k item.value = v end end end end
instance_network_interfaces()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 396 def instance_network_interfaces interface = Google::Apis::ComputeV1::NetworkInterface.new interface.network = network_url interface.subnetwork = subnet_url if subnet_url interface.access_configs = interface_access_configs Array(interface) end
instance_scheduling()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 425 def instance_scheduling Google::Apis::ComputeV1::Scheduling.new.tap do |scheduling| scheduling.automatic_restart = auto_restart?.to_s scheduling.preemptible = preemptible?.to_s scheduling.on_host_maintenance = migrate_setting end end
instance_service_accounts()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 449 def instance_service_accounts return if config[:service_account_scopes].nil? || config[:service_account_scopes].empty? service_account = Google::Apis::ComputeV1::ServiceAccount.new service_account.email = config[:service_account_name] service_account.scopes = config[:service_account_scopes].map { |scope| service_account_scope_url(scope) } Array(service_account) end
interface_access_configs()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 415 def interface_access_configs return [] if config[:use_private_ip] access_config = Google::Apis::ComputeV1::AccessConfig.new access_config.name = "External NAT" access_config.type = "ONE_TO_ONE_NAT" Array(access_config) end
ip_address_for(server)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 298 def ip_address_for(server) config[:use_private_ip] ? private_ip_for(server) : public_ip_for(server) end
machine_type_url()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 371 def machine_type_url "zones/#{zone}/machineTypes/#{config[:machine_type]}" end
migrate_setting()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 445 def migrate_setting auto_migrate? ? "MIGRATE" : "TERMINATE" end
name()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 84 def name "Google Compute (GCE)" end
network_url()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 405 def network_url "projects/#{project}/global/networks/#{config[:network]}" end
operation_errors(operation_name)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 534 def operation_errors(operation_name) operation = zone_operation(operation_name) return [] if operation.error.nil? operation.error.errors end
preemptible?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 433 def preemptible? config[:preemptible] end
private_ip_for(server)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 302 def private_ip_for(server) server.network_interfaces.first.network_ip rescue NoMethodError raise "Unable to determine private IP for instance" end
project()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 256 def project config[:project] end
public_ip_for(server)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 308 def public_ip_for(server) server.network_interfaces.first.access_configs.first.nat_ip rescue NoMethodError raise "Unable to determine public IP for instance" end
refresh_rate()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 476 def refresh_rate config[:refresh_rate] end
region()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 268 def region config[:region].nil? ? region_for_zone : config[:region] end
region_for_zone()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 272 def region_for_zone @region_for_zone ||= connection.get_zone(project, zone).region.split("/").last end
server_exist?(server_name)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 252 def server_exist?(server_name) check_api_call { server_instance(server_name) } end
server_instance(server_name)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 294 def server_instance(server_name) connection.get_instance(project, zone, server_name) end
service_account_scope_url(scope)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 459 def service_account_scope_url(scope) return scope if scope.start_with?("https://www.googleapis.com/auth/") "https://www.googleapis.com/auth/#{translate_scope_alias(scope)}" end
subnet_url()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 409 def subnet_url return unless config[:subnet] "projects/#{project}/regions/#{region}/subnetworks/#{config[:subnet]}" end
translate_scope_alias(scope_alias)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 464 def translate_scope_alias(scope_alias) SCOPE_ALIAS_MAP.fetch(scope_alias, scope_alias) end
update_windows_password(server_name)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 187 def update_windows_password(server_name) return unless winrm_transport? username = instance.transport[:username] info("Resetting the Windows password for user #{username} on #{server_name}...") state[:password] = GoogleComputeWindowsPassword.new( project: project, zone: zone, instance_name: server_name, email: config[:email], username: username ).new_password info("Password reset complete on #{server_name} complete.") end
valid_disk_type?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 243 def valid_disk_type? return false if config[:disk_type].nil? check_api_call { connection.get_disk_type(project, zone, config[:disk_type]) } end
valid_machine_type?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 218 def valid_machine_type? return false if config[:machine_type].nil? check_api_call { connection.get_machine_type(project, zone, config[:machine_type]) } end
valid_network?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 223 def valid_network? return false if config[:network].nil? check_api_call { connection.get_network(project, config[:network]) } end
valid_project?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 214 def valid_project? check_api_call { connection.get_project(project) } end
valid_region?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 238 def valid_region? return false if config[:region].nil? check_api_call { connection.get_region(project, config[:region]) } end
valid_subnet?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 228 def valid_subnet? return false if config[:subnet].nil? check_api_call { connection.get_subnetwork(project, region, config[:subnet]) } end
valid_zone?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 233 def valid_zone? return false if config[:zone].nil? check_api_call { connection.get_zone(project, config[:zone]) } end
validate!()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 140 def validate! raise "Project #{config[:project]} is not a valid project" unless valid_project? raise "Either zone or region must be specified" unless config[:zone] || config[:region] raise "'any' is no longer a valid region" if config[:region] == "any" raise "Zone #{config[:zone]} is not a valid zone" if config[:zone] && !valid_zone? raise "Region #{config[:region]} is not a valid region" if config[:region] && !valid_region? raise "Machine type #{config[:machine_type]} is not valid" unless valid_machine_type? raise "Disk type #{config[:disk_type]} is not valid" unless valid_disk_type? raise "Either image family or name must be specified" unless config[:image_family] || config[:image_name] raise "Disk image #{config[:image_name]} is not valid - check your image name and image project" if boot_disk_source_image.nil? raise "Network #{config[:network]} is not valid" unless valid_network? raise "Subnet #{config[:subnet]} is not valid" if config[:subnet] && !valid_subnet? raise "Email address of GCE user is not set" if winrm_transport? && config[:email].nil? warn("Both zone and region specified - region will be ignored.") if config[:zone] && config[:region] warn("Both image family and name specified - image family will be ignored") if config[:image_family] && config[:image_name] warn("Image project not specified - searching current project only") unless config[:image_project] warn("Auto-migrate disabled for preemptible instance") if preemptible? && config[:auto_migrate] warn("Auto-restart disabled for preemptible instance") if preemptible? && config[:auto_restart] end
wait_for_operation(operation)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 505 def wait_for_operation(operation) operation_name = operation.name wait_for_status("DONE") { zone_operation(operation_name) } errors = operation_errors(operation_name) return if errors.empty? errors.each do |error| error("#{error.code}: #{error.message}") end raise "Operation #{operation_name} failed." end
wait_for_server()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 520 def wait_for_server begin instance.transport.connection(state).wait_until_ready rescue error("Server not reachable. Destroying server...") destroy(state) raise end end
wait_for_status(requested_status) { || ... }
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 480 def wait_for_status(requested_status, &block) last_status = "" begin Timeout.timeout(wait_time) do loop do item = yield current_status = item.status unless last_status == current_status last_status = current_status info("Current status: #{current_status}") end break if current_status == requested_status sleep refresh_rate end end rescue Timeout::Error error("Request did not complete in #{wait_time} seconds. Check the Google Cloud Console for more info.") raise end end
wait_time()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 472 def wait_time config[:wait_time] end
winrm_transport?()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 183 def winrm_transport? instance.transport.name.casecmp("winrm") == 0 end
zone()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 276 def zone @zone ||= state[:zone] || config[:zone] || find_zone end
zone_operation(operation_name)
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 530 def zone_operation(operation_name) connection.get_zone_operation(project, zone, operation_name) end
zones_in_region()
click to toggle source
# File lib/kitchen/driver/gce_as.rb, line 287 def zones_in_region connection.list_zones(project).items.select do |zone| zone.status == "UP" && zone.region.split("/").last == region end end