foca-integrity 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +139 -0
- data/Rakefile +87 -0
- data/app.rb +247 -0
- data/bin/integrity +60 -0
- data/config/config.sample.ru +30 -0
- data/config/config.sample.yml +8 -0
- data/config/thin.sample.yml +14 -0
- data/integrity.gemspec +65 -0
- data/lib/integrity.rb +47 -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 +56 -0
- data/lib/integrity/project.rb +83 -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 +131 -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 +14 -0
- data/views/integrity.sass +361 -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 +32 -0
- data/views/unauthorized.haml +38 -0
- metadata +197 -0
data/bin/integrity
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "integrity"
|
5
|
+
require "thor"
|
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
|
+
after_setup_message
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
attr_reader :root
|
23
|
+
|
24
|
+
def create_dir_structure
|
25
|
+
mkdir_p root
|
26
|
+
mkdir_p root / "builds"
|
27
|
+
mkdir_p root / "log"
|
28
|
+
end
|
29
|
+
|
30
|
+
def copy_template_files
|
31
|
+
cp Integrity.root / "config" / "config.sample.ru", root / "config.ru"
|
32
|
+
cp Integrity.root / "config" / "config.sample.yml", root / "config.yml"
|
33
|
+
cp Integrity.root / "config" / "thin.sample.yml", root / "thin.yml"
|
34
|
+
end
|
35
|
+
|
36
|
+
def edit_template_files
|
37
|
+
config = File.read(root / "config.yml")
|
38
|
+
config.gsub!(%r(sqlite3:///var/integrity.db), "sqlite3://#{root}/integrity.db")
|
39
|
+
config.gsub!(%r(/path/to/scm/exports), "#{root}/builds")
|
40
|
+
File.open(root / "config.yml", "w") {|f| f.puts config }
|
41
|
+
end
|
42
|
+
|
43
|
+
def after_setup_message
|
44
|
+
puts
|
45
|
+
puts %Q(Awesome! Integrity was installed successfully!)
|
46
|
+
puts
|
47
|
+
puts %Q(If you want to enable notifiers, install the gems and then require them)
|
48
|
+
puts %Q(in #{root}/config.ru)
|
49
|
+
puts
|
50
|
+
puts %Q(For example:)
|
51
|
+
puts
|
52
|
+
puts %Q( sudo gem install -s http://gems.github.com foca-integrity-email)
|
53
|
+
puts
|
54
|
+
puts %Q(And then in #{root}/config.ru add:)
|
55
|
+
puts
|
56
|
+
puts %Q( require "notifier/email")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
WithIntegrity.start
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "lib/integrity"
|
3
|
+
require "sinatra"
|
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
|
+
|
23
|
+
Sinatra::Application.default_options.merge!(
|
24
|
+
:run => false,
|
25
|
+
:port => Integrity.config[:port],
|
26
|
+
:env => :production
|
27
|
+
)
|
28
|
+
|
29
|
+
require "app"
|
30
|
+
run Sinatra.application
|
@@ -0,0 +1,8 @@
|
|
1
|
+
:base_uri: http://integrity.domain.tld
|
2
|
+
:database_uri: sqlite3:///var/integrity.db
|
3
|
+
:export_directory: /path/to/scm/exports
|
4
|
+
:hash_admin_password: true
|
5
|
+
:use_basic_auth: true
|
6
|
+
:admin_username: username
|
7
|
+
:admin_password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
|
8
|
+
:port: 4567
|
@@ -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,65 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'integrity'
|
3
|
+
s.version = '0.1.0'
|
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.files = %w(
|
14
|
+
README.markdown
|
15
|
+
Rakefile
|
16
|
+
app.rb
|
17
|
+
bin/integrity
|
18
|
+
config/config.sample.ru
|
19
|
+
config/config.sample.yml
|
20
|
+
config/thin.sample.yml
|
21
|
+
integrity.gemspec
|
22
|
+
lib/integrity.rb
|
23
|
+
lib/integrity/build.rb
|
24
|
+
lib/integrity/builder.rb
|
25
|
+
lib/integrity/core_ext/object.rb
|
26
|
+
lib/integrity/core_ext/string.rb
|
27
|
+
lib/integrity/core_ext/time.rb
|
28
|
+
lib/integrity/notifier.rb
|
29
|
+
lib/integrity/notifier/base.rb
|
30
|
+
lib/integrity/project.rb
|
31
|
+
lib/integrity/scm.rb
|
32
|
+
lib/integrity/scm/git.rb
|
33
|
+
lib/integrity/scm/git/uri.rb
|
34
|
+
lib/integrity/version.rb
|
35
|
+
public/buttons.css
|
36
|
+
public/reset.css
|
37
|
+
public/spinner.gif
|
38
|
+
vendor/sinatra-hacks/lib/hacks.rb
|
39
|
+
views/build.haml
|
40
|
+
views/build_info.haml
|
41
|
+
views/home.haml
|
42
|
+
views/integrity.sass
|
43
|
+
views/layout.haml
|
44
|
+
views/new.haml
|
45
|
+
views/not_found.haml
|
46
|
+
views/notifier.haml
|
47
|
+
views/project.haml
|
48
|
+
views/unauthorized.haml
|
49
|
+
spec/spec_helper.rb
|
50
|
+
spec/form_field_matchers.rb
|
51
|
+
)
|
52
|
+
|
53
|
+
s.add_dependency 'sinatra', ['>= 0.3.2']
|
54
|
+
s.add_dependency 'dm-core', ['>= 0.9.5']
|
55
|
+
s.add_dependency 'dm-validations', ['>= 0.9.5']
|
56
|
+
s.add_dependency 'dm-types', ['>= 0.9.5']
|
57
|
+
s.add_dependency 'dm-timestamps', ['>= 0.9.5']
|
58
|
+
s.add_dependency 'dm-aggregates', ['>= 0.9.5']
|
59
|
+
s.add_dependency 'data_objects', ['>= 0.9.5']
|
60
|
+
s.add_dependency 'do_sqlite3', ['>= 0.9.5']
|
61
|
+
s.add_dependency 'json'
|
62
|
+
s.add_dependency 'foca-sinatra-diddies', ['>= 0.0.2']
|
63
|
+
s.add_dependency 'rspec_hpricot_matchers'
|
64
|
+
s.add_dependency 'thor'
|
65
|
+
end
|
data/lib/integrity.rb
ADDED
@@ -0,0 +1,47 @@
|
|
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:4567',
|
34
|
+
:use_basic_auth => false,
|
35
|
+
:port => 9876 }
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.config
|
39
|
+
@config ||= default_configuration
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.config=(file)
|
43
|
+
@config = default_configuration.merge(YAML.load_file(file))
|
44
|
+
rescue Errno::ENOENT
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
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
|
+
all.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)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Notifier
|
3
|
+
class Base
|
4
|
+
def self.notify_of_build(build, config)
|
5
|
+
new(build, config).deliver!
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.to_haml
|
9
|
+
filename = name.split("::").last.downcase
|
10
|
+
File.read File.join(Integrity.root / "lib" / "integrity" / "notifier" / "#{filename}.haml")
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :build
|
14
|
+
|
15
|
+
def initialize(build, config)
|
16
|
+
@build = build
|
17
|
+
@config = config
|
18
|
+
end
|
19
|
+
|
20
|
+
def deliver!
|
21
|
+
raise NoMethodError, "you need to implement this method in your notifier"
|
22
|
+
end
|
23
|
+
|
24
|
+
def short_message
|
25
|
+
"Build #{build.short_commit_identifier} #{build.successful? ? "was successful" : "failed"}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def full_message
|
29
|
+
<<-EOM
|
30
|
+
"Build #{build.commit_identifier} #{build.successful? ? "was successful" : "failed"}"
|
31
|
+
|
32
|
+
Commit Message: #{build.commit_message}
|
33
|
+
Commit Date: #{build.commited_at}
|
34
|
+
Commit Author: #{build.commit_author.name}
|
35
|
+
|
36
|
+
Link: #{build_url}
|
37
|
+
|
38
|
+
Build Output:
|
39
|
+
|
40
|
+
#{stripped_build_output}
|
41
|
+
EOM
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_url
|
45
|
+
raise if Integrity.config[:base_uri].nil?
|
46
|
+
Integrity.config[:base_uri] / build.project.permalink / "builds" / build.commit_identifier
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def stripped_build_output
|
52
|
+
build.output.gsub("\e[0m", '').gsub(/\e\[3[1-7]m/, '')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|