require "lib/knife/knife_factory" require "hooks" module Devops module Executor class ServerExecutor include Hooks #params: # out - container for output data # deploy_info - hash with deploy data define_hook :before_deploy define_hook :after_deploy define_hook :before_create define_hook :after_create #params: # out - container for output data define_hook :before_bootstrap define_hook :after_bootstrap before_deploy :create_run_list def initialize server, out if server @project = Devops::Db.connector.project(server.project) @deploy_env = @project.deploy_env(server.deploy_env) end @knife_instance = KnifeFactory.instance @server = server @out = out @out.class.send(:define_method, :flush) { } unless @out.respond_to?(:flush) end def project= p @project = p end def deploy_env= e @deploy_env = e end def server @server end def create_server options @server = Devops::Model::Server.new({"project" => @project.id, "deploy_env" => @deploy_env.identifier, "created_by" => options["created_by"], "provider" => @deploy_env.provider}) provider = @server.provider_instance mongo = ::Devops::Db.connector begin @out << "Create server...\n" @out.flush @server.run_list = options["run_list"] || [] @server.chef_node_name = options["name"] @server.key = options["key"] || provider.ssh_key i = mongo.image(@deploy_env.image) @server.remote_user = i.remote_user res = {} @out << "\nBefore create hooks...\n" res[:before] = self.run_hook :before_create @out << "Done\n" return false unless provider.create_server(@server, @deploy_env.image, @deploy_env.flavor, @deploy_env.subnets, @deploy_env.groups, @out) mongo.server_insert @server @out << "\nAfter create hooks...\n" res[:after] = self.run_hook :after_create @out << "Done\n" @out.flush DevopsLogger.logger.info "Server with parameters: #{@server.to_hash.inspect} is running" unless options["without_bootstrap"] bootstrap_options = { bootstrap_template: i.bootstrap_template } return two_phase_bootstrap(bootstrap_options) else return 0 end rescue => e @out << e.message DevopsLogger.logger.error e.message roll_back mongo.server_delete @server.id return 5 end end def bootstrap options @out << "\n\nBootstrap...\n" @out.flush k = Devops::Db.connector.key(@server.key) cert_path = k.path options[:cert_path] = k.path @out << "\nBefore bootstrap hooks...\n" res = self.run_hook(:before_bootstrap, @out) @out << "Done\n" if @server.private_ip.nil? @out << "Error: Private IP is null" return false end ja = { :provider => @server.provider, :devops_host => `hostname`.strip } ip = @server.private_ip unless @server.public_ip.nil? ip = @server.public_ip @out << "\nPublic IP is present\n" end address = "#{@server.remote_user}@#{ip}" cmd = 'ssh ' cmd << "-i #{cert_path} " cmd << '-q ' cmd << '-o ConnectTimeout=2 -o ConnectionAttempts=1 ' cmd << "#{address} 'exit'" cmd << " 2>&1" @out << "\nWaiting for SSH..." @out << "Test command: '#{cmd}'\n" @out.flush retries_amount = 0 begin sleep(5) res = `#{cmd}` retries_amount += 1 if retries_amount == max_retries_amount @out << "\nCan not connect to #{address}" @out << "\n" + res DevopsLogger.logger.error "Can not connect with command '#{cmd}':\n#{res}" return false end raise ArgumentError.new("Can not connect with command '#{cmd}' ") unless $?.success? rescue ArgumentError => e retry end provider = @server.provider_instance @server.chef_node_name = provider.create_default_chef_node_name(@server) if @server.chef_node_name.nil? r = @knife_instance.knife_bootstrap(@out, ip, self.bootstrap_options(ja, options)) if r == 0 @out << "Chef node name: #{@server.chef_node_name}\n" ::Devops::Db.connector.server_set_chef_node_name @server @out << "Chef node name has been updated\n" @out << "After bootstrap hooks...\n" res = self.run_hook(:after_bootstrap, @out) @out << "Done\n" DevopsLogger.logger.info "Server with id '#{@server.id}' is bootstraped" else @out << "Can not bootstrap node '#{@server.id}', error code: #{r}" end @out.flush r end def bootstrap_options attributes, options bootstrap_options = [ "-x #{@server.remote_user}", "-i #{options[:cert_path]}", "--json-attributes '#{attributes.to_json}'" ] bootstrap_options.push "-N #{@server.chef_node_name}" if @server.chef_node_name bootstrap_options.push "--sudo" unless @server.remote_user == "root" bootstrap_options.push "-t #{options[:bootstrap_template]}" if options[:bootstrap_template] rl = options[:run_list] bootstrap_options.push "-r #{rl.join(",")}" unless rl.nil?# rl.empty? bootstrap_options.push "-c #{options[:config]}" if options[:config] bootstrap_options end def two_phase_bootstrap options provider = @server.provider_instance mongo = ::Devops::Db.connector options[:run_list] = provider.run_list status = bootstrap(options) if status == 0 if check_server @out << "Server #{@server.chef_node_name} is created" else @out << roll_back mongo.server_delete @server.id return 5 end @out << "\n" @out.flush # run_list = @server.run_list + provider.run_list # @out << "\nRun list: #{run_list.inspect}" # s.options[:run_list] += run_list # @knife_instance.set_run_list(@server.chef_node_name, run_list) status = deploy_server(@project.deploy_info(@deploy_env)) if status != 0 msg = "Failed on chef-client with run list, server with id '#{@server.id}'" DevopsLogger.logger.error msg @out << "\n" + msg + "\n" end else msg = "Failed while bootstraping server with id '#{@server.id}'" DevopsLogger.logger.error msg @out << "\n" + msg + "\n" # @out << roll_back # mongo.server_delete @server.id end status end def check_server @knife_instance.chef_node_list.include?(@server.chef_node_name) and @knife_instance.chef_client_list.include?(@server.chef_node_name) end def unbootstrap k = Devops::Db.connector.key(@server.key) cert_path = k.path i = 0 begin r = `ssh -i #{cert_path} -q #{@server.remote_user}@#{@server.private_ip} rm -Rf /etc/chef` raise(r) unless $?.success? rescue => e DevopsLogger.logger.error "Unbootstrap error: " + e.message i += 1 sleep(1) retry unless i == 5 return e.message end nil end def deploy_server_with_tags tags, deploy_info old_tags_str = nil new_tags_str = nil unless tags.empty? old_tags_str = @knife_instance.tags_list(@server.chef_node_name).join(" ") @out << "Server tags: #{old_tags_str}\n" @knife_instance.tags_delete(@server.chef_node_name, old_tags_str) new_tags_str = tags.join(" ") @out << "Server new tags: #{new_tags_str}\n" cmd = @knife_instance.tags_create(@server.chef_node_name, new_tags_str) unless cmd[1] m = "Error: Cannot add tags '#{new_tags_str}' to server '#{@server.chef_node_name}'" DevopsLogger.logger.error(m) @out << m + "\n" return 3 end DevopsLogger.logger.info("Set tags for '#{@server.chef_node_name}': #{new_tags_str}") end r = deploy_server deploy_info unless tags.empty? @out << "Restore tags\n" cmd = @knife_instance.tags_delete(@server.chef_node_name, new_tags_str) DevopsLogger.logger.info("Deleted tags for #{@server.chef_node_name}: #{new_tags_str}") cmd = @knife_instance.tags_create(@server.chef_node_name, old_tags_str) DevopsLogger.logger.info("Set tags for #{@server.chef_node_name}: #{old_tags_str}") end return r end def deploy_server deploy_info @out << "\nBefore deploy hooks...\n" res = self.run_hook(:before_deploy, @out, deploy_info) @out << "Done\n" @out << "\nRun chef-client on '#{@server.chef_node_name}'\n" cmd = "chef-client --no-color" if deploy_info["use_json_file"] deploy_info.delete("use_json_file") @out << "Build information:\n" json = JSON.pretty_generate(deploy_info) @out << json @out << "\n" file = "#{@server.project}_#{@server.deploy_env}_#{Time.new.to_i}" dir = DevopsConfig.config[:project_info_dir] File.open(File.join(dir, file), "w") do |f| f.write json end @out.flush cmd << " -j http://#{DevopsConfig.config[:address]}:#{DevopsConfig.config[:port]}/#{DevopsConfig.config[:url_prefix]}/v2.0/deploy/data/#{file}" end ip = if @server.public_ip.nil? @server.private_ip else @out << "Public IP detected\n" @server.public_ip end @out.flush k = Devops::Db.connector.key(@server.key) lline = @knife_instance.ssh_stream(@out, cmd, ip, @server.remote_user, k.path) r = /Chef\sClient\sfinished/i if lline[r].nil? 1 else @out << "\nAfter deploy hooks...\n" res = self.run_hook(:after_deploy, @out, deploy_info) @out << "Done\n" 0 end end def delete_from_chef_server node_name { :chef_node => @knife_instance.chef_node_delete(node_name), :chef_client => @knife_instance.chef_client_delete(node_name) } end =begin def delete_etc_chef s, cert_path cmd = "ssh -i #{cert_path} -t -q #{s.remote_user}@#{s.private_ip}" cmd += " sudo " unless s.remote_user == "root" cmd += "rm -Rf /etc/chef" r = `#{cmd}` raise(r) unless $?.success? end =end def delete_server mongo = ::Devops::Db.connector if @server.static? if !@server.chef_node_name.nil? unbootstrap end mongo.server_delete @server.id msg = "Static server '#{@server.id}' is removed" DevopsLogger.logger.info msg return msg, nil end r = delete_from_chef_server(@server.chef_node_name) provider = @server.provider_instance begin r[:server] = provider.delete_server @server rescue Fog::Compute::OpenStack::NotFound, Fog::Compute::AWS::NotFound r[:server] = "Server with id '#{@server.id}' not found in '#{provider.name}' servers" DevopsLogger.logger.warn r[:server] end mongo.server_delete @server.id info = "Server '#{@server.id}' with name '#{@server.chef_node_name}' for project '#{@server.project}-#{@server.deploy_env}' is removed" DevopsLogger.logger.info info r.each{|key, log| DevopsLogger.logger.info("#{key} - #{log}")} return info, r end def roll_back unless @server.id.nil? @out << "Server '#{@server.chef_node_name}' with id '#{@server.id}' is not created\n" @out << delete_from_chef_server(@server.chef_node_name).values.join("\n") begin @out << @server.provider_instance.delete_server(@server) rescue => e @out << e.message end @out << "\nRolled back\n" end end def create_run_list out, deploy_info out << "\nGenerate run list hook...\n" out << "Project run list: #{@project.run_list.join(", ")}\n" out << "Deploy environment run list: #{@deploy_env.run_list.join(", ")}\n" out << "Server run list: #{@server.run_list.join(", ")}\n" rlist = Set.new.merge(@deploy_env.provider_instance.run_list).merge(@project.run_list).merge(@deploy_env.run_list).merge(@server.run_list) if @server.stack stack = Devops::Db.connector.stack(@server.stack) out << "Stack run list: #{stack.run_list.join(", ")}\n" rlist.merge(stack.run_list) end deploy_info["run_list"] = rlist.to_a out << "New deploy run list: #{deploy_info["run_list"].join(", ")}\nRun list has been generated\n\n" end private def max_retries_amount 120 end end end end