lib/setup.rb in forj-0.0.36 vs lib/setup.rb in forj-0.0.37

- old
+ new

@@ -22,83 +22,212 @@ require_relative 'yaml_parse.rb' include YamlParse require_relative 'helpers.rb' include Helpers +require 'encryptor' # gem install encryptor +require 'base64' + +# TODO: To move to a specific module driven by providers. +require 'hpcloud/version' +require 'hpcloud/config' +require 'hpcloud/accounts' +require 'hpcloud/connection' +include HP::Cloud + # # Setup module call the hpcloud functions # module Setup - def setup(sProvider, oConfig, options ) - begin + def setup(oConfig, options ) - Logging.fatal(1, 'No provider specified.') if not sProvider + # TODO: Provide a way to re-edit all or partially elements set up by this function. + begin + Logging.fatal(1, 'No provider specified.') if not oConfig.exist?('provider') - sAccountName = sProvider # By default, the account name uses the same provider name. - sAccountName = options[:account_name] if options[:account_name] + sProvider = oConfig.get('provider') + sAccountName = sProvider # By default, the account name uses the same provider name. + sAccountName = options[:account_name] if options[:account_name] - if sProvider != 'hpcloud' - Logging.fatal(1, "forj setup support only hpcloud. '%s' is currently not supported." % sProvider) - end + if sProvider != 'hpcloud' + Logging.fatal(1, "forj setup support only hpcloud. '%s' is currently not supported." % sProvider) + end - # TODO: Support of multiple providers thanks to fog. - # TODO: Replace this code by our own forj account setup, inspired/derived from hpcloud account::setup + # setting up provider account - Required, while calling external provider tool, like hpcloud. + setup_provider_account(oConfig, sAccountName) - # delegate the initial configuration to hpcloud (unix_cli) - hpcloud_data=File.expand_path('~/.hpcloud/accounts') - if File.exists?(File.join(hpcloud_data, 'hp')) and not File.exists?(File.join(hpcloud_data, sAccountName)) and sAccountName != 'hp' - Logging.info("hpcloud: Copying 'hp' account setup to '%s'" % sAccountName) - Kernel.system('hpcloud account:copy hp %s' % [sAccountName]) + # Implementation of simple credential encoding for build.sh/maestro + save_maestro_creds(oConfig, sAccountName) + + # DNS Setting for Gardener + setup_dns(oConfig, sAccountName) + + # Check/create keypair + keypair_setup(oConfig) + + # Checking cloud connection + Logging.message("Checking cloud connection") + oFC=ForjConnection.new(oConfig) + + Logging.message("Setup '%s' done. Thank you." % sAccountName) + + rescue RuntimeError => e + Logging.fatal(1,e.message) + rescue => e + Logging.fatal(1,"%s\n%s" % [e.message,e.backtrace.join("\n")]) end + end +end - Logging.info("Configuring hpcloud account '%s'" % [sAccountName] ) - command = 'hpcloud account:setup %s' % [sAccountName] - Logging.debug("Executing : '%s'" % command) - case Kernel.system(command) - when false - Logging.fatal(1, "Unable to setup your hpcloud account") - when nil - Logging.fatal(1, "Unable to execute 'hpcloud' cli. Please check hpcloud installation.") +def setup_tenant_name(oConfig, sAccountName) + # Maestro uses fog/openstack to connect to the cloud. It needs Tenant name instead of tenant ID. + # Getting it from Compute connection and set it + + oSSLError=SSLErrorMgt.new # Retry object + Logging.debug("Getting tenants from hpcloud cli libraries") + begin + tenants = Connection.instance.tenants(sAccountName) + rescue => e + if not oSSLError.ErrorDetected(e.message,e.backtrace) + retry end + Logging.fatal(1, 'Network: Unable to connect.') + end + tenant_id = rhGet(oConfig.ExtraGet(:hpc_accounts, sAccountName, :credentials), :tenant_id) + tenant_name = nil + tenants.each { |elem| tenant_name = elem['name'] if elem['id'] == tenant_id } + if tenant_name + Logging.debug("Tenant ID '%s': '%s' found." % [tenant_id, tenant_name]) + hCompute = { :tenant_name => tenant_name } + oConfig.ExtraSet(:forj_accounts, sAccountName, :compute, hCompute) + else + Logging.error("Unable to found the tenant Name for '%s' ID." % tenant_id) + end + oConfig.set('tenants', tenants) +end - if not oConfig.yConfig['default'].has_key?('account') - oConfig.LocalSet('account',sAccountName) - oConfig.SaveConfig +def setup_provider_account(oConfig, sAccountName) + # TODO: Support of multiple providers thanks to fog. + # TODO: Replace this code by our own forj account setup, inspired/derived from hpcloud account::setup + + # delegate the initial configuration to hpcloud (unix_cli) + if File.exists?(File.join($HPC_ACCOUNTS, 'hp')) and not File.exists?(File.join($HPC_ACCOUNTS, sAccountName)) and sAccountName != 'hp' + Logging.info("hpcloud: Copying 'hp' account setup to '%s'" % sAccountName) + Kernel.system('hpcloud account:copy hp %s' % [sAccountName]) + end + + Logging.info("Configuring hpcloud account '%s'" % [sAccountName] ) + command = 'hpcloud account:setup %s' % [sAccountName] + Logging.debug("Executing : '%s'" % command) + case Kernel.system(command) + when false + Logging.fatal(1, "Unable to setup your hpcloud account") + when nil + Logging.fatal(1, "Unable to execute 'hpcloud' cli. Please check hpcloud installation.") + end + + if not oConfig.yConfig['default'].has_key?('account') + oConfig.LocalSet('account',sAccountName) + oConfig.SaveConfig + end + + # Loading HPCloud account setting in Config. + hpc_account_file = File.join($HPC_ACCOUNTS, sAccountName) + + # Maestro compute use openstack. It requires meta tenant_name (not ID). Need to query HPC to get the Project Name from the ID. + oConfig.ExtraLoad(hpc_account_file, :hpc_accounts, sAccountName) + + setup_tenant_name(oConfig, sAccountName) +end + +def setup_dns(oConfig, sAccountName) + sAsk = "Optionally, you can ask Maestro to manage a Domain name. It requires your DNS cloud service to be enabled.\nDo you want to configure it?" + if agree(sAsk) + # Get HPCloud account definition + yHPC = oConfig.ExtraGet(:hpc_accounts, sAccountName) + + # Get Forj account definition + yDNS = oConfig.ExtraGet(:forj_accounts, sAccountName, :dns) + yDNS = {} if not yDNS + + # Getting tenants + tenants = oConfig.get('tenants') + + # Question about DNS Tenant ID + # In HPCloud : credentials/tenant_id + aDNS_TenantIDs = [] + sDNS_TenantIDs = rhGet(yDNS, :tenant_id) + sDNS_TenantIDs = rhGet(yHPC, :credentials, :tenant_id) if not sDNS_TenantIDs and rhExist?(yHPC, :credentials, :tenant_id) > 0 + + Logging.message("Following are the list of know project attached to your credentials:") + tenants.each do | elem | + aDNS_TenantIDs.push(elem['id']) + if sDNS_TenantIDs and elem['id'] == sDNS_TenantIDs + Logging.message("%s - %s" % [ANSI.bold+elem['id']+ANSI.reset, elem['name']]) + else + Logging.message("%s - %s" % [elem['id'], elem['name']]) + end end - # Implementation of simple credential encoding for build.sh/maestro - save_maestro_creds(sAccountName) + sOption = ' [%s]' % aDNS_TenantIDs.join(', ') if aDNS_TenantIDs.length() == 2 + sDNS_TenantID = ask('Enter DNS Tenant ID:%s' % sOption) do |q| + q.default = sDNS_TenantIDs + q.validate = /[\w\d]+/ + end + yDNS[:tenant_id] = sDNS_TenantID.to_s + + # Question about DNS Service + # In HPCloud : regions/dns + if sDNS_TenantID == rhGet(yHPC, :credentials, :tenant_id) + sDNS_Service = rhGet(yHPC, :regions, :dns) + else + aDNS_Services = [] + aDNS_Services.push(rhGet(yDNS, :service)) if rhExist?(yDNS, :service) > 0 - # Check/create keypair - keypair_setup(oConfig) - - rescue RuntimeError => e - Logging.fatal(1,e.message) - rescue => e - Logging.fatal(1,"%s\n%s" % [e.message,e.backtrace.join("\n")]) - end - end + sDNS_Service = ask("Enter DNS Service for the Tenant ID '%s' (ex: region-a.geo-1): " % sDNS_TenantID) do |q| + q.validate = /[\w.-]+/ + end + end + yDNS[:service] = sDNS_Service.to_s + + else + yDNS = {} # Any information about DNS setting is removed. + Logging.message("Maestro won't manage any Domain.") + end + # Question about Domain name + previousDomainName = rhGet(yDNS, :domain_name) if rhExist?(yDNS, :domain_name) > 0 + + sDNS_DomainName = ask('Enter Domain name (puppet requirement) (ex: dev.forj.io):') do |q| + q.default = previousDomainName if previousDomainName + q.validate = /[\w._]+/ + end + yDNS[:domain_name] = sDNS_DomainName.to_s + + oConfig.ExtraSet(:forj_accounts, sAccountName, :dns, yDNS) + forjAccountFile = File.join($FORJ_ACCOUNTS_PATH, sAccountName) + oConfig.ExtraSave(forjAccountFile, :forj_accounts, sAccountName) end def ensure_forj_dirs_exists() # Function to create FORJ paths if missing. # Defining Global variables $FORJ_DATA_PATH = File.expand_path(File.join('~', '.forj')) - $FORJ_ACCOUNT_PATH = File.join($FORJ_DATA_PATH, 'account') # Not currently used... + $FORJ_ACCOUNTS_PATH = File.join($FORJ_DATA_PATH, 'accounts') $FORJ_KEYPAIRS_PATH = File.join($FORJ_DATA_PATH, 'keypairs') $FORJ_CREDS_PATH = File.expand_path(File.join('~', '.cache', 'forj')) - + # TODO: To move to an hpcloud object. $HPC_KEYPAIRS = File.expand_path(File.join('~', '.hpcloud', 'keypairs')) + $HPC_ACCOUNTS = File.expand_path(File.join('~', '.hpcloud', 'accounts')) Helpers.ensure_dir_exists($FORJ_DATA_PATH) - Helpers.ensure_dir_exists($FORJ_ACCOUNT_PATH) + Helpers.ensure_dir_exists($FORJ_ACCOUNTS_PATH) Helpers.ensure_dir_exists($FORJ_KEYPAIRS_PATH) Helpers.ensure_dir_exists($FORJ_CREDS_PATH) end - + def keypair_setup(oConfig) key_path = oConfig.get('keypair_path') Logging.info("Configuring forj keypair '%s'" % [key_path] ) @@ -120,61 +249,79 @@ else if real_key_path != key_path and not oConfig.LocalDefaultExist?('keypair_path') Logging.debug("Saving forj keypair '%s' as default." % [real_key_path] ) oConfig.LocalSet('keypair_path', real_key_path) oConfig.SaveConfig() - end + end end end end -def save_maestro_creds(sAccountName) - # Check required global data - if not $FORJ_CREDS_PATH - Logging.fatal(1, "Internal error: '$FORJ_CREDS_PATH' missing.") - end - if not Helpers.dir_exists?($FORJ_CREDS_PATH) - Logging.fatal(1, "Internal error: '%s' doesn't exist." % $FORJ_CREDS_PATH) - end +def save_maestro_creds(oConfig, sAccountName) + # Check required global data + if not $FORJ_CREDS_PATH + Logging.fatal(1, "Internal error: '$FORJ_CREDS_PATH' missing.") + end + if not Helpers.dir_exists?($FORJ_CREDS_PATH) + Logging.fatal(1, "Internal error: '%s' doesn't exist." % $FORJ_CREDS_PATH) + end - Logging.info("Completing hpcloud account '%s' information." % [sAccountName] ) + Logging.info("Completing hpcloud account '%s' information." % [sAccountName] ) - # TODO Be able to load the previous username if the g64 file exists. - hpcloud_os_user = ask('Enter hpcloud username: ') do |q| - q.validate = /\w+/ - q.default = '' - end + forjAccountFile = File.join($FORJ_ACCOUNTS_PATH, sAccountName) + oConfig.ExtraLoad(forjAccountFile, :forj_accounts, sAccountName) - hpcloud_os_key = ask('Enter hpcloud password: ') do |q| - q.echo = '*' - q.validate = /.+/ - end + forj_user = rhGet(oConfig.ExtraGet(:forj_accounts, sAccountName, :credentials), :os_user) - add_creds = {:credentials => {:hpcloud_os_user=> hpcloud_os_user, :hpcloud_os_key=> hpcloud_os_key}} + hpcloud_os_user = ask('Enter hpcloud username: ') do |q| + q.validate = /\w+/ + q.default = forj_user if forj_user + end - cloud_fog = File.join($FORJ_CREDS_PATH, sAccountName+'.g64') + hpcloud_os_key = ask("Enter hpcloud password for '%s': " % hpcloud_os_user) do |q| + q.echo = '*' + q.validate = /.+/ + end - # Security fix: Remove old temp file with clear password. - old_file = '%s/master.forj-13.5' % [$FORJ_CREDS_PATH] - File.delete(old_file) if File.exists?(old_file) - old_file = '%s/creds' % [$FORJ_CREDS_PATH] - File.delete(old_file) if File.exists?(old_file) + # Checking key file used to encrypt/decrypt passwords + key_file = File.join($FORJ_CREDS_PATH, '.key') + if not File.exists?(key_file) + # Need to create a random key. + entr = { :key => rand(36**10).to_s(36), :salt => Time.now.to_i.to_s, :iv => OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_iv} - hpcloud_creds = File.expand_path('~/.hpcloud/accounts/%s' % [sAccountName]) - creds = YAML.load_file(hpcloud_creds) + Logging.debug("Writing '%s' key file" % key_file) + File.open(key_file, 'w') do |out| + out.write(Base64::encode64(entr.to_yaml)) + end + else + Logging.debug("Loading '%s' key file" % key_file) + encoded_key = IO.read(key_file) + entr = YAML.load(Base64::decode64(encoded_key)) + end + enc_hpcloud_os_key = Base64::strict_encode64(Encryptor.encrypt(:value => hpcloud_os_key, :key => entr[:key], :iv => entr[:iv], :salt => entr[:salt])) - access_key = creds[:credentials][:account_id] - secret_key = creds[:credentials][:secret_key] + cloud_fog = File.join($FORJ_CREDS_PATH, sAccountName+'.g64') - os_user = add_creds[:credentials][:hpcloud_os_user] - os_key = add_creds[:credentials][:hpcloud_os_key] + # Security fix: Remove old temp file with clear password. + old_file = '%s/master.forj-13.5' % [$FORJ_CREDS_PATH] + File.delete(old_file) if File.exists?(old_file) + old_file = '%s/creds' % [$FORJ_CREDS_PATH] + File.delete(old_file) if File.exists?(old_file) - IO.popen('gzip -c | base64 -w0 > %s' % [cloud_fog], 'r+') {|pipe| - pipe.puts('HPCLOUD_OS_USER=%s' % [os_user] ) - pipe.puts('HPCLOUD_OS_KEY=%s' % [os_key] ) - pipe.puts('DNS_KEY=%s' % [access_key] ) - pipe.puts('DNS_SECRET=%s' % [secret_key]) - pipe.close_write - } - Logging.info("'%s' written." % cloud_fog) + hpc_creds = oConfig.ExtraGet(:hpc_accounts, sAccountName, :credentials) + + forj_creds = { :os_user => hpcloud_os_user.to_s, + :os_enckey => enc_hpcloud_os_key + } + oConfig.ExtraSet(:forj_accounts, sAccountName, :credentials, forj_creds) + oConfig.ExtraSave(forjAccountFile, :forj_accounts, sAccountName) + + IO.popen('gzip -c | base64 -w0 > %s' % [cloud_fog], 'r+') {|pipe| + pipe.puts('HPCLOUD_OS_USER=%s' % [hpcloud_os_user] ) + pipe.puts('HPCLOUD_OS_KEY=%s' % [hpcloud_os_key] ) + pipe.puts('DNS_KEY=%s' % [hpc_creds[:account_id]] ) + pipe.puts('DNS_SECRET=%s' % [hpc_creds[:secret_key]]) + pipe.close_write + } + Logging.info("'%s' written." % cloud_fog) end