defunkt-integrity 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +146 -0
- data/Rakefile +89 -0
- data/app.rb +249 -0
- data/bin/integrity +80 -0
- data/config/config.sample.ru +30 -0
- data/config/config.sample.yml +31 -0
- data/config/thin.sample.yml +14 -0
- data/integrity.gemspec +66 -0
- data/lib/integrity.rb +44 -0
- data/lib/integrity/build.rb +56 -0
- data/lib/integrity/builder.rb +42 -0
- data/lib/integrity/core_ext/object.rb +6 -0
- data/lib/integrity/core_ext/string.rb +5 -0
- data/lib/integrity/core_ext/time.rb +13 -0
- data/lib/integrity/notifier.rb +49 -0
- data/lib/integrity/notifier/base.rb +55 -0
- data/lib/integrity/project.rb +87 -0
- data/lib/integrity/scm.rb +22 -0
- data/lib/integrity/scm/git.rb +72 -0
- data/lib/integrity/scm/git/uri.rb +57 -0
- data/lib/integrity/version.rb +3 -0
- data/public/buttons.css +82 -0
- data/public/reset.css +7 -0
- data/public/spinner.gif +0 -0
- data/spec/form_field_matchers.rb +91 -0
- data/spec/spec_helper.rb +130 -0
- data/vendor/sinatra-hacks/lib/hacks.rb +49 -0
- data/views/build.haml +2 -0
- data/views/build_info.haml +22 -0
- data/views/home.haml +22 -0
- data/views/integrity.sass +391 -0
- data/views/layout.haml +25 -0
- data/views/new.haml +53 -0
- data/views/not_found.haml +31 -0
- data/views/notifier.haml +7 -0
- data/views/project.haml +36 -0
- data/views/unauthorized.haml +38 -0
- metadata +197 -0
data/bin/integrity
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "rubygems"
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + "/../lib/integrity"
|
6
|
+
|
7
|
+
class WithIntegrity < Thor
|
8
|
+
include FileUtils
|
9
|
+
|
10
|
+
desc "Install integrity at PATH",
|
11
|
+
"Copy template files at PATH. After this, edit the files to your convenience"
|
12
|
+
def install(path)
|
13
|
+
@root = File.expand_path(path)
|
14
|
+
|
15
|
+
create_dir_structure
|
16
|
+
copy_template_files
|
17
|
+
edit_template_files
|
18
|
+
create_db(root / 'config.yml')
|
19
|
+
after_setup_message
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Create databases for CONFIG",
|
23
|
+
"Create the database necessary to run Integrity"
|
24
|
+
def create_db(config)
|
25
|
+
Integrity.config = config
|
26
|
+
Integrity.new
|
27
|
+
DataMapper.auto_migrate!
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
attr_reader :root
|
32
|
+
|
33
|
+
def create_dir_structure
|
34
|
+
mkdir_p root
|
35
|
+
mkdir_p root / "builds"
|
36
|
+
mkdir_p root / "log"
|
37
|
+
end
|
38
|
+
|
39
|
+
def copy_template_files
|
40
|
+
cp Integrity.root / "config" / "config.sample.ru", root / "config.ru"
|
41
|
+
cp Integrity.root / "config" / "config.sample.yml", root / "config.yml"
|
42
|
+
cp Integrity.root / "config" / "thin.sample.yml", root / "thin.yml"
|
43
|
+
end
|
44
|
+
|
45
|
+
def edit_template_files
|
46
|
+
edit_integrity_configuration
|
47
|
+
edit_thin_configuration
|
48
|
+
end
|
49
|
+
|
50
|
+
def edit_integrity_configuration
|
51
|
+
config = File.read(root / "config.yml")
|
52
|
+
config.gsub!(%r(sqlite3:///var/integrity.db), "sqlite3://#{root}/integrity.db")
|
53
|
+
config.gsub!(%r(/path/to/scm/exports), "#{root}/builds")
|
54
|
+
File.open(root / "config.yml", "w") {|f| f.puts config }
|
55
|
+
end
|
56
|
+
|
57
|
+
def edit_thin_configuration
|
58
|
+
config = File.read(root / 'thin.yml')
|
59
|
+
config.gsub!(%r(/apps/integrity), root)
|
60
|
+
File.open(root / 'thin.yml', 'w') { |f| f.puts config }
|
61
|
+
end
|
62
|
+
|
63
|
+
def after_setup_message
|
64
|
+
puts
|
65
|
+
puts %Q(Awesome! Integrity was installed successfully!)
|
66
|
+
puts
|
67
|
+
puts %Q(If you want to enable notifiers, install the gems and then require them)
|
68
|
+
puts %Q(in #{root}/config.ru)
|
69
|
+
puts
|
70
|
+
puts %Q(For example:)
|
71
|
+
puts
|
72
|
+
puts %Q( sudo gem install -s http://gems.github.com foca-integrity-email)
|
73
|
+
puts
|
74
|
+
puts %Q(And then in #{root}/config.ru add:)
|
75
|
+
puts
|
76
|
+
puts %Q( require "notifier/email")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
WithIntegrity.start
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'integrity'
|
4
|
+
|
5
|
+
# If you want to add any notifiers, install the gems and then require them here
|
6
|
+
# For example, to enable the Email notifier: install the gem (from github:
|
7
|
+
#
|
8
|
+
# sudo gem install -s http://gems.github.com foca-integrity-email
|
9
|
+
#
|
10
|
+
# And then uncomment the following line:
|
11
|
+
#
|
12
|
+
# require "notifier/email"
|
13
|
+
|
14
|
+
# Load integrity's configuration.
|
15
|
+
Integrity.config = File.expand_path('./config.yml')
|
16
|
+
|
17
|
+
#######################################################################
|
18
|
+
## ##
|
19
|
+
## == DON'T EDIT ANYTHING BELOW UNLESS YOU KNOW WHAT YOU'RE DOING == ##
|
20
|
+
## ##
|
21
|
+
#######################################################################
|
22
|
+
require Integrity.root / 'app'
|
23
|
+
|
24
|
+
set :public, Integrity.root / 'public'
|
25
|
+
set :views, Integrity.root / 'views'
|
26
|
+
set :port, 8910
|
27
|
+
set :env, :production
|
28
|
+
disable :run, :reload
|
29
|
+
|
30
|
+
run Sinatra.application
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Domain where integrity will be running from. This is used to have
|
2
|
+
# nice URLs in your notifications.
|
3
|
+
# For example:
|
4
|
+
# http://builder.integrityapp.com
|
5
|
+
:base_uri: http://integrity.domain.tld
|
6
|
+
|
7
|
+
# This should be a complete connection string to your database. For example
|
8
|
+
# `mysql://user@localhost/integrity` (you need an `integrity` db created in
|
9
|
+
# localhost, of course).
|
10
|
+
:database_uri: sqlite3:///var/integrity.db
|
11
|
+
|
12
|
+
# This is where your project's code will be checked out to. Make sure it's
|
13
|
+
# writable by the user that runs Integrity.
|
14
|
+
:export_directory: /path/to/scm/exports
|
15
|
+
|
16
|
+
# Enable or disable HTTP authentication for the app. BE AWARE that if you
|
17
|
+
# disable this anyone can delete and alter projects, so do it only if your
|
18
|
+
# app is running in a controlled environment (ie, behind your company's
|
19
|
+
# firewall.)
|
20
|
+
:use_basic_auth: true
|
21
|
+
|
22
|
+
# When `use_basic_auth` is true, the admin's username for HTTP authentication.
|
23
|
+
:admin_username: username
|
24
|
+
|
25
|
+
# When `use_basic_auth` is true, the admin's password. Usually saved as a
|
26
|
+
# SHA1 hash. See the next option.
|
27
|
+
:admin_password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
|
28
|
+
|
29
|
+
# If this is true, then whenever we authenticate the admin user, will hash
|
30
|
+
# it using SHA1. If not, we'll assume the provided password is in plain text.
|
31
|
+
:hash_admin_password: true
|
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
environment: production
|
3
|
+
chdir: /apps/integrity
|
4
|
+
address: 127.0.0.1
|
5
|
+
user: deploy
|
6
|
+
group: deploy
|
7
|
+
port: 8910
|
8
|
+
pid: /apps/integrity/thin.pid
|
9
|
+
rackup: /apps/integrity/config.ru
|
10
|
+
log: /apps/integrity/log/thin.log
|
11
|
+
max_conns: 1024
|
12
|
+
timeout: 30
|
13
|
+
max_persistent_conns: 512
|
14
|
+
daemonize: true
|
data/integrity.gemspec
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'integrity'
|
3
|
+
s.version = '0.1.1'
|
4
|
+
s.date = '2008-11-14'
|
5
|
+
s.summary = 'The easy and fun Continuous Integration server'
|
6
|
+
s.description = 'Your Friendly Continuous Integration server. Easy, fun and painless!'
|
7
|
+
s.homepage = 'http://integrityapp.com'
|
8
|
+
s.rubyforge_project = 'integrity'
|
9
|
+
s.email = 'contacto@nicolassanguinetti.info'
|
10
|
+
s.authors = ['Nicolás Sanguinetti', 'Simon Rozet']
|
11
|
+
s.has_rdoc = false
|
12
|
+
s.executables = ['integrity']
|
13
|
+
s.post_install_message = 'Run `integrity help` for information on how to setup Integrity.'
|
14
|
+
s.files = %w(
|
15
|
+
README.markdown
|
16
|
+
Rakefile
|
17
|
+
app.rb
|
18
|
+
bin/integrity
|
19
|
+
config/config.sample.ru
|
20
|
+
config/config.sample.yml
|
21
|
+
config/thin.sample.yml
|
22
|
+
integrity.gemspec
|
23
|
+
lib/integrity.rb
|
24
|
+
lib/integrity/build.rb
|
25
|
+
lib/integrity/builder.rb
|
26
|
+
lib/integrity/core_ext/object.rb
|
27
|
+
lib/integrity/core_ext/string.rb
|
28
|
+
lib/integrity/core_ext/time.rb
|
29
|
+
lib/integrity/notifier.rb
|
30
|
+
lib/integrity/notifier/base.rb
|
31
|
+
lib/integrity/project.rb
|
32
|
+
lib/integrity/scm.rb
|
33
|
+
lib/integrity/scm/git.rb
|
34
|
+
lib/integrity/scm/git/uri.rb
|
35
|
+
lib/integrity/version.rb
|
36
|
+
public/buttons.css
|
37
|
+
public/reset.css
|
38
|
+
public/spinner.gif
|
39
|
+
vendor/sinatra-hacks/lib/hacks.rb
|
40
|
+
views/build.haml
|
41
|
+
views/build_info.haml
|
42
|
+
views/home.haml
|
43
|
+
views/integrity.sass
|
44
|
+
views/layout.haml
|
45
|
+
views/new.haml
|
46
|
+
views/not_found.haml
|
47
|
+
views/notifier.haml
|
48
|
+
views/project.haml
|
49
|
+
views/unauthorized.haml
|
50
|
+
spec/spec_helper.rb
|
51
|
+
spec/form_field_matchers.rb
|
52
|
+
)
|
53
|
+
|
54
|
+
s.add_dependency 'sinatra', ['>= 0.3.2']
|
55
|
+
s.add_dependency 'dm-core', ['>= 0.9.5']
|
56
|
+
s.add_dependency 'dm-validations', ['>= 0.9.5']
|
57
|
+
s.add_dependency 'dm-types', ['>= 0.9.5']
|
58
|
+
s.add_dependency 'dm-timestamps', ['>= 0.9.5']
|
59
|
+
s.add_dependency 'dm-aggregates', ['>= 0.9.5']
|
60
|
+
s.add_dependency 'data_objects', ['>= 0.9.5']
|
61
|
+
s.add_dependency 'do_sqlite3', ['>= 0.9.5']
|
62
|
+
s.add_dependency 'json'
|
63
|
+
s.add_dependency 'foca-sinatra-diddies', ['>= 0.0.2']
|
64
|
+
s.add_dependency 'rspec_hpricot_matchers'
|
65
|
+
s.add_dependency 'thor'
|
66
|
+
end
|
data/lib/integrity.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
__DIR__ = File.dirname(__FILE__)
|
2
|
+
$:.unshift "#{__DIR__}/integrity", *Dir["#{__DIR__}/../vendor/**/lib"].to_a
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'json'
|
6
|
+
require 'dm-core'
|
7
|
+
require 'dm-validations'
|
8
|
+
require 'dm-types'
|
9
|
+
require 'dm-timestamps'
|
10
|
+
require 'dm-aggregates'
|
11
|
+
|
12
|
+
require 'yaml'
|
13
|
+
require 'digest/sha1'
|
14
|
+
|
15
|
+
require "core_ext/object"
|
16
|
+
require "core_ext/string"
|
17
|
+
require "core_ext/time"
|
18
|
+
|
19
|
+
%w(project build builder scm scm/git notifier version).each &method(:require)
|
20
|
+
|
21
|
+
module Integrity
|
22
|
+
def self.new
|
23
|
+
DataMapper.setup(:default, config[:database_uri])
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.root
|
27
|
+
File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.default_configuration
|
31
|
+
@defaults ||= { :database_uri => 'sqlite3::memory:',
|
32
|
+
:export_directory => root / 'exports',
|
33
|
+
:base_uri => 'http://localhost:8910',
|
34
|
+
:use_basic_auth => false }
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.config
|
38
|
+
@config ||= default_configuration
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.config=(file)
|
42
|
+
@config = default_configuration.merge(YAML.load_file(file))
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Integrity
|
4
|
+
class Build
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Integer, :serial => true
|
8
|
+
property :output, Text, :nullable => false, :default => ''
|
9
|
+
property :successful, Boolean, :nullable => false, :default => false
|
10
|
+
property :commit_identifier, String, :nullable => false
|
11
|
+
property :commit_metadata, Yaml, :nullable => false, :lazy => false
|
12
|
+
property :created_at, DateTime
|
13
|
+
property :updated_at, DateTime
|
14
|
+
|
15
|
+
belongs_to :project, :class_name => "Integrity::Project"
|
16
|
+
|
17
|
+
def failed?
|
18
|
+
!successful?
|
19
|
+
end
|
20
|
+
|
21
|
+
def status
|
22
|
+
successful? ? :success : :failed
|
23
|
+
end
|
24
|
+
|
25
|
+
def human_readable_status
|
26
|
+
successful? ? 'Build Successful' : 'Build Failed'
|
27
|
+
end
|
28
|
+
|
29
|
+
def short_commit_identifier
|
30
|
+
sha1?(commit_identifier) ? commit_identifier[0..6] : commit_identifier
|
31
|
+
end
|
32
|
+
|
33
|
+
def commit_author
|
34
|
+
@author ||= begin
|
35
|
+
commit_metadata[:author] =~ /^(.*) <(.*)>$/
|
36
|
+
OpenStruct.new(:name => $1.strip, :email => $2.strip, :full => commit_metadata[:author])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def commit_message
|
41
|
+
commit_metadata[:message]
|
42
|
+
end
|
43
|
+
|
44
|
+
def commited_at
|
45
|
+
case commit_metadata[:date]
|
46
|
+
when String then Time.parse(commit_metadata[:date])
|
47
|
+
else commit_metadata[:date]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def sha1?(string)
|
53
|
+
string =~ /^[a-f0-9]{40}$/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Integrity
|
4
|
+
class Builder
|
5
|
+
attr_reader :build_script
|
6
|
+
|
7
|
+
def initialize(project)
|
8
|
+
@uri = project.uri
|
9
|
+
@build_script = project.command
|
10
|
+
@branch = project.branch
|
11
|
+
@scm = SCM.new(@uri, @branch, export_directory)
|
12
|
+
@build = Build.new(:project => project)
|
13
|
+
end
|
14
|
+
|
15
|
+
def build(commit)
|
16
|
+
@scm.with_revision(commit) { run_build_script }
|
17
|
+
@build
|
18
|
+
ensure
|
19
|
+
@build.commit_identifier = @scm.commit_identifier(commit)
|
20
|
+
@build.commit_metadata = @scm.commit_metadata(commit)
|
21
|
+
@build.save
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete_code
|
25
|
+
FileUtils.rm_r export_directory
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def export_directory
|
32
|
+
Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_build_script
|
36
|
+
IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe|
|
37
|
+
@build.output = pipe.read
|
38
|
+
end
|
39
|
+
@build.successful = $?.success?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Time
|
2
|
+
alias :strftime_without_ordinals :strftime
|
3
|
+
def strftime(format_string)
|
4
|
+
format_string.gsub! "%o", case day
|
5
|
+
when 1, 21, 31 then "st"
|
6
|
+
when 2, 22 then "nd"
|
7
|
+
when 3, 23 then "rd"
|
8
|
+
else "th"
|
9
|
+
end
|
10
|
+
|
11
|
+
strftime_without_ordinals(format_string)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Notifier
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Integer, :serial => true
|
6
|
+
property :name, String, :nullable => false
|
7
|
+
property :config, Yaml, :nullable => false, :lazy => false
|
8
|
+
|
9
|
+
belongs_to :project, :class_name => "Integrity::Project"
|
10
|
+
|
11
|
+
validates_is_unique :name, :scope => :project_id
|
12
|
+
validates_present :project_id
|
13
|
+
|
14
|
+
def self.available
|
15
|
+
@available ||= constants.map {|name| const_get(name) }.select do |notifier|
|
16
|
+
notifier.respond_to?(:to_haml) && notifier.respond_to?(:notify_of_build)
|
17
|
+
end - [Notifier::Base]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.enable_notifiers(project, enabled, config={})
|
21
|
+
Project.get(project).notifiers.destroy!
|
22
|
+
list_of_enabled_notifiers(enabled).each do |name|
|
23
|
+
create! :project_id => project, :name => name, :config => config[name]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def notify_of_build(build)
|
28
|
+
to_const.notify_of_build(build, config)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def to_const
|
34
|
+
self.class.module_eval(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.list_of_enabled_notifiers(names)
|
38
|
+
case names
|
39
|
+
when Array then names
|
40
|
+
when NilClass then []
|
41
|
+
else [names]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
require File.dirname(__FILE__) / 'notifier' / 'base'
|
48
|
+
|
49
|
+
Dir["#{File.dirname(__FILE__)}/notifier/*.rb"].each &method(:require)
|