# coding: utf-8 require 'json' require 'uri' require File.dirname(__FILE__) + '/deployer' require 'eventmachine' module Hercules # Class that knows how to handle deploy requests. # This implementation will just parse a JSON as defined by github http hooks. # In order to use other hook formats this class should be reimplemented. class RequestHandler include EM::Deferrable # We must pass the request data (method, path, query and body). # * options is a hash containing all the request data described above plus the logger and the config hash. def initialize(options) @method = options[:method] @body = options[:body] @log = options[:log] @path = options[:path] @config = options[:config] end def run status message set_deferred_status :succeeded end # Returns the message generated as response for the request passed in the initializer. # We also store the message for further queries. def message @result ||= process_request @result[:message] end # Returns the status generated as response for the request passed in the initializer. # We also store the status for further queries. def status @result ||= process_request @result[:status] end # Returns the repository name that fired the request. def repository_name return payload['repository']['name'] if @method == "POST" return @path.split('/')[1] if @method == "GET" end # Returns the security token that fired the request. def request_token @path.split('/')[2] end # Returns true whenever the request made was a HDI request def request_hdi? @path.split('/')[3] == 'hdi' and @method == "GET" end # Returns the assembled HDI def hdi response = "" css = "" File.open(File.dirname(__FILE__) + "/../hdi/site/index.html", 'r'){|f| response = f.read } File.open(File.dirname(__FILE__) + "/../hdi/site/stylesheets/style.css", 'r'){|f| css = f.read } response.gsub(//, "").gsub(/##REQUEST_ADDRESS##/, @path.gsub(/\/hdi/, "")) end # Returns the url of the repository that fired the request. def repository_url payload['repository']['url'] end # Returns the branch of the repository that fired the request. def branch payload['ref'].split('/').pop end private def process_request @method == "GET" ? process_get : process_post end def project_json response = {} @config.branches[repository_name].each do |k| deployed = File.exist?("#{@config[repository_name]['target_directory']}/branches/#{k}") response[k] = {:deployed => deployed} if deployed checkouts = [] Dir.glob("#{@config[repository_name]['target_directory']}/checkouts/#{k}/*").sort{|a,b| File.new(a).ctime.strftime("%Y%m%d%H%M%S") <=> File.new(b).ctime.strftime("%Y%m%d%H%M%S") }.reverse_each do |path| output = "" checkout = path.split('/').pop begin File.open("#{@config[repository_name]['target_directory']}/logs/#{k}/#{checkout}.log"){|f| output = f.read } checkouts.push({:sha1 => checkout, :timestamp => File.mtime(path).strftime("%Y-%m-%d %H:%M:%S"), :output => output}) rescue Errno::ENOENT => e checkouts.push({:sha1 => checkout, :timestamp => File.mtime(path).strftime("%Y-%m-%d %H:%M:%S")}) end end response[k][:checkouts] = checkouts end end response.to_json end def process_get return {:status => 402, :message => "Repository not found"} if @config[repository_name].nil? return {:status => 403, :message => "Invalid token"} unless @config[repository_name]['token'] == request_token return {:status => 200, :message => hdi } if request_hdi? # Otherwise we must return the json with project data {:status => 200, :message => project_json } end def process_post return {:status => 404, :message => "POST content is null"} if @body.nil? return {:status => 404, :message => "Repository #{repository_name} not found in config"} unless @config.include? repository_name return {:status => 404, :message => "Branch #{branch} not found in config"} unless @config[repository_name].include? branch return {:status => 403, :message => "Invalid token"} unless @config[repository_name]['token'] == request_token deploy end # Call the Deployer class to do the deploy magic. # To implement a diferent SCM we will need to rewrite the Deployer and this method. def deploy d = Deployer.new(@log, @config[repository_name], branch) begin d.deploy return {:status => 200, :message => "ok"} rescue Exception => e @log.error "Backtrace: #{e.backtrace}" return {:status => 500, :message => "Error while deploying branch #{branch}: #{e.inspect}"} end end # Parses the request body (only for POST) # Here is the github specific code. def parse_body post = URI.unescape(@body) @log.debug "Received POST: #{post}" JSON.parse(post.gsub(/^payload=/, "")) end # Here we call the body parser and store the result for further queries. def payload @payload ||= parse_body end end end