duke 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ .DS_Store
2
+ pkg
3
+ coverage
4
+ log
5
+ tmp
6
+ g50
7
+ .bundle
8
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "thor"
4
+ gem "cijoe", "0.5.0"
5
+ gem "daemon_controller"
6
+
7
+ group :test do
8
+ gem "rspec"
9
+ gem 'simplecov'
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ choice (0.1.4)
5
+ cijoe (0.5.0)
6
+ choice
7
+ sinatra
8
+ daemon_controller (0.2.5)
9
+ diff-lcs (1.1.2)
10
+ rack (1.2.1)
11
+ rspec (2.0.1)
12
+ rspec-core (~> 2.0.1)
13
+ rspec-expectations (~> 2.0.1)
14
+ rspec-mocks (~> 2.0.1)
15
+ rspec-core (2.0.1)
16
+ rspec-expectations (2.0.1)
17
+ diff-lcs (>= 1.1.2)
18
+ rspec-mocks (2.0.1)
19
+ rspec-core (~> 2.0.1)
20
+ rspec-expectations (~> 2.0.1)
21
+ simplecov (0.3.6)
22
+ simplecov-html (>= 0.3.7)
23
+ simplecov-html (0.3.8)
24
+ sinatra (1.1.0)
25
+ rack (~> 1.1)
26
+ tilt (~> 1.1)
27
+ thor (0.14.3)
28
+ tilt (1.1)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ cijoe (= 0.5.0)
35
+ daemon_controller
36
+ rspec
37
+ simplecov
38
+ thor
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Justin Marney
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,83 @@
1
+ = duke
2
+
3
+ Duke helps you manage multiple CIJoe instances. It comes with a command-line interface and a web interface.
4
+
5
+ == Basic Usage
6
+
7
+ Install the duke gem
8
+ gem install duke
9
+
10
+ Create a new duke installation
11
+ duke new some/directory
12
+
13
+ Inside the duke directory use the command-line interface to add a new repository
14
+ duke add git@github.com:gotascii/duke.git
15
+
16
+ Duke will clone the repo and configure it with some defaults
17
+
18
+ == Configuration
19
+
20
+ === Default Configuration
21
+
22
+ host: localhost
23
+ Given a cijoe running on port 4567 and configured host of 'localhost' the cijoe url will be [http://localhost:4567/](http://localhost:4567/)
24
+ runner: rake
25
+ When duke checks out a new repo it will configure it to build using this command.
26
+ pid_dir: tmp/pids
27
+ The relative directory duke will use to store cijoe daemon pid files.
28
+ log_dir: log
29
+ The relative directory duke will use to store cijoe log files.
30
+ campfire: {}
31
+ Options in the campfire hash are used to configure a new repo.
32
+
33
+ === Custom Configuration
34
+
35
+ A config.yml in the root of the duke installation directory will override the defaults.
36
+
37
+ === Per-Project Runner
38
+
39
+ The duke command-line utility allows you to set the runner for a particular project.
40
+ duke runner repo_dir "rake spec"
41
+
42
+ == Command-Line Usage
43
+
44
+ duke COMMAND [ARGS]
45
+
46
+ === Commands
47
+
48
+ new [DIR]
49
+ Installs duke into DIR. Creates basic directory structure required for duke to do its thing.
50
+ add REPO_URL
51
+ Clones the repo at REPO_URL into the duke directory and configures it to run cijoe.
52
+ start REPO_DIR PORT
53
+ Starts a cijoe server set to build the repo in REPO_DIR on port PORT.
54
+ stop REPO_DIR
55
+ Stops the cijoe server running for REPO_DIR.
56
+ runner REPO_DIR CMD
57
+ Configures REPO_DIR to use CMD for cijoe builds.
58
+ build REPO_DIR
59
+ Tell cijoe to build the project in REPO_DIR
60
+ list
61
+ Lists all of the repo_dirs and their cijoe server and build statuses.
62
+
63
+ == Website Interface
64
+
65
+ Duke provides a way to add, start, and stop your cijoes. It also provides a nice list of all your cijoes and their current build status. It looks like the following screenshot.
66
+
67
+ http://img.skitch.com/20101108-nj852sa18856hiykq5e9kas9m1.jpg
68
+
69
+ The duke installation directory contains a config.ru and the easiest way to get rolling is by using passenger standalone. Just start up passenger inside the duke directory.
70
+
71
+ passenger start
72
+
73
+ == Note on Patches/Pull Requests
74
+
75
+ * Fork the project.
76
+ * Make your feature addition or bug fix.
77
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
78
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
79
+ * Send me a pull request. Bonus points for topic branches.
80
+
81
+ == Copyright
82
+
83
+ Copyright (c) 2010 Justin Marney. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "duke"
8
+ gem.summary = %Q{Manage your CIJoes.}
9
+ gem.description = %Q{Allows you to easily manage multiple CIJoes.}
10
+ gem.email = "gotascii@gmail.com"
11
+ gem.homepage = "http://github.com/gotascii/duke"
12
+ gem.authors = ["Justin Marney"]
13
+ gem.add_runtime_dependency "thor"
14
+ gem.add_runtime_dependency "cijoe"
15
+ gem.add_runtime_dependency "daemon_controller"
16
+ gem.add_development_dependency "rspec", ">= 2.0.1"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'rspec/core/rake_task'
25
+ RSpec::Core::RakeTask.new(:spec) do |spec|
26
+ spec.pattern = 'spec/**/*_spec.rb'
27
+ end
28
+
29
+ desc "run for cover"
30
+ task :simplecov do
31
+ ENV['SIMPLECOV'] = 'true'
32
+ Rake::Task[:spec].invoke
33
+ end
34
+
35
+ task :spec => :check_dependencies
36
+
37
+ task :default => :spec
38
+
39
+ require 'rake/rdoctask'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "duke #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/duke ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'duke'
4
+
5
+ ARGV << '--help' if ARGV.empty?
6
+
7
+ command = ARGV.shift
8
+
9
+ case command
10
+ when '-h', '--help'
11
+ puts <<-EOT
12
+ Usage: duke COMMAND [ARGS]
13
+
14
+ Commands:
15
+ new [DIR] install duke into DIR or current directory
16
+ add REPO_URL add REPO_URL to current duke directory
17
+ start REPO_DIR PORT start cijoe for REPO_DIR on PORT
18
+ stop REPO_DIR stop cijoe for REPO_DIR
19
+ runner REPO_DIR CMD use CMD to run build for REPO_DIR
20
+ build REPO_DIR tell cijoe to build REPO_DIR
21
+ list list cijoe server statues
22
+ EOT
23
+ else
24
+ Duke::Cli.new.send(command.to_sym, *ARGV)
25
+ end
data/duke.gemspec ADDED
@@ -0,0 +1,83 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{duke}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Justin Marney"]
12
+ s.date = %q{2010-11-08}
13
+ s.default_executable = %q{duke}
14
+ s.description = %q{Allows you to easily manage multiple CIJoes.}
15
+ s.email = %q{gotascii@gmail.com}
16
+ s.executables = ["duke"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".gitignore",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE",
26
+ "README.rdoc",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/duke",
30
+ "duke.gemspec",
31
+ "lib/duke.rb",
32
+ "lib/duke/app.rb",
33
+ "lib/duke/cli.rb",
34
+ "lib/duke/controller.rb",
35
+ "lib/duke/project.rb",
36
+ "lib/ext/string.rb",
37
+ "spec/duke/cli_spec.rb",
38
+ "spec/duke/controller_spec.rb",
39
+ "spec/duke/project_spec.rb",
40
+ "spec/spec_helper.rb",
41
+ "templates/config.ru",
42
+ "templates/public/help.html",
43
+ "templates/public/stylesheets/base.css",
44
+ "templates/public/stylesheets/button.css",
45
+ "templates/public/stylesheets/custom.css",
46
+ "templates/public/stylesheets/reset.css",
47
+ "templates/views/index.rhtml"
48
+ ]
49
+ s.homepage = %q{http://github.com/gotascii/duke}
50
+ s.rdoc_options = ["--charset=UTF-8"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.7}
53
+ s.summary = %q{Manage your CIJoes.}
54
+ s.test_files = [
55
+ "spec/duke/cli_spec.rb",
56
+ "spec/duke/controller_spec.rb",
57
+ "spec/duke/project_spec.rb",
58
+ "spec/spec_helper.rb"
59
+ ]
60
+
61
+ if s.respond_to? :specification_version then
62
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
63
+ s.specification_version = 3
64
+
65
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
66
+ s.add_runtime_dependency(%q<thor>, [">= 0"])
67
+ s.add_runtime_dependency(%q<cijoe>, [">= 0"])
68
+ s.add_runtime_dependency(%q<daemon_controller>, [">= 0"])
69
+ s.add_development_dependency(%q<rspec>, [">= 2.0.1"])
70
+ else
71
+ s.add_dependency(%q<thor>, [">= 0"])
72
+ s.add_dependency(%q<cijoe>, [">= 0"])
73
+ s.add_dependency(%q<daemon_controller>, [">= 0"])
74
+ s.add_dependency(%q<rspec>, [">= 2.0.1"])
75
+ end
76
+ else
77
+ s.add_dependency(%q<thor>, [">= 0"])
78
+ s.add_dependency(%q<cijoe>, [">= 0"])
79
+ s.add_dependency(%q<daemon_controller>, [">= 0"])
80
+ s.add_dependency(%q<rspec>, [">= 2.0.1"])
81
+ end
82
+ end
83
+
data/lib/duke/app.rb ADDED
@@ -0,0 +1,37 @@
1
+ module Duke
2
+ class App < Sinatra::Base
3
+ helpers do
4
+ def status_style(project)
5
+ if project.built?
6
+ if project.building?
7
+ " building"
8
+ elsif project.passing?
9
+ " pass"
10
+ else
11
+ " fail"
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ get '/' do
18
+ @projects = Project.all
19
+ erb :index
20
+ end
21
+
22
+ put '/projects/:id' do
23
+ @project = Project.find(:repo_dir => params[:id])
24
+ if @project.running?
25
+ @project.stop
26
+ else
27
+ @project.start(params[:project][:port])
28
+ end
29
+ redirect '/'
30
+ end
31
+
32
+ post '/projects' do
33
+ Project.create(params[:project])
34
+ redirect '/'
35
+ end
36
+ end
37
+ end
data/lib/duke/cli.rb ADDED
@@ -0,0 +1,58 @@
1
+ module Duke
2
+ class Cli < Thor::Group
3
+ include Thor::Actions
4
+
5
+ source_root File.join(File.dirname(__FILE__), '..', '..')
6
+
7
+ def new(dir=nil)
8
+ dir ||= "."
9
+ [:pid_dir, :log_dir].collect do |meth|
10
+ FileUtils.mkdir_p("#{dir}/#{Config.send(meth)}")
11
+ end
12
+ directory 'templates', dir
13
+ end
14
+
15
+ def add(repo_url)
16
+ p = Project.create(:repo_url => repo_url)
17
+ puts <<-MSG
18
+ *** #{p.repo_dir} has been cloned and configured!
19
+ *** Don't forget to:
20
+ *** set up #{p.repo_dir}/config/database.yml
21
+ *** create the project gemset
22
+ *** run bundle install
23
+ *** start up cijoe!
24
+ MSG
25
+ end
26
+
27
+ def start(repo_dir, port)
28
+ Project.find(:repo_dir => repo_dir).start(port)
29
+ end
30
+
31
+ def stop(repo_dir)
32
+ Project.find(:repo_dir => repo_dir).stop
33
+ end
34
+
35
+ def list
36
+ Project.all.each(&:print_status_msg)
37
+ end
38
+
39
+ def build(repo_dir)
40
+ Project.find(:repo_dir => repo_dir).build
41
+ end
42
+
43
+ def cijoed(repo_dir, port, log_file, pid_file)
44
+ cmd = "nohup cijoe -p #{port} #{repo_dir} 1>#{log_file} 2>&1 & echo $! > #{pid_file}"
45
+ exec(rvm_exec(repo_dir, cmd))
46
+ end
47
+
48
+ def runner(repo_dir, cmd)
49
+ Project.find(:repo_dir => repo_dir).set_runner(cmd)
50
+ end
51
+
52
+ def rvm_exec(repo_dir, cmd)
53
+ path = "#{repo_dir}/.rvmrc"
54
+ cmd = "#{File.read(path)} exec '#{cmd}'" if File.exist?(path)
55
+ cmd
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,58 @@
1
+ module Duke
2
+ class Controller
3
+ extend Forwardable
4
+ def_delegators :controller, :start, :stop, :running?, :pid
5
+ attr_reader :dir, :repo_dir, :timeout, :port
6
+
7
+ def self.pid_files
8
+ Dir["#{Config.pid_dir}/*.pid"]
9
+ end
10
+
11
+ def initialize(repo_dir, port=nil)
12
+ @dir = Dir.pwd
13
+ @repo_dir = repo_dir
14
+ @port = port
15
+ @timeout = 7
16
+ end
17
+
18
+ def port
19
+ @port ||= current_port
20
+ end
21
+
22
+ def current_port
23
+ repo_dir_regex = /#{repo_dir}\.(\d+)/
24
+ Controller.pid_files.each do |pid_file|
25
+ return $1.to_i if pid_file =~ repo_dir_regex
26
+ end
27
+ nil
28
+ end
29
+
30
+ def identifier
31
+ "#{repo_dir}.#{port}"
32
+ end
33
+
34
+ def pid_file
35
+ "#{dir}/#{Config.pid_dir}/#{identifier}.pid"
36
+ end
37
+
38
+ def log_file
39
+ "#{dir}/#{Config.log_dir}/#{identifier}.log"
40
+ end
41
+
42
+ def ping_command
43
+ lambda { TCPSocket.new(Config.host, port) }
44
+ end
45
+
46
+ def controller
47
+ @controller ||= DaemonController.new({
48
+ :identifier => identifier,
49
+ :start_command => "duke cijoed #{repo_dir} #{port} #{log_file} #{pid_file}",
50
+ :ping_command => ping_command,
51
+ :pid_file => pid_file,
52
+ :log_file => log_file,
53
+ :log_file_activity_timeout => timeout,
54
+ :start_timeout => timeout
55
+ })
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,121 @@
1
+ module Duke
2
+ class Project < Thor::Group
3
+ include Thor::Actions
4
+ extend Forwardable
5
+ def_delegators :controller, :stop, :pid, :running?, :port
6
+ def_delegators :cijoe, :building?
7
+ attr_reader :repo_dir, :repo_url
8
+ attr_writer :controller
9
+
10
+ def self.all
11
+ Dir['*'].collect do |repo_dir|
12
+ find(:repo_dir => repo_dir)
13
+ end.compact
14
+ end
15
+
16
+ def self.create(repo_url)
17
+ p = new(repo_url)
18
+ p.clone
19
+ p.set_runner Config.runner
20
+ p.set_campfire Config.campfire
21
+ p
22
+ end
23
+
24
+ def self.find(opts)
25
+ p = new(opts)
26
+ p if p.repo_dir?
27
+ end
28
+
29
+ def initialize(opts)
30
+ args = []
31
+ options = {}
32
+ config = {}
33
+ super
34
+ @repo_url = opts[:repo_url] if opts[:repo_url]
35
+ @repo_dir = opts[:repo_dir] || opts[:repo_url].repo_dir
36
+ end
37
+
38
+ def cijoe
39
+ @cijoe ||= begin
40
+ ci = CIJoe.new(repo_dir)
41
+ ci.restore
42
+ ci
43
+ end if repo_dir?
44
+ end
45
+
46
+ def controller
47
+ @controller ||= Controller.new(repo_dir)
48
+ end
49
+
50
+ def start(port)
51
+ self.controller = Controller.new(repo_dir, port)
52
+ controller.start
53
+ end
54
+
55
+ def git_dir
56
+ File.join(repo_dir, '.git')
57
+ end
58
+
59
+ def repo_dir?
60
+ File.exist?(git_dir)
61
+ end
62
+
63
+ def clone
64
+ run "git clone #{repo_url}"
65
+ end
66
+
67
+ def set_config(key, value)
68
+ inside(repo_dir) do
69
+ run "git config --unset-all \"#{key}\""
70
+ run "git config \"#{key}\" \"#{value}\""
71
+ end
72
+ end
73
+
74
+ def set_runner(runner)
75
+ set_config("cijoe.runner", runner)
76
+ end
77
+
78
+ def set_campfire(campfire)
79
+ campfire.each do |k, v|
80
+ set_config("campfire.#{k}", v)
81
+ end
82
+ end
83
+
84
+ def print_status_msg
85
+ fields = []
86
+ fields << repo_dir
87
+ if running?
88
+ fields += ["port #{port}", "pid #{pid}"]
89
+ if building?
90
+ fields << 'building...'
91
+ elsif built?
92
+ fields << if passing?
93
+ "passing"
94
+ else
95
+ "failing"
96
+ end
97
+ end
98
+ else
99
+ fields << "stopped"
100
+ end
101
+ puts fields.join(", ")
102
+ end
103
+
104
+ def build
105
+ uri = URI.parse("#{url}/")
106
+ Net::HTTP.post_form(uri, {})
107
+ end
108
+
109
+ def built?
110
+ !cijoe.last_build.nil?
111
+ end
112
+
113
+ def passing?
114
+ !cijoe.last_build.failed?
115
+ end
116
+
117
+ def url
118
+ "http://#{Config.host}:#{port}"
119
+ end
120
+ end
121
+ end
data/lib/duke.rb ADDED
@@ -0,0 +1,31 @@
1
+ $LOAD_PATH << File.dirname(__FILE__)
2
+
3
+ require 'forwardable'
4
+ require 'erb'
5
+ require 'net/http'
6
+ require 'daemon_controller'
7
+ require 'fileutils'
8
+ require 'thor'
9
+ require 'thor/group'
10
+ require 'socket'
11
+ require 'cijoe'
12
+ require 'sinatra/base'
13
+ require 'ext/string'
14
+ require 'duke/controller'
15
+ require 'duke/cli'
16
+ require 'duke/project'
17
+ require 'duke/app'
18
+
19
+ module Duke
20
+ CONFIG_DEFAULT = {
21
+ :host => "localhost",
22
+ :runner => "rake",
23
+ :pid_dir => "tmp/pids",
24
+ :log_dir => "log",
25
+ :campfire => {}
26
+ }
27
+ config_path = File.join('config', 'config.yml')
28
+ config_yml = File.exist?(config_path) ? YAML.load_file(config_path) : {}
29
+ config = CONFIG_DEFAULT.merge(config_yml)
30
+ Config = OpenStruct.new(config)
31
+ end
data/lib/ext/string.rb ADDED
@@ -0,0 +1,10 @@
1
+ class String
2
+ REPO_REGEX = /\/(.*).git$/
3
+ def repo_dir
4
+ self =~ REPO_REGEX ? $1 : self
5
+ end
6
+
7
+ def repo_url?
8
+ !!(self =~ REPO_REGEX)
9
+ end
10
+ end