require 'net/ssh'
require 'pathname'
require 'json'
require 'base64'
namespace :exec_commands do
desc "Exec commands Script"
task :exec_commands,[:base64_args,:site_construct_id,:commands,:type,:server_name,:rails_env,:commands_i18n] => :environment do |task,args|
if args.base64_args.present?
tmp = JSON.parse(Base64.decode64(args.base64_args)) rescue {}
args = Rake::TaskArguments.new(tmp.keys, tmp.values)
end
@type = args.type
if args.site_construct_id.present?
@site_construct = SiteConstruct.find(args.site_construct_id)
site_server = SiteServer.where(:server_name=>@site_construct.server_type).first
site_servers = [site_server]
@site_construct.update(:status=>"execing",:infos=>[""])
else
@site_construct = nil
site_servers = SiteServer.where(:server_name.in=>args.server_name.split("////")).to_a
end
if args.type == "exec_all"
Multithread.where(:key=>'execing_commands').each do |thread|
if thread.status["status"] == "error" || thread.status["status"] == "finish"
thread.destroy
elsif thread.updated_at < (Time.now - 5.minute)
thread.destroy
end
end
@thread = Multithread.where(:key=>'execing_commands').first
else
@thread = nil
end
if @thread.nil?
@command_trans = {}
@command_relations = {"upgrade_site"=>'git fetch origin && rails runner "a=Admin::SitesController.new;a.git_reset(%w(origin)[0],%w(update)[0]);while(1) do (Multithread.where(key: %w(update_manager)[0],status: %w(finish)[0]).count!=0 ? break : nil) end;sleep(5)"',
"bundle_update"=>"bundle update && kill -s USR2 `cat tmp/pids/unicorn.pid`",
"restart_site"=>"kill -s TERM `fuser tmp/pids/unicorn.sock tmp/sockets/unicorn.sock tmp/unicorn.sock 2>/dev/null`; rvm use `cat .ruby-version`; bundle exec unicorn_rails -c config/unicorn.rb -D -E {{rails_env}}",
"get_template"=>"mongo --eval 'db.sites.find().pretty()[0][\"template\"]' {{db_name}} --quiet"}
@preserve_output = ["get_template"]
@command_relations.each do |k,v|
@command_trans[k] = I18n.t("client_management.#{k}")
end
rails_envs = ["production", "development"]
rails_envs.each do |env|
@command_relations["start_site_in_env,#{env}"] = "kill -s TERM `fuser tmp/pids/unicorn.sock tmp/sockets/unicorn.sock tmp/unicorn.sock 2>/dev/null`; rvm use `cat .ruby-version`; bundle exec unicorn_rails -c config/unicorn.rb -D -E #{env}"
@command_relations["start_site_in_env\\,#{env}"] = @command_relations["start_site_in_env,#{env}"]
@command_trans["start_site_in_env,#{env}"] = I18n.t("client_management.start_site_in_env",{:env=>env})
@command_trans["start_site_in_env\\,#{env}"] = @command_trans["start_site_in_env,#{env}"]
end
begin
if args.type == "exec_all"
if @thread.nil?
@thread = Multithread.create(:key=>'execing_commands',:status=>{"infos"=>[],"status"=>"execing"})
else
@thread.update(:status=>{"infos"=>[],"status"=>"execing"})
end
end
site_servers.each do |site_server|
ip = site_server.ip
server_port = site_server.port
user = site_server.account
password = site_server.password
@password = password
begin
Net::SSH.start(ip , user , { password: password, port: server_port}) do |ssh|
end
rescue Net::SSH::HostKeyMismatch
system("ssh-keygen -f \"$HOME/.ssh/known_hosts\" -R #{ip}")
rescue Errno::ENOTTY
system("ssh-add \"$HOME/.ssh/id_rsa\"")
end
Net::SSH.start(ip , user , { password: password, port: server_port}) do |ssh|
@site_construct.update!(:infos=>[]) rescue nil
if args.type == 'close_site'
exec_ssh_command_by_sudo_and_see_output(ssh,"chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R",false)
exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'kill -s TERM `fuser #{@site_construct.path}/#{@site_construct.site_name}/tmp/unicorn.sock 2>/dev/null`'",false)
update_infos_for_exec("Finish closing #{@site_construct.site_name}")
@site_construct.update(:status =>"closed")
elsif args.type == 'open_site'
default_rails_env = 'development'
enable_rails_env = ['development','production']
rails_env = @site_construct.rails_env.blank? ? default_rails_env : @site_construct.rails_env
cmd_output = exec_ssh_command_by_sudo_and_see_output(ssh,"ps -o args= -p `cat #{@site_construct.path}/#{@site_construct.site_name}/tmp/pids/unicorn.pid`",false) rescue []
enable_rails_env.each do |env|
if( cmd_output[0].include?(env) rescue false)
rails_env = env
break
elsif( cmd_output[0].include?("No such file or directory") rescue false)
@site_construct.update(:status => "closed")
break
end
end
if args.rails_env.present?
rails_env = args.rails_env
end
@site_construct.update(:rails_env=>rails_env)
update_infos_for_exec("Starting site to #{rails_env}")
outputs = exec_ssh_command_by_sudo_and_see_output(ssh,"sudo -p 'sudo password:' chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R",false)
output = exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name}\nkill -s TERM `fuser tmp/unicorn.sock 2>/dev/null`\nsudo -p \"sudo password:\" kill -s TERM `sudo -p \"sudo password:\" fuser tmp/unicorn.sock 2>/dev/null`\nsudo -p \"sudo password:\" rm -f tmp/pids/unicorn.pid\n rvm use `cat .ruby-version`\nbundle exec unicorn_rails -c config/unicorn.rb -D -E #{rails_env}\n'",false)
if output.include? "bundle install"
update_infos_for_exec("Not yet bundle install")
outputs = exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name}; rvm use `cat .ruby-version`; bundle install'")
if outputs.join("\n").include? "Username for"
update_infos_for_exec("Cannot finish bundle install.")
update_infos_for_exec("This site include an private git project.")
@site_construct.update(:status =>"error")
break
else
exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name}; rvm use `cat .ruby-version`; bundle exec unicorn_rails -c config/unicorn.rb -D -E #{rails_env}'",false)
end
end
update_infos_for_exec("Finish starting #{@site_construct.site_name}")
@site_construct.update(:status =>"finish")
else
if args.type == 'exec_all'
sites = SiteConstruct.where(:server_type=>site_server.server_name).to_a
else
sites = [@site_construct]
end
commands = args.commands
if commands.class == String
commands = commands.split("////").select{|c| c != ""}
elsif commands.nil?
commands = []
end
if commands.include?("{{create_users}}")
commands = ['sudo -p "sudo password:" chmod 777 {{full_site_path}} -R',
'sudo -p "sudo password:" chmod 777 {{full_site_path}}/public -R',
'sudo -p "sudo password:" chmod 777 {{full_site_path}}/app/templates -R',
'sudo -p "sudo password:" useradd --home-dir {{full_site_path}}/app/templates {{site_name}} --no-create-home --badnames',
'sudo -p "sudo password:" bash -l -c \'echo -e "{{site_name}}\n{{site_name}}"|passwd {{site_name}}\'']
end
@commands_i18n = args.commands_i18n # command 客製化翻譯
sites.each do |site_construct|
@site_construct = site_construct
@site_construct.update!(:infos=>[])
exec_ssh_command_by_sudo(ssh,"chmod 777 #{@site_construct.path}/#{@site_construct.site_name} -R")
rails_env = @site_construct.rails_env.blank? ? "development" : @site_construct.rails_env
commands.each_with_index do |command, idx|
@command_i18n = command
update_flag = 1
@command_relations.each do |k,v|
if command.include?("{{#{k}}}")
if @preserve_output.include?(k)
update_flag = 1
else
update_flag = 2 #Not logging commands result
end
end
command = command.gsub("{{#{k}}}",v)
@command_i18n = @command_i18n.gsub("{{#{k}}}",@command_trans[k])
end
if @commands_i18n
tmp = @commands_i18n[idx]
@command_i18n = tmp if tmp
end
command = command.gsub("{{rails_env}}",rails_env)
command = command.gsub("{{full_site_path}}",@site_construct.full_site_path)
command = command.gsub("{{site_name}}",@site_construct.site_name)
command = command.gsub("{{db_name}}",@site_construct.db_name)
@command_i18n = @command_i18n.gsub("{{rails_env}}",rails_env)
@command_i18n = @command_i18n.gsub("{{full_site_path}}",@site_construct.full_site_path)
@command_i18n = @command_i18n.gsub("{{site_name}}",@site_construct.site_name)
command = command.gsub("'","'\"'\"'")
exec_ssh_command_by_sudo_and_see_output(ssh,"bash -l -c 'cd #{@site_construct.path}/#{@site_construct.site_name};#{command}'", update_flag)
@command_i18n = nil
end
end
end
end
end
if !@thread.nil?
@thread.update(:status=>@thread.status.merge({"status"=>"finish"}))
elsif @site_construct
@site_construct.update(:status =>"finish")
end
rescue => e
if !@thread.nil?
@thread.update(:status=>{"infos"=>@thread.status["infos"].push(e.message),"status"=>"error"})
@thread.update(:status=>{"infos"=>@thread.status["infos"].push(e.backtrace.join("\n")),"status"=>"error"})
end
if !@site_construct.nil?
@site_construct.update(:status =>"error",:infos=>@site_construct.infos.push("#{e.message}"))
@site_construct.update(:status =>"error",:infos=>@site_construct.infos.push("#{e.backtrace.join("\n")}"))
end
end
end
end
def exec_ssh_command_by_sudo(session,command)
output = session.exec!("echo '#{@password}' | sudo -S #{command}")
# output = session.exec!("echo '#{@password}' | sudo -S -s #{command}")
if output.include?("sudo:") && output.include?("command not found")
output = session.exec!(command)
end
return output.encode!("UTF-8", :invalid => :replace, :undef => :replace, :replace => '')
end
def update_infos_for_exec(info,update_last=false,update_array=false)
return if @site_construct.nil?
if update_last && !@site_construct.infos.empty?
if @remove_last_line
@site_construct.infos[-1] = info.to_s
else
@site_construct.infos[-1] += info.to_s
end
else
if @remove_last_line
@site_construct.infos = @site_construct.infos[0...-1]
end
if update_array
@site_construct.infos += info
else
@site_construct.infos.push(info.to_s)
end
end
@site_construct.save!
return @site_construct.infos
end
def update_thread_infos_for_exec(info,update_last=false,update_array=false)
if update_last && !@thread.status["infos"].empty?
if @remove_last_line
@thread.status["infos"][-1] = info.to_s
else
@thread.status["infos"][-1] += info.to_s
end
else
if @remove_last_line
@thread.status["infos"] = @thread.status["infos"][0...-1]
end
if update_array
@thread.status["infos"] += info
else
@thread.status["infos"].push(info.to_s)
end
end
@thread.save!
return @thread.status["infos"]
end
def exec_ssh_command_and_see_output(session,command,update_flag=1,output_string=false)
outputs = []
@flag = (@type == "exec_all")
command_i18n = command
update = (update_flag == true || update_flag == 1 || update_flag == 2)
update_outputs = (update_flag == true || update_flag == 1)
if update
command_i18n = @command_i18n if @command_i18n
if @site_construct
update_thread_infos_for_exec(["Execing #{command_i18n} on on #{@site_construct.domain_name}",""],false,true) if @flag
update_infos_for_exec(["Execing #{command_i18n}",""],false,true)
elsif @thread
update_thread_infos_for_exec(["Execing #{command_i18n}...",""],false,true)
end
end
session.open_channel do |channel|
channel.request_pty do |channel, success|
channel.exec("LANG=en.UTF8 #{command}") do |ch, success|
abort "could not execute command: #{command}" unless success
channel.on_data do |ch, data|
data_str = data.to_s
data_str.encode!("UTF-8", :invalid => :replace, :undef => :replace, :replace => '')
if data_str.include?("sudo password:") || data_str.include?("Password:")
channel.send_data "#{@password}\n"
else
print data_str unless @no_stdout
data_str.gsub!(/\r\n/, "\n")
next if data_str.length == 0
output_lines = data_str.split("\n", -1)
first_output = output_lines[0]
if first_output
if outputs.count != 0
outputs[-1] += first_output
@remove_last_line = false
outputs[-1] = outputs[-1].sub(/(.+)\r(.+)/) do |s|
@remove_last_line = true
first_output = $2
end
else
outputs << first_output
end
if update_outputs
update_thread_infos_for_exec(first_output,true) if @flag
update_infos_for_exec(first_output,true)
end
@remove_last_line = false
new_arr = output_lines[1..-1]
new_arr = new_arr.map do |output_line|
output_line = output_line.sub(/(.+)\r(.+)/, '\2')
end
outputs += new_arr
if update_outputs
update_thread_infos_for_exec(new_arr,false,true) if @flag
update_infos_for_exec(new_arr,false,true)
end
end
end
end
channel.on_close do |ch|
if update
if @site_construct
update_thread_infos_for_exec("Finish execing #{command_i18n} on #{@site_construct.domain_name}") if @flag
update_infos_for_exec("Finish execing #{command_i18n}")
elsif @thread
update_thread_infos_for_exec("Finish execing #{command_i18n}")
end
end
end
end
end
end
session.loop
if output_string
return outputs.join("\n")
else
return outputs
end
end
def exec_ssh_command_by_sudo_and_see_output(session,command,update=1,output_string=false)
outputs = exec_ssh_command_and_see_output(session,command,update)
tmp = outputs.join("\n")
if tmp.include?("Permission denied") || tmp.include?("Operation not permitted")
outputs = exec_ssh_command_and_see_output(session,"sudo -p 'sudo password:' #{command}",update)
tmp = outputs.join("\n")
end
if output_string
return tmp
else
return outputs
end
end
end