lib/forj/process/ForjProcess.rb in forj-1.0.7 vs lib/forj/process/ForjProcess.rb in forj-1.0.8

- old
+ new

@@ -29,107 +29,151 @@ INFRA_VERSION = '0.0.37' # Functions for boot - build_forge class ForjCoreProcess def build_forge(sObjectType, hParams) - forge_exist?(sObjectType) + # TODO: To be replaced by a migration task at install phase. + update_keypair_config - o_server = hParams.refresh[:server, :ObjectData] + o_forge = forge_get_or_create(sObjectType, hParams) - boot_options = boot_keypairs(hParams) + # Refresh full data on the server found or created. + server = controller_get(:server, o_forge[:servers, 'maestro'][:id]) + o_forge[:servers, 'maestro'] = server + boot_options = boot_keypairs(server) + # Define the log lines to get and test. config.set(:log_lines, 5) PrcLib.info("Maestro server '%s' id is '%s'.", - o_server[:name], o_server[:id]) + server[:name], server[:id]) # Waiting for server to come online before assigning a public IP. s_status = :checking maestro_create_status(s_status) o_address = hParams.refresh[:public_ip, :ObjectData] - s_status = active_server?(o_server, o_address, boot_options[:keys], - boot_options[:coherent], s_status - ) + s_status = active_server?(server, o_address, boot_options[:keys], + boot_options[:coherent], s_status) - till_server_active(s_status, o_server, o_address, boot_options) + till_server_active(s_status, server, o_address, boot_options) o_forge = get_forge(sObjectType, config[:instance_name], hParams) read_blueprint_implemented(o_forge, hParams) o_forge end - def forge_exist?(sObjectType) + def forge_get_or_create(sObjectType, hParams) o_forge = process_get(sObjectType, config[:instance_name]) if o_forge.empty? || o_forge[:servers].length == 0 PrcLib.high_level_msg("\nBuilding your forge...\n") process_create(:internet_server) + o_forge[:servers, 'maestro'] = hParams.refresh[:server] else - load_existing_forge(o_forge) + o_forge = load_existing_forge(o_forge, hParams) end + o_forge end - def load_existing_forge(o_forge) - o_forge[:servers].each do |oServerToFind| - if /^maestro\./ =~ oServerToFind[:name] - process_get(:server, oServerToFind[:id]) - end - end + def load_existing_forge(o_forge, hParams) PrcLib.high_level_msg("\nChecking your forge...\n") - o_server = data_objects(:server, :ObjectData) + o_server = o_forge[:servers, 'maestro'] if o_server - o_ip = process_query(:public_ip, :server_id => o_server[:id]) - register o_ip[0] if o_ip.length > 0 - process_create(:keypairs) + query = { :server_id => o_server[:id] } + register(o_server) + o_ip = process_query(:public_ip, query) + register(o_ip[0]) if o_ip.length > 0 else PrcLib.high_level_msg("\nYour forge exist, without maestro." \ " Building Maestro...\n") process_create(:internet_server) + o_forge[:servers, 'maestro'] = hParams.refresh[:server] PrcLib.high_level_msg("\nBuilding your forge...\n") end + o_forge end +end - def boot_keypairs(params) - o_server = params[:server, :ObjectData] - h_keys = params[:keypairs] +# Functions for boot - build_forge +class ForjCoreProcess + def boot_keypairs(server) + h_keys = process_create(:keypairs) + keypair = process_get(:keypairs, server[:key_name]) - if h_keys.nil? || o_server[:key_name] != h_keys[:name] - h_keys = process_get(:keypairs, o_server[:key_name]) + h_keys = choose_best_kp(h_keys, keypair, server[:name]) + + h_keys[:keys] = File.join(h_keys[:keypair_path], + h_keys[:private_key_name]) + + unless h_keys[:coherent] + PrcLib.warning("The local keypair '%s' public key and '%s' server's "\ + "public key are different.\n"\ + "You won't be able to access it until "\ + 'you get a copy of the key used to create the server.'\ + "\nPublic key found in the cloud:\n%s", + h_keys[:name], server[:name], keypair[:public_key]) + return h_keys end - private_key_file = File.join(h_keys[:keypair_path], - h_keys[:private_key_name]) - { :keys => private_key_file, :coherent => h_keys[:coherent] } + unless h_keys[:private_key_exist?] + PrcLib.warning("The local keypair '%s' private key '%s' is not found. "\ + "You won't be able to access '%s' until you get a copy"\ + 'of the private key use to create the server.'\ + "\nPublic key found in the cloud:\n%s", + h_keys[:keys], server[:name], keypair[:public_key]) + end + + h_keys end + def choose_best_kp(predef_keypair, server_keypair, server_name) + if server_keypair[:name] != predef_keypair[:name] + if coherent_keypair?(predef_keypair, server_keypair) + PrcLib.warning("Server '%s' is using keypair name '%s' instead of the"\ + " your account keypair name '%s'.\n"\ + 'Your predefined keypair name is compatible with '\ + 'that server. (public key identical), so Forj will use'\ + " '%s' by default", + server_name, server_keypair[:name], + server_keypair[:name], server_keypair[:name]) + return predef_keypair + end + PrcLib.warning("Server '%s' is using keypair name '%s' instead of the "\ + "your account keypair name '%s'.\n"\ + "Forj will try to find and use a local keypair name '%s'"\ + ' instead.', server_name, server_keypair[:name], + predef_keypair[:name], server_keypair[:name]) + return server_keypair + end + predef_keypair + end + def active_server?(o_server, o_address, private_key_file, keypair_coherent, s_status ) if o_server[:attrs][:status] == :active image = server_get_image o_server - s_msg = <<-END -Your forj Maestro server is up and running and is publically accessible -through IP '%s'. + s_msg = format('Your forj Maestro server is up and running and is '\ + "publically accessible through IP '%s'.\n\n"\ + "You can connect to '%s' with:\n"\ + 'ssh %s@%s -o StrictHostKeyChecking=no -i ', + o_address[:public_ip], o_server[:name], + image[:ssh_user], o_address[:public_ip]) -You can connect to '%s' with: -ssh %s@%s -o StrictHostKeyChecking=no -i %s - END - s_msg = format(s_msg, o_address[:public_ip], o_server[:name], - image[:ssh_user], o_address[:public_ip], private_key_file - ) - - unless keypair_coherent - s_msg += "\n" + ANSI.bold( - 'Unfortunatelly' - ) + ' your current keypair is not usable to connect to your server.' \ - "\n" + 'You need to fix this issue to gain access to your server.' + if keypair_coherent + s_msg += private_key_file + else + s_msg += ANSI.red(ANSI.bold('<no valid private key found>')) + "\n\n" + + ANSI.bold('Unfortunatelly') + ', Forj was not able to find a '\ + 'valid keypair to connect to your server.' \ + "\nYou need to fix this issue to gain access to your server." end PrcLib.info(s_msg) o_log = process_get(:server_log, 25)[:attrs][:output] if /cloud-init boot finished/ =~ o_log @@ -221,12 +265,12 @@ PrcLib.warning("No more server activity detected.\n"\ "#{highlight}\n"\ "%s\n"\ "#{highlight}\n"\ "The server '%s' is not providing any output log for"\ - " more than 5 minutes.\nPlease review the current"\ - 'output show below to determine if this a normal '\ + " more than 5 minutes.\nPlease review the current "\ + 'output shown below to determine if this is a normal '\ "situation.\nYou can connect to the server if you "\ "want to.\nTo connect, use:\n"\ 'ssh %s@%s -o StrictHostKeyChecking=no -i %s', o_old_log, o_server[:name], image[:ssh_user], o_address[:public_ip], boot_options[:keys]) @@ -423,11 +467,11 @@ PrcLib.high_level_msg("\n%s\nEnjoy!\n", s_msg) end def display_servers_with_ip(o_forge, s_msg) i_count = 0 - o_forge[:servers].each do |server| + o_forge[:servers].each do |_type, server| next if /^maestro\./ =~ server[:name] register(server) o_ip = process_query(:public_ip, :server_id => server[:id]) if o_ip.length == 0 s_msg += format("\n- %s (No public IP)", server[:name]) @@ -508,18 +552,19 @@ 'network_name' => hParams[:network_name], 'hpcloud_os_region' => hParams[:compute], 'PUPPET_DEBUG' => 'True', 'image_name' => hParams[:image_name], 'key_name' => hParams[:keypair_name], - 'hpcloud_priv' => Base64.strict_encode64( - hpcloud_priv - ).gsub('=', '') # Remove pad + # Remove pad + 'hpcloud_priv' => Base64.strict_encode64(hpcloud_priv).gsub('=', ''), + 'compute_os_auth_url' => hParams[:auth_uri] } if hParams[:dns_service] h_meta['dns_zone'] = hParams[:dns_service] h_meta['dns_tenantid'] = hParams[:dns_tenant_id] + h_meta['dns_auth_url'] = hParams[:auth_uri] end # If requested by user, ask Maestro to instantiate a blueprint. h_meta['blueprint'] = hParams[:blueprint] if hParams[:blueprint] # Add init additionnal git clone steps. @@ -876,18 +921,25 @@ bootstrap = "#{build_tmpl_dir}/bootstrap_build.sh" cmd = format( "%s '%s' '%s' '%s' '%s' '%s' '%s' '%s'", bootstrap, # script - PrcLib.data_path, # $1 = Forj data base dir + # $1 = Forj data base dir + PrcLib.data_path, # $2 = Maestro repository dir hParams[:maestro_repository, :maestro_repo], - config[:bootstrap_dirs], # $3 = Bootstrap directories - config[:bootstrap_extra_dir], # $4 = Bootstrap extra directory - meta_data, # $5 = meta_data (string) - mime_cmd, # $6: mime script file to execute. - mime # $7: mime file generated. + # $3 = Bootstrap directories + hParams[:infra_repository, :infra_repo] + ' ' + + config.get(:bootstrap_dirs, ''), + # $4 = Bootstrap extra directory + config[:bootstrap_extra_dir], + # $5 = meta_data (string) + meta_data, + # $6: mime script file to execute. + mime_cmd, + # $7: mime file generated. + mime ) run_userdata_cmd(cmd, bootstrap, mime) begin @@ -925,15 +977,19 @@ else return false end # end end + # Check files existence - def forj_check_keypairs_files(keypath) + def forj_check_keypairs_files(input_pathbase) key_name = config.get(:keypair_name) - keys_entered = keypair_detect(key_name, keypath) + keypair_path = File.expand_path(File.dirname(input_pathbase)) + keypair_base = File.expand_path(File.basename(input_pathbase)) + keys_entered = keypair_detect(key_name, keypair_path, keypair_base) + if !keys_entered[:private_key_exist?] && !keys_entered[:public_key_exist?] if agree('The key you entered was not found. Do you want to create' \ ' this one?') base_dir = keys_entered[:keypair_path] return create_directory(base_dir) @@ -942,36 +998,75 @@ end end true end - def duplicate_keyname?(keys_imported, keys, key_name) - if keys_imported && keys_imported[:key_basename] != keys[:key_basename] && - Forj.keypairs_path != keys[:keypair_path] - PrcLib.warning("The private key '%s' was assigned to a different private"\ - " key file '%s'.\nTo not overwrite it, we recommend you"\ - ' to choose a different keypair name.', - keys, keys_imported[:key_basename]) - new_key_name = key_name - s_msg = 'Please, provide a different keypair name:' - while key_name == new_key_name - new_key_name = ask(s_msg) do |q| - q.validate = /.+/ - end - new_key_name = new_key_name.to_s - s_msg = 'Incorrect. You have to choose a keypair name different' \ - " than '#{key_name}'. If you want to interrupt, press Ctrl-C and" \ - ' retry later.\nSo, please, provide a different keypair' \ - ' name:' if key_name == new_key_name + # Function to identify if the keypair name has already been imported. + # If so, we can't change the original files used to import it. + # The script will ask for another keypair_name. + # + # It will loop until keyname is new or until files originally used + # is identical. + def check_about_imported_key(setup_keys, key_name) + loop do + keys_imported = nil + if config.local_exist?(key_name.to_sym, :imported_keys) + keys_imported = keypair_detect(key_name, + config.local_get(key_name.to_sym, + :imported_keys)) end - key_name = new_key_name + + return setup_keys if keys_imported.nil? + + unless keys_imported[:private_key_exist?] || + keys_imported[:public_key_exist?] + PrcLib.warning("The local keypair '%s' imported files do not exist "\ + 'anymore. Removed from imported_keys.', key_name) + local_del(key_name.to_sym, :imported_keys) + break + end + + setup_keybase = File.join(setup_keys[:keypair_path], + setup_keys[:key_basename]) + imported_keybase = File.join(keys_imported[:keypair_path], + keys_imported[:key_basename]) + + break if setup_keybase == imported_keybase + + PrcLib.warning("You entered a keypair base file '%s' for keypair name "\ + "'%s'. Originally, this keypair name was created from "\ + "'%s' instead.\n"\ + 'To not overwrite it, we recommend you'\ + ' to choose a different keypair name.', + setup_keybase, key_name, imported_keybase) + key_name = _keypair_files_ask(key_name) config.set(:key_name, key_name) - keys = keypair_detect(key_name, key_path) + + setup_keys = keypair_detect(key_name, + setup_keys[:keypair_path], + setup_keys[:key_basename]) end - keys + setup_keys end + # Function to change the keypair name, as already used. + def _keypair_files_ask(key_name) + new_key_name = key_name + s_msg = 'Please, provide a different keypair base file:' + while key_name == new_key_name + new_key_name = ask(s_msg) do |q| + q.validate = /.+/ + end + new_key_name = new_key_name.to_s + s_msg = 'Incorrect. You have to choose a keypair base file different'\ + " than '#{key_name}'. If you want to interrupt, press Ctrl-C."\ + "\nSo, please, provide a different keypair"\ + ' name:' if key_name == new_key_name + end + new_key_name + end + def create_keys_automatically(keys, private_key_file) return if keys[:private_key_exist?] unless File.exist?(private_key_file) # Need to create a key. ask if we need so. PrcLib.message("The private key file attached to keypair named '%s' is "\ @@ -988,22 +1083,30 @@ end end # Functions for setup class ForjCoreProcess + # Import the keypair base files setup by user in forj keypair files. + # + # This function is can be executed only if we copy files to internal + # forj keypair storage. Otherwise this update is ignored. def save_sequences(private_key_file, forj_private_key_file, public_key_file, forj_public_key_file, key_name ) PrcLib.info('Importing key pair to FORJ keypairs list.') + FileUtils.copy(private_key_file, forj_private_key_file) FileUtils.copy(public_key_file, forj_public_key_file) # Attaching this keypair to the account config.set(:keypair_name, key_name, :name => 'account') - config.set(:keypair_path, forj_private_key_file, :name => 'account') - config.local_set(key_name.to_s, private_key_file, :imported_keys) + config.local_set(key_name.to_sym, private_key_file, :imported_keys) end + # Update the forj keypair base files copy from the original base files. + # + # This function is can be executed only if we copy files to internal + # forj keypair storage. Otherwise this update is ignored. def save_md5(private_key_file, forj_private_key_file, public_key_file, forj_public_key_file ) # Checking source/dest files content if Digest::MD5.file(private_key_file).hexdigest != @@ -1027,79 +1130,139 @@ end end # Functions for setup class ForjCoreProcess - def save_internal_key(forj_private_key_file, keys) + def save_internal_key(keys) # Saving internal copy of private key file for forj use. - config.set(:keypair_path, forj_private_key_file, :name => 'account') + config.set(:keypair_base, keys[:keypair_name], :name => 'account') PrcLib.info("Configured forj keypair '%s' with '%s'", keys[:keypair_name], File.join(keys[:keypair_path], keys[:key_basename]) ) end # keypair_files post setup + # + # This function will get the keypair_files setup by user and it will: + # + # * In case keypair already exist, check if imported files is identical. + # * Create SSH keys if missing (ssh-keygen - create_keys_automatically) + # * exit if :keypair_change is not set to the internal forj dir. + # * For new keys, copy new files and keep the original files import place. + # * For existing keys, update them from their original places (imported from) + # * done def forj_setup_keypairs_files - # Getting Account keypair information - key_name = config[:keypair_name] - key_path = File.expand_path(config[:keypair_files]) + keys = check_setup_keypair - keys_imported = nil - keys_imported = keypair_detect( - key_name, - config.local_get(key_name, :imported_keys) - ) if config.local_exist?(key_name, :imported_keys) - keys = keypair_detect(key_name, key_path) - - keys = duplicate_keyname?(keys_imported, keys, key_name) - private_key_file = File.join(keys[:keypair_path], keys[:private_key_name]) public_key_file = File.join(keys[:keypair_path], keys[:public_key_name]) # Creation sequences create_keys_automatically(keys, private_key_file) - forj_private_key_file = File.join(Forj.keypairs_path, key_name) - forj_public_key_file = File.join(Forj.keypairs_path, key_name + '.pub') + if Forj.keypairs_path != config[:keypair_path] + # Do not save in a config keypair_path not managed by forj. + save_internal_key(keys) + return true + end + forj_private_key_file = File.join(Forj.keypairs_path, keys[:keypair_name]) + forj_public_key_file = File.join(Forj.keypairs_path, + keys[:keypair_name] + '.pub') + # Saving sequences - if keys[:keypair_path] != Forj.keypairs_path - if !File.exist?(forj_private_key_file) || - !File.exist?(forj_public_key_file) - save_sequences(private_key_file, forj_private_key_file, - public_key_file, forj_public_key_file, key_name - ) - else - save_md5(private_key_file, forj_private_key_file, - public_key_file, forj_public_key_file - ) - end + if !File.exist?(forj_private_key_file) || !File.exist?(forj_public_key_file) + save_sequences(private_key_file, forj_private_key_file, + public_key_file, forj_public_key_file, keys[:keypair_name]) + else + save_md5(private_key_file, forj_private_key_file, + public_key_file, forj_public_key_file) end - save_internal_key(forj_private_key_file, keys) - true # forj_setup_keypairs_files successfull + save_internal_key(keys) + true # forj_setup_keypairs_files successful end + def check_setup_keypair + key_name = config[:keypair_name] + setup_keypair_path = File.expand_path(File.dirname(config[:keypair_files])) + setup_keypair_base = File.basename(config[:keypair_files]) + + setup_keys = keypair_detect(key_name, setup_keypair_path, + setup_keypair_base) + + # Request different keypair_name, if exist and already imported from another + # :keypair_files + if config[:keypair_path] == Forj.keypairs_path + check_about_imported_key(setup_keys, key_name) + else + setup_keys + end + end + + # TODO: Change this by a migration function called at install time. + + # Function to convert unclear data structure keypair_path, splitted. + # Update config with keypair_base and keypair_path - forj 1.0.8 + # + # * split it in path and base. + # * Fix existing config about path&base (update_keypair_config) + # * save it in account file respectively as :keypair_path and :keypair_base + # + # Used in keypair_name pre-step at setup time. + # return true to not skip the data. + # + def update_keypair_config(_ = nil) + %w(local account).each do |config_name| + next if config.exist?(:keypair_base, :names => [config_name]) + + keypair_path = config.get(:keypair_path, nil, :name => config_name) + + options = { :name => config_name } + options.merge!(:section => :default) if config_name == 'local' + config.set(:keypair_base, File.basename(keypair_path), options) + config.set(:keypair_path, File.dirname(keypair_path), options) + end + true + end + def forj_dns_settings + config[:dns_settings] = false + + return true unless forj_dns_supported? + s_ask = 'Optionally, you can ask Maestro to use/manage a domain name on' \ " your cloud. It requires your DNS cloud service to be enabled.\nDo" \ ' you want to configure it?' - config.set(:dns_settings, agree(s_ask)) + config[:dns_settings] = agree(s_ask) true end def forj_dns_settings?(sKey) # Return true to ask the question. false otherwise - unless config.get(:dns_settings) + unless config[:dns_settings] section = Lorj.data.first_section(sKey) config.del(sKey, :name => 'account', :section => section) return false # Do not ask end true end + def forj_dns_supported? + unless config[:provider] == 'hpcloud' + PrcLib.message("maestro running under '%s' provider currently do "\ + "support DNS setting.\n", config.get(:provider)) + config[:dns_settings] = false + return false # Do not ask + end + true + end +end + +# Functions for setup +class ForjCoreProcess def setup_tenant_name # TODO: To re-introduce with a Controller call instead. o_ssl_error = SSLErrorMgt.new # Retry object PrcLib.debug('Getting tenants from hpcloud cli libraries') begin @@ -1120,50 +1283,90 @@ else PrcLib.error("Unable to find the tenant Name for '%s' ID.", tenant_id) end @oConfig.set('tenants', tenants) end + + # post process after asking keypair name + # return true go to next step + # return false go back to ask keypair name again + def forj_check_cloud_keypair + key_name = config[:keypair_name] + return true if key_name.nil? + config[:key_cloud_coherence] = false + cloud_key = process_get(:keypairs, key_name) + if !cloud_key.empty? + if cloud_key[:coherent] + config[:key_cloud_coherence] = true + return true + end + else + return true + end + keypair_display(cloud_key) + s_ask = 'Do you still want to create new key?' + PrcLib.fatal(1, 'This keypair name cannot be used. ' \ + 'You may check keypair_path setting ' \ + 'in your account.') unless agree(s_ask) + false + end + + # pre process before asking keypair files + # return true continue to ask keypair files + # return false skip asking keypair files + def forj_cloud_keypair_coherent?(_keypair_files) + if config[:key_cloud_coherence] + PrcLib.message('Your local ssh keypair is detected ' \ + 'and valid to access the box.') + return false + end + true + end end # Funtions for get class ForjCoreProcess def get_forge(sCloudObj, sForgeId, _hParams) s_query = {} - h_servers = [] + servers = {} s_query[:name] = sForgeId o_servers = process_query(:server, s_query) regex = Regexp.new(format('\.%s$', sForgeId)) o_servers.each do |o_server| - o_name = o_server[:name] - h_servers << o_server if regex =~ o_name + name = o_server[:name] + next unless regex =~ name + + type = name.clone + type['.' + sForgeId] = '' + servers[type] = o_server end PrcLib.info('%s server(s) were found under instance name %s ', - h_servers.count, s_query[:name]) + servers.count, s_query[:name]) - o_forge = register(h_servers, sCloudObj) - o_forge[:servers] = h_servers + o_forge = register({}, sCloudObj) + o_forge[:servers] = servers o_forge[:name] = sForgeId o_forge end end # Funtions for destroy class ForjCoreProcess def delete_forge(_sCloudObj, hParams) PrcLib.state('Destroying server(s) of your forge') - forge_serverid = config.get(:forge_server) + forge_serverid = hParams[:forge_server] o_forge = hParams[:forge] - o_forge[:servers].each do|server| + o_forge[:servers].each do|_type, server| next if forge_serverid && forge_serverid != server[:id] register(server) - PrcLib.state("Destroying server '%s'", server[:name]) + PrcLib.state("Destroying server '%s - %s'", server[:name], server[:id]) process_delete(:server) end if forge_serverid.nil? PrcLib.high_level_msg("The forge '%s' has been destroyed. (all servers" \ " linked to the forge)\n", o_forge[:name]) @@ -1176,20 +1379,13 @@ # Functions for ssh class ForjCoreProcess def ssh_connection(sObjectType, hParams) o_forge = hParams[:forge] - o_server = nil - o_forge[:servers].each do|server| - next if hParams[:forge_server] != server[:id] - o_server = server - break - end - # Get server information PrcLib.state('Getting server information') - o_server = process_get(:server, o_server[:id]) + o_server = o_forge[:servers, hParams[:forge_server]] register(o_server) public_ip = ssh_server_public_ip(o_server) ssh_options = ssh_keypair(o_server)