diff --git a/devops-service/app/api2/handlers/deploy.rb b/devops-service/app/api2/handlers/deploy.rb index 9efc066..491b564 100644 --- a/devops-service/app/api2/handlers/deploy.rb +++ b/devops-service/app/api2/handlers/deploy.rb @@ -3,65 +3,56 @@ require "commands/status" require "workers/deploy_worker" module Devops - module Version2_0 + module API2_0 module Handler class Deploy extend DeployCommands extend StatusCommands - def self.deploy - lambda { - check_privileges("server", "x") - # TODO: send message - #broadcast(:devops_deploy, "deploy") - r = create_object_from_json_body - names = check_array(r["names"], "Parameter 'names' should be a not empty array of strings") - tags = check_array(r["tags"], "Parameter 'tags' should be an array of strings", String, true) || [] + def initialize req, params + @request = req + @params = params + end - servers = settings.mongo.servers(nil, nil, names, true) - halt(404, "No reserved servers found for names '#{names.join("', '")}'") if servers.empty? - keys = {} - servers.sort_by!{|s| names.index(s.chef_node_name)} - if r.key?("trace") - stream() do |out| - status = [] - begin - servers.each do |s| - project = begin - settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - rescue InvalidPrivileges, RecordNotFound => e - out << e.message + "\n" - status.push 2 - next - end - res = deploy_server_proc.call(out, s, settings.mongo, tags) - status.push(res) - end - out << create_status(status) - rescue IOError => e - logger.error e.message - break - end - end # stream - else - dir = DevopsService.config[:report_dir_v2] - files = [] - uri = URI.parse(request.url) - servers.each do |s| - project = begin - settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] - rescue InvalidPrivileges, RecordNotFound => e - next - end - jid = DeployWorker.perform_async(dir, s.to_hash, tags, request.env['REMOTE_USER'], DevopsService.config) - logger.info "Job '#{jid}' has been started" - uri.path = "#{DevopsService.config[:url_prefix]}/v2.0/report/" + jid - files.push uri.to_s - end - sleep 1 - json files + def deploy names, tags + dir = DevopsConfig.config[:report_dir_v2] + files = [] + uri = URI.parse(@request.url) + servers(names).each do |s| + project = begin + Devops::Db.connector.check_project_auth s.project, s.deploy_env, @request.env['REMOTE_USER'] + rescue InvalidPrivileges, RecordNotFound => e + next end - } + jid = DeployWorker.perform_async(dir, s.to_hash, tags, @request.env['REMOTE_USER'], DevopsConfig.config) + files.push jid + end + files + end + + def deploy_stream out, names, tags + status = [] + servers(names).each do |s| + project = begin + Devops::Db.connector.check_project_auth s.project, s.deploy_env, @request.env['REMOTE_USER'] + rescue InvalidPrivileges, RecordNotFound => e + out << e.message + "\n" + status.push 2 + next + end + res = deploy_server_proc.call(out, s, tags) + status.push(res) + end + out << create_status(status) + rescue RecordNotFound => e + out << e.message + end + + def servers names + servers = Devops::Db.connector.servers(nil, nil, names, true) + raise RecordNotFound.new("No reserved servers found for names '#{names.join("', '")}'") if servers.empty? + servers.sort_by!{|s| names.index(s.chef_node_name)} + servers end end end diff --git a/devops-service/app/api2/handlers/report.rb b/devops-service/app/api2/handlers/report.rb index 66e2a99..9120a1c 100644 --- a/devops-service/app/api2/handlers/report.rb +++ b/devops-service/app/api2/handlers/report.rb @@ -1,61 +1,62 @@ +require_relative "request_handler" + module Devops module Version2_0 module Handler - class Report + class Report < RequestHandler - def self.reports_all - lambda { - options = {} - ["project", "deploy_env", "type", "created_by", "date_from", "date_to", "sort", "status", "max_number", "chef_node_name"].each do |k| - options[k] = params[k] unless params[k].nil? - end - attributes_keys = params.keys.select{|k| k =~ /attributes\.*/} - attributes_keys.each do |ak| - options[ak] = params[ak] - end - json Devops::Db.connector.reports(options).map{|r| r.to_hash} - } + def initialize params + @params = params end - def self.reports_latest - lambda { - options = {} - ["project", "deploy_env", "type", "created_by", "date_from", "date_to", "sort", "status", "chef_node_name"].each do |k| - options[k] = params[k] unless params[k].nil? - end - attributes_keys = params.keys.select{|k| k =~ /attributes\.*/} - attributes_keys.each do |ak| - options[ak] = params[ak] - end - json Devops::Db.connector.latest_reports(options).map{|r| r.to_hash} - } + def options + options = {} + ["project", "deploy_env", "type", "created_by", "date_from", "date_to", "sort", "status", "chef_node_name", "max_number"].each do |k| + options[k] = @params[k] unless @params[k].nil? + end + attributes_keys = @params.keys.select{|k| k =~ /attributes\.*/} + attributes_keys.each do |ak| + options[ak] = @params[ak] + end + options end - def self.attributes_all - lambda{ - json Devops::Db.connector.reports_attributes_values(params["name"]) - } + def all + Devops::Db.connector.reports(options) end - def self.report - lambda{ - begin - r = Devops::Db.connector.report(params[:id]) - file = r.file - return [404, "Report '#{params[:id]}' does not exist"] unless File.exists? file - @text = Rack::Utils.escape_html(File.read(file)) - @done = completed?(params[:id]) - rescue RecordNotFound => e - if task_status(params[:id]) == Worker::STATUS::IN_QUEUE - @text = "Task '#{params[:id]}' has been queued" - @done = false - else - raise e - end - end - erb :index - } + def all_latest + Devops::Db.connector.latest_reports(options()) end + + def attributes name + Devops::Db.connector.reports_attributes_values(name) + end + + def report id + r = Devops::Db.connector.report(id) + file = r.file + raise RecordNotFound.new("Report '#{id}' does not exist") unless File.exists? file + return Rack::Utils.escape_html(File.read(file)), completed?(id) + rescue RecordNotFound => e + if status(id) == Worker::STATUS::IN_QUEUE + return "Task '#{id}' has been queued", false + else + raise e + end + end + + def status id + Sidekiq.redis do |connection| + connection.hget("devops", id) + end + end + + def completed? id + r = self.status(id) + r == "completed" or r == "failed" + end + end end end diff --git a/devops-service/app/api2/handlers/request_handler.rb b/devops-service/app/api2/handlers/request_handler.rb new file mode 100644 index 0000000..8a6d70b --- /dev/null +++ b/devops-service/app/api2/handlers/request_handler.rb @@ -0,0 +1,12 @@ +module Devops + module API2_0 + module Handler + class RequestHandler + def initialize request, params + @request = request + @params = params + end + end + end + end +end diff --git a/devops-service/app/api2/handlers/script.rb b/devops-service/app/api2/handlers/script.rb index 9887063..2d58401 100644 --- a/devops-service/app/api2/handlers/script.rb +++ b/devops-service/app/api2/handlers/script.rb @@ -1,125 +1,96 @@ require "providers/provider_factory" require "fileutils" require "commands/status" +require_relative "request_handler" module Devops - module Version2_0 + module API2_0 module Handler - class Script + class Script < RequestHandler - def self.get_scripts - lambda { - check_privileges("script", "r") - res = [] - Dir.foreach(DevopsService.config[:scripts_dir]) {|f| res.push(f) unless f.start_with?(".")} - json res - } + def scripts + res = [] + Dir.foreach(DevopsConfig.config[:scripts_dir]) {|f| res.push(f) unless f.start_with?(".")} end - def self.execute_command - lambda { - check_privileges("script", "x") - user = request.env['REMOTE_USER'] - s = ::Devops::Db.connector.server_by_chef_node_name params[:node_name] - ::Devops::Db.connector.check_project_auth s.project, s.deploy_env, user - cert = ::Devops::Db.connector.key s.key - cmd = request.body.read - addr = "#{s.remote_user}@#{s.public_ip || s.private_ip}" - ssh_cmd = "ssh -i %s #{addr} '#{cmd}'" - stream() do |out| - begin - out << ssh_cmd % File.basename(cert.path) - out << "\n" - IO.popen((ssh_cmd % cert.path) + " 2>&1") do |so| - while line = so.gets do - out << line - end - end - out << "\nDone" - rescue IOError => e - logger.error e.message - end + def execute_command out, cmd, node_name + user = @request.env['REMOTE_USER'] + s = ::Devops::Db.connector.server_by_chef_node_name node_name + ::Devops::Db.connector.check_project_auth s.project, s.deploy_env, user + cert = ::Devops::Db.connector.key s.key + addr = "#{s.remote_user}@#{s.public_ip || s.private_ip}" + ssh_cmd = "ssh -i %s #{addr} '#{cmd}'" + out << ssh_cmd % File.basename(cert.path) + out << "\n" + IO.popen((ssh_cmd % cert.path) + " 2>&1") do |so| + while line = so.gets do + out << line end - } + end + out << "\nDone" end - def self.run_script - lambda { - check_privileges("script", "x") - file_name = params[:script_name] - @file = File.join(DevopsService.config[:scripts_dir], check_filename(file_name, "Parameter 'script_name' must be a not empty string", false)) - halt(404, "File '#{file_name}' does not exist") unless File.exists?(@file) - body = create_object_from_json_body - nodes = check_array(body["nodes"], "Parameter 'nodes' must be a not empty array of strings") - p = check_array(body["params"], "Parameter 'params' should be a not empty array of strings", String, true) + def run_script out, file_name, nodes, script_params + file = File.join(DevopsConfig.config[:scripts_dir], file_name) + unless File.exists?(file) + out << "File '#{file_name}' does not exist\n" + return + end servers = ::Devops::Db.connector.servers_by_names(nodes) - return [404, "No servers found for names '#{nodes.join("', '")}'"] if servers.empty? - user = request.env['REMOTE_USER'] + if servers.empty? + out << "No servers found for names '#{nodes.join("', '")}'\n" + return + end + user = @request.env['REMOTE_USER'] servers.each do |s| ::Devops::Db.connector.check_project_auth s.project, s.deploy_env, user end - stream() do |out| - begin - status = [] - servers.each do |s| - cert = begin - ::Devops::Db.connector.key s.key - rescue - out << "No key found for '#{s.chef_node_name}'" - status.push 2 - next - end - ssh_cmd = "ssh -i #{cert.path} #{s.remote_user}@#{s.public_ip || s.private_ip} 'bash -s' < %s" - out << "\nRun script on '#{s.chef_node_name}'\n" - unless p.nil? - ssh_cmd += " " + p.join(" ") - end - out << (ssh_cmd % [params[:script_name]]) - out << "\n" + status = [] + servers.each do |s| + cert = begin + ::Devops::Db.connector.key s.key + rescue + out << "No key found for '#{s.chef_node_name}'" + status.push 2 + next + end + ssh_cmd = "ssh -i #{cert.path} #{s.remote_user}@#{s.public_ip || s.private_ip} 'bash -s' < %s" + out << "\nRun script on '#{s.chef_node_name}'\n" + unless script_params.nil? + ssh_cmd += " " + script_params.join(" ") + end + out << (ssh_cmd % [file_name]) + out << "\n" - begin - IO.popen( (ssh_cmd % [@file]) + " 2>&1") do |so| - while line = so.gets do - out << line - end - so.close - status.push $?.to_i - end - rescue IOError => e - logger.error e.message - out << e.message - status.push 3 + begin + IO.popen( (ssh_cmd % [file]) + " 2>&1") do |so| + while line = so.gets do + out << line end + so.close + status.push $?.to_i end - out << create_status(status) rescue IOError => e logger.error e.message + out << e.message + status.push 3 end end - } + out << create_status(status) end - def self.create_script - lambda { - check_privileges("script", "w") - file_name = params[:script_name] - file = File.join(settings.scripts_dir, check_filename(file_name, "Parameter 'script_name' must be a not empty string")) - halt_response("File '#{file_name}' already exist") if File.exists?(file) - File.open(file, "w") {|f| f.write(request.body.read)} - create_response("File '#{params[:script_name]}' created", nil, 201) - } + def create_script file_name + file = File.join(settings.scripts_dir, file_name) + raise RecordNotFound.new("File '#{file_name}' already exist") if File.exists?(file) + File.open(file, "w") {|f| f.write(@request.body.read)} end - def self.delete_script - lambda { - check_privileges("script", "w") - file_name = params[:script_name] - file = File.join(settings.scripts_dir, check_filename(file_name, "Parameter 'script_name' must be a not empty string")) - halt_response("File '#{file_name}' does not exist", 404) unless File.exists?(file) - FileUtils.rm(file) - create_response("File '#{params[:script_name]}' deleted") - } + def delete_script file_name + file = File.join(settings.scripts_dir, file_nsme) + raise RecordNotFound.new("File '#{file_name}' does not exist", 404) unless File.exists?(file) + FileUtils.rm(file) end + end end end diff --git a/devops-service/app/api2/handlers/status.rb b/devops-service/app/api2/handlers/status.rb deleted file mode 100644 index 0f2eab2..0000000 --- a/devops-service/app/api2/handlers/status.rb +++ /dev/null @@ -1,22 +0,0 @@ -require "sidekiq" - -module Devops - module Version2_0 - module Handler - class Status - - def self.get_status - lambda { - r = Sidekiq.redis do |connection| - connection.hget("devops", params[:id]) - end - return [404, "Job with id '#{params[:id]}' not found"] if r.nil? - r - } - end - - end - end - end -end - diff --git a/devops-service/app/api2/routes/deploy.rb b/devops-service/app/api2/routes/deploy.rb index ea881a3..045ec9f 100644 --- a/devops-service/app/api2/routes/deploy.rb +++ b/devops-service/app/api2/routes/deploy.rb @@ -1,6 +1,6 @@ module Devops - module Version2_0 + module API2_0 module Routes module DeployRoutes @@ -20,7 +20,36 @@ module Devops # } # # * *Returns* : text stream - app.post_with_headers "/deploy", :headers => [:content_type], &Devops::Version2_0::Handler::Deploy.deploy + app.post_with_headers "/deploy", :headers => [:content_type] do + check_privileges("server", "x") + # TODO: send message + #broadcast(:devops_deploy, "deploy") + r = create_object_from_json_body + names = check_array(r["names"], "Parameter 'names' should be a not empty array of strings") + tags = check_array(r["tags"], "Parameter 'tags' should be an array of strings", String, true) || [] + + if r.key?("trace") + stream() do |out| + status = [] + begin + Devops::API2_0::Handler::Deploy.new(request, params).deploy_stream(out, names, tags) + rescue IOError => e + logger.error e.message + break + end + end # stream + else + ids = Devops::API2_0::Handler::Deploy.new(request, params).deploy(names, tags) + sleep 1 + ids.each do |jid| + logger.info "Job '#{jid}' has been queued" + uri.path = "#{DevopsConfig.config[:url_prefix]}/v2.0/report/" + jid + files.push uri.to_s + end + json files + end + + end puts "Deploy routes initialized" end diff --git a/devops-service/app/api2/routes/report.rb b/devops-service/app/api2/routes/report.rb index f9c345c..48cdfa0 100644 --- a/devops-service/app/api2/routes/report.rb +++ b/devops-service/app/api2/routes/report.rb @@ -1,26 +1,34 @@ module Devops - module Version2_0 + module API2_0 module Routes module ReportRoutes def self.registered(app) - app.get_with_headers "/report/all", headers: [:accept], &Devops::Version2_0::Handler::Report.reports_all - app.get_with_headers "/report/all/latest", headers: [:accept], &Devops::Version2_0::Handler::Report.reports_latest - app.get_with_headers "/report/all/attributes/:name", headers: [:accept], &Devops::Version2_0::Handler::Report.attributes_all - app.get_with_headers "/report/:id", headers: [:accept], &Devops::Version2_0::Handler::Report.report - puts "Report routes initialized" - end - - def completed? id - r = task_status(id) - r == "completed" or r == "failed" - end - - def task_status id - r = Sidekiq.redis do |connection| - connection.hget("devops", id) + app.get_with_headers "/report/all", headers: [:accept] do + json Devops::API2_0::Handler::Report.new(request, params).all.map{|r| r.to_hash} end + + app.get_with_headers "/report/all/latest", headers: [:accept] do + json Devops::API2_0::Handler::Report.new(request, params).all_latest.map{|r| r.to_hash} + end + + app.get_with_headers "/report/all/attributes/:name", headers: [:accept] do |name| + json Devops::API2_0::Handler::Report.new(request, params).attributes(name) + end + + app.get_with_headers "/report/:id", headers: [:accept] do |id| + @text, @done = Devops::API2_0::Handler::Report.new(request, params).report(id) + erb :index + end + + app.get "/status/:id" do + r = Devops::API2_0::Handler::Report.new(request, params).status(params[:id]) + return [404, "Job with id '#{params[:id]}' not found"] if r.nil? + r + end + + puts "Report routes initialized" end end diff --git a/devops-service/app/api2/routes/script.rb b/devops-service/app/api2/routes/script.rb index c52f724..b65e10f 100644 --- a/devops-service/app/api2/routes/script.rb +++ b/devops-service/app/api2/routes/script.rb @@ -16,7 +16,10 @@ module Devops # [ # "script_1" # ] - app.get_with_headers "/scripts", :headers => [:accept], &Devops::Version2_0::Handler::Script.get_scripts + app.get_with_headers "/scripts", :headers => [:accept] do + check_privileges("script", "r") + json Devops::API2_0::Handler::Script.new(request, params).scripts + end # Run command on node :node_name # @@ -26,7 +29,16 @@ module Devops # command to run # # * *Returns* : text stream - app.post_with_statistic "/script/command/:node_name", &Devops::Version2_0::Handler::Script.execute_command + app.post_with_statistic "/script/command/:node_name" do |node_name| + check_privileges("script", "x") + stream() do |out| + begin + Devops::API2_0::Handler::Script.new(request, params).execute_command(out, request.body.read, node_name) + rescue IOError => e + logger.error e.message + end + end + end # Run script :script_name on nodes # @@ -41,7 +53,21 @@ module Devops # } # # * *Returns* : text stream - app.post_with_headers "/script/run/:script_name", :headers => [:content_type], &Devops::Version2_0::Handler::Script.run_script + app.post_with_headers "/script/run/:script_name", :headers => [:content_type] do |script_name| + check_privileges("script", "x") + file_name = check_filename(script_name, "Parameter 'script_name' must be a not empty string", false) + body = create_object_from_json_body + nodes = check_array(body["nodes"], "Parameter 'nodes' must be a not empty array of strings") + p = check_array(body["params"], "Parameter 'params' should be a not empty array of strings", String, true) + + stream() do |out| + begin + Devops::API2_0::Handler::Script.new(request, params).run_script out, file_name, nodes, p + rescue IOError => e + logger.error e.message + end + end + end hash = {} # Create script :script_name @@ -54,7 +80,13 @@ module Devops # # * *Returns* : # 201 - Created - hash["PUT"] = Devops::Version2_0::Handler::Script.create_script + hash["PUT"] = lambda { + check_privileges("script", "w") + file_name = params[:script_name] + check_filename(file_name, "Parameter 'script_name' must be a not empty string") + Devops::API2_0::Handler::Script.new(request, params).create_script(file_name) + create_response("File '#{file_name}' created", nil, 201) + } # Delete script :script_name # @@ -65,7 +97,13 @@ module Devops # # * *Returns* : # 200 - Deleted - hash["DELETE"] = Devops::Version2_0::Handler::Script.delete_script + hash["DELETE"] = lambda { + check_privileges("script", "w") + file_name = params[:script_name] + check_filename(file_name, "Parameter 'script_name' must be a not empty string") + Devops::API2_0::Handler::Script.new(request, params).delete_script file_name + create_response("File '#{file_name}' deleted") + } app.multi_routes "/script/:script_name", {:headers => [:accept]}, hash puts "Script routes initialized" diff --git a/devops-service/app/api2/routes/status.rb b/devops-service/app/api2/routes/status.rb deleted file mode 100644 index 69afa83..0000000 --- a/devops-service/app/api2/routes/status.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Devops - module Version2_0 - module Routes - module StatusRoutes - - def self.registered(app) - app.get "/status/:id", &Devops::Version2_0::Handler::Status.get_status - - puts "Status routes initialized" - end - - end - end - end -end diff --git a/devops-service/app/devops-api2.rb b/devops-service/app/devops-api2.rb index 1b32eac..954d0bf 100644 --- a/devops-service/app/devops-api2.rb +++ b/devops-service/app/devops-api2.rb @@ -8,20 +8,19 @@ module Devops require_relative "api2/handlers/filter" require_relative "api2/handlers/group" require_relative "api2/handlers/user" + require_relative "api2/handlers/report" + require_relative "api2/handlers/deploy" + require_relative "api2/handlers/script" =begin require "routes/v2.0/handlers/bootstrap_templates" - require "routes/v2.0/handlers/deploy" require "routes/v2.0/handlers/image" require "routes/v2.0/handlers/network" require "routes/v2.0/handlers/key" require "routes/v2.0/handlers/project" - require "routes/v2.0/handlers/script" - require "routes/v2.0/handlers/status" require "routes/v2.0/handlers/tag" require "routes/v2.0/handlers/server" require "routes/v2.0/handlers/stack" require "routes/v2.0/handlers/stack_template" - require "routes/v2.0/handlers/report" require_relative "api2/routes/handlers/stack_template_preset" =end @@ -57,7 +56,6 @@ module Devops require_relative "api2/routes/tag" require_relative "api2/routes/server" require_relative "api2/routes/script" - require_relative "api2/routes/status" require_relative "api2/routes/bootstrap_templates" require_relative "api2/routes/stack" require_relative "api2/routes/stack_template"