Permalink
Browse files

Initial commit - working app

  • Loading branch information...
0 parents commit 1c5320740eb9e4af4ebebb977592cad709145c1e Mat Brown committed Aug 17, 2009
@@ -0,0 +1 @@
+config.yml
@@ -0,0 +1 @@
+*
@@ -0,0 +1,60 @@
+$: << File.join(File.dirname(__FILE__), 'lib')
+
+require 'rubygems'
+require 'sinatra'
+require 'haml'
+require 'net/http'
+require 'net/https'
+require 'cgi'
+require 'json'
+
+require 'config_puller'
+require 'deploy_runner'
+
+APP_ROOT = File.expand_path(File.dirname(__FILE__))
+
+get '/' do
+ if File.exist?('state/last_branch')
+ @branch = IO.read('state/last_branch')
+ end
+ haml :index
+end
+
+post '/deploy' do
+ branch = params[:branch]
+ File.open('state/last_branch', 'w') { |f| f << branch }
+ config = File.open('config.yml') { |f| YAML.load(f) }
+ ConfigPuller.new(config).pull(%w(config/deploy.rb Capfile), branch)
+ pid = fork do
+ DeployRunner.new(config).run
+ exit
+ end
+ Process.detach(pid)
+ redirect('/progress')
+end
+
+get '/progress' do
+ haml :progress
+end
+
+get '/status' do
+ log_path = File.join(APP_ROOT, 'state', 'deploy.out')
+ if File.exist?(log_path)
+ File.open(log_path) do |file|
+ if start = params[:start]
+ file.seek(start.to_i, IO::SEEK_SET)
+ end
+ log = CGI.escapeHTML(file.read).gsub("\n", '<br>')
+ status = IO.read(File.join(APP_ROOT, 'state', 'status'))
+ position = file.pos
+ content_type('application/json')
+ {
+ :log => log,
+ :status => status,
+ :position => position
+ }.to_json
+ end
+ else
+ ''
+ end
+end
@@ -0,0 +1,36 @@
+class ConfigPuller
+ ConfigRequestError = Class.new(StandardError)
+
+ def initialize(config)
+ @config = config
+ end
+
+ def pull(files, branch)
+ http = Net::HTTP.new('github.com', 443)
+ http.use_ssl = true
+ files.each do |path|
+ request_path = "/#{account}/#{repository}/raw/#{branch}/#{path}?login=#{account}&token=#{token}"
+ puts request_path
+ response = http.get(request_path)
+ if response.code == '200'
+ File.open("caproot/#{path}", 'w') { |f| f << response.body }
+ else
+ raise ConfigRequestError, "Got response code #{response.code} when requesting #{request_path}"
+ end
+ end
+ end
+
+ private
+
+ def repository
+ @config['repository']
+ end
+
+ def account
+ @config['account']
+ end
+
+ def token
+ @config['token']
+ end
+end
@@ -0,0 +1,55 @@
+require 'escape'
+require 'fileutils'
+require 'open3'
+
+class DeployRunner
+ InProgressError = Class.new(StandardError)
+
+ def initialize(config)
+ @config = config
+ end
+
+ def run
+ environment = @config['environment']
+ raise InProgressError if status == 'INPROGRESS'
+ set_status('INPROGRESS')
+ log_path = File.join(APP_ROOT, 'state', 'deploy.out')
+ begin
+ FileUtils.cd(File.join(APP_ROOT, 'caproot')) do
+ FileUtils.rm(log_path) if File.exist?(log_path)
+ Open3.popen3(Escape.shell_command(['cap', environment, 'deploy'])) do |stdin, stdout, stderr|
+ until stderr.eof?
+ data = stderr.readline
+ File.open(log_path, 'a') do |file|
+ file << data
+ end
+ end
+ end
+ end
+ if $? == 0
+ set_status('DONE')
+ else
+ set_status('FAILED')
+ end
+ rescue => e
+ set_status('ERROR')
+ File.open(log_path, 'a') do |file|
+ file.puts("#{e.class.name}: #{e.message}")
+ file.puts(e.backtrace)
+ end
+ end
+ end
+
+ private
+
+ def set_status(status)
+ File.open(File.join(APP_ROOT, 'state', 'status'), 'w') { |file| file << status }
+ end
+
+ def status
+ path = File.join(APP_ROOT, 'state', 'status')
+ if File.exist?(path)
+ IO.read(path)
+ end
+ end
+end

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -0,0 +1,151 @@
+/**
+ * jQuery.timers - Timer abstractions for jQuery
+ * Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
+ * Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
+ * Date: 2008/08/26
+ *
+ * @author Blair Mitchelmore
+ * @version 1.0.0
+ *
+ **/
+
+jQuery.fn.extend({
+ everyTime: function(interval, label, fn, times, belay) {
+ return this.each(function() {
+ jQuery.timer.add(this, interval, label, fn, times, belay);
+ });
+ },
+ oneTime: function(interval, label, fn) {
+ return this.each(function() {
+ jQuery.timer.add(this, interval, label, fn, 1);
+ });
+ },
+ stopTime: function(label, fn) {
+ return this.each(function() {
+ jQuery.timer.remove(this, label, fn);
+ });
+ }
+});
+
+jQuery.extend({
+ timer: {
+ guid: 1,
+ global: {},
+ regex: /^([0-9]+)\s*(.*s)?$/,
+ powers: {
+ // Yeah this is major overkill...
+ 'ms': 1,
+ 'cs': 10,
+ 'ds': 100,
+ 's': 1000,
+ 'das': 10000,
+ 'hs': 100000,
+ 'ks': 1000000
+ },
+ timeParse: function(value) {
+ if (value == undefined || value == null)
+ return null;
+ var result = this.regex.exec(jQuery.trim(value.toString()));
+ if (result[2]) {
+ var num = parseInt(result[1], 10);
+ var mult = this.powers[result[2]] || 1;
+ return num * mult;
+ } else {
+ return value;
+ }
+ },
+ add: function(element, interval, label, fn, times, belay) {
+ var counter = 0;
+
+ if (jQuery.isFunction(label)) {
+ if (!times)
+ times = fn;
+ fn = label;
+ label = interval;
+ }
+
+ interval = jQuery.timer.timeParse(interval);
+
+ if (typeof interval != 'number' || isNaN(interval) || interval <= 0)
+ return;
+
+ if (times && times.constructor != Number) {
+ belay = !!times;
+ times = 0;
+ }
+
+ times = times || 0;
+ belay = belay || false;
+
+ if (!element.$timers)
+ element.$timers = {};
+
+ if (!element.$timers[label])
+ element.$timers[label] = {};
+
+ fn.$timerID = fn.$timerID || this.guid++;
+
+ var handler = function() {
+ if (belay && this.inProgress)
+ return;
+ this.inProgress = true;
+ if ((++counter > times && times !== 0) || fn.call(element, counter) === false)
+ jQuery.timer.remove(element, label, fn);
+ this.inProgress = false;
+ };
+
+ handler.$timerID = fn.$timerID;
+
+ if (!element.$timers[label][fn.$timerID])
+ element.$timers[label][fn.$timerID] = window.setInterval(handler,interval);
+
+ if ( !this.global[label] )
+ this.global[label] = [];
+ this.global[label].push( element );
+
+ },
+ remove: function(element, label, fn) {
+ var timers = element.$timers, ret;
+
+ if ( timers ) {
+
+ if (!label) {
+ for ( label in timers )
+ this.remove(element, label, fn);
+ } else if ( timers[label] ) {
+ if ( fn ) {
+ if ( fn.$timerID ) {
+ window.clearInterval(timers[label][fn.$timerID]);
+ delete timers[label][fn.$timerID];
+ }
+ } else {
+ for ( var fn in timers[label] ) {
+ window.clearInterval(timers[label][fn]);
+ delete timers[label][fn];
+ }
+ }
+
+ for ( ret in timers[label] ) break;
+ if ( !ret ) {
+ ret = null;
+ delete timers[label];
+ }
+ }
+
+ for ( ret in timers ) break;
+ if ( !ret )
+ element.$timers = null;
+ }
+ }
+ }
+});
+
+if (jQuery.browser.msie)
+ jQuery(window).one("unload", function() {
+ var global = jQuery.timer.global;
+ for ( var label in global ) {
+ var els = global[label], i = els.length;
+ while ( --i )
+ jQuery.timer.remove(els[i], label);
+ }
+ });
@@ -0,0 +1,39 @@
+$(function() {
+ var $progress = $('#progress');
+ var $log = $('#log');
+ var $status = $('#status');
+ var position = 0;
+ $log.everyTime(100, 'update', function() {
+ $.getJSON('/status', { start: position }, function(data) {
+ if(data.status != 'INPROGRESS') {
+ $log.stopTime('update');
+ }
+ var status_message;
+ var status_class;
+ switch(data.status) {
+ case 'INPROGRESS':
+ status_message = 'In Progress';
+ status_class = 'in_progress';
+ break;
+ case 'DONE':
+ status_message = 'Done';
+ status_class = 'success';
+ break;
+ case 'FAILED':
+ status_message = 'Failed';
+ status_class = 'failed';
+ break;
+ case 'ERROR':
+ status_message = 'Error';
+ status_class = 'failed';
+ break;
+ }
+ $status.removeClass();
+ $status.addClass(status_class);
+ $status.text(status_message);
+ position = data.position
+ $log.append(data.log);
+ $progress.scrollTop($log.height());
+ });
+ });
+});
Oops, something went wrong.

0 comments on commit 1c53207

Please sign in to comment.