giraffesoft-integrity 0.1.4
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 +153 -0
- data/Rakefile +81 -0
- data/VERSION.yml +4 -0
- data/app.rb +266 -0
- data/bin/integrity +82 -0
- data/config/config.sample.ru +30 -0
- data/config/config.sample.yml +34 -0
- data/config/thin.sample.yml +13 -0
- data/integrity.gemspec +70 -0
- data/lib/integrity.rb +71 -0
- data/lib/integrity/build.rb +56 -0
- data/lib/integrity/builder.rb +49 -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 +90 -0
- data/lib/integrity/scm.rb +22 -0
- data/lib/integrity/scm/git.rb +83 -0
- data/lib/integrity/scm/git/uri.rb +57 -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 +135 -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/error.haml +29 -0
- data/views/home.haml +22 -0
- data/views/integrity.sass +387 -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 +207 -0
data/bin/integrity
ADDED
@@ -0,0 +1,82 @@
|
|
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 [PATH]",
|
11
|
+
"Copy template files to PATH. Next, go there and edit them."
|
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_db [CONFIG]",
|
23
|
+
"Checks the `database_uri` in CONFIG and creates and bootstraps a database for integrity"
|
24
|
+
def create_db(config)
|
25
|
+
Integrity.new(config)
|
26
|
+
DataMapper.auto_migrate!
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
attr_reader :root
|
31
|
+
|
32
|
+
def create_dir_structure
|
33
|
+
mkdir_p root
|
34
|
+
mkdir_p root / "builds"
|
35
|
+
mkdir_p root / "log"
|
36
|
+
end
|
37
|
+
|
38
|
+
def copy_template_files
|
39
|
+
cp Integrity.root / "config" / "config.sample.ru", root / "config.ru"
|
40
|
+
cp Integrity.root / "config" / "config.sample.yml", root / "config.yml"
|
41
|
+
cp Integrity.root / "config" / "thin.sample.yml", root / "thin.yml"
|
42
|
+
end
|
43
|
+
|
44
|
+
def edit_template_files
|
45
|
+
edit_integrity_configuration
|
46
|
+
edit_thin_configuration
|
47
|
+
end
|
48
|
+
|
49
|
+
def edit_integrity_configuration
|
50
|
+
config = File.read(root / "config.yml")
|
51
|
+
config.gsub! %r(sqlite3:///var/integrity.db), "sqlite3://#{root}/integrity.db"
|
52
|
+
config.gsub! %r(/path/to/scm/exports), "#{root}/builds"
|
53
|
+
config.gsub! %r(/var/log), "#{root}/log"
|
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
|
+
puts
|
78
|
+
puts %Q(Don't forget to tweak #{root / "config.yml"} to your needs.)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
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,34 @@
|
|
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
|
+
# Path to the integrity log file
|
17
|
+
:log: /var/log/integrity.log
|
18
|
+
|
19
|
+
# Enable or disable HTTP authentication for the app. BE AWARE that if you
|
20
|
+
# disable this anyone can delete and alter projects, so do it only if your
|
21
|
+
# app is running in a controlled environment (ie, behind your company's
|
22
|
+
# firewall.)
|
23
|
+
:use_basic_auth: false
|
24
|
+
|
25
|
+
# When `use_basic_auth` is true, the admin's username for HTTP authentication.
|
26
|
+
:admin_username: username
|
27
|
+
|
28
|
+
# When `use_basic_auth` is true, the admin's password. Usually saved as a
|
29
|
+
# SHA1 hash. See the next option.
|
30
|
+
:admin_password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
|
31
|
+
|
32
|
+
# If this is true, then whenever we authenticate the admin user, will hash
|
33
|
+
# it using SHA1. If not, we'll assume the provided password is in plain text.
|
34
|
+
:hash_admin_password: true
|
@@ -0,0 +1,13 @@
|
|
1
|
+
---
|
2
|
+
environment: production
|
3
|
+
chdir: /apps/integrity
|
4
|
+
address: 127.0.0.1
|
5
|
+
port: 8910
|
6
|
+
pid: /apps/integrity/thin.pid
|
7
|
+
rackup: /apps/integrity/config.ru
|
8
|
+
log: /apps/integrity/log/thin.log
|
9
|
+
max_conns: 1024
|
10
|
+
timeout: 30
|
11
|
+
max_persistent_conns: 512
|
12
|
+
daemonize: true
|
13
|
+
servers: 2
|
data/integrity.gemspec
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{integrity}
|
5
|
+
s.version = "0.1.4"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Nicol\303\241s Sanguinetti", "Simon Rozet"]
|
9
|
+
s.date = %q{2008-11-22}
|
10
|
+
s.default_executable = %q{integrity}
|
11
|
+
s.description = %q{Your Friendly Continuous Integration server. Easy, fun and painless!}
|
12
|
+
s.email = %q{contacto@nicolassanguinetti.info}
|
13
|
+
s.executables = ["integrity"]
|
14
|
+
s.files = ["README.markdown", "Rakefile", "VERSION.yml", "app.rb", "bin/integrity", "config/config.sample.ru", "config/config.sample.yml", "config/thin.sample.yml", "integrity.gemspec", "lib/integrity.rb", "lib/integrity/build.rb", "lib/integrity/builder.rb", "lib/integrity/core_ext/object.rb", "lib/integrity/core_ext/string.rb", "lib/integrity/core_ext/time.rb", "lib/integrity/notifier.rb", "lib/integrity/notifier/base.rb", "lib/integrity/project.rb", "lib/integrity/scm.rb", "lib/integrity/scm/git.rb", "lib/integrity/scm/git/uri.rb", "public/buttons.css", "public/reset.css", "public/spinner.gif", "vendor/sinatra-hacks/lib/hacks.rb", "views/build.haml", "views/build_info.haml", "views/error.haml", "views/home.haml", "views/integrity.sass", "views/layout.haml", "views/new.haml", "views/not_found.haml", "views/notifier.haml", "views/project.haml", "views/unauthorized.haml", "spec/spec_helper.rb", "spec/form_field_matchers.rb"]
|
15
|
+
s.homepage = %q{http://integrityapp.com}
|
16
|
+
s.post_install_message = %q{Run `integrity help` for information on how to setup Integrity.}
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{integrity}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{The easy and fun Continuous Integration server}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 0.3.2"])
|
28
|
+
s.add_runtime_dependency(%q<haml>, [">= 0"])
|
29
|
+
s.add_runtime_dependency(%q<dm-core>, [">= 0.9.5"])
|
30
|
+
s.add_runtime_dependency(%q<dm-validations>, [">= 0.9.5"])
|
31
|
+
s.add_runtime_dependency(%q<dm-types>, [">= 0.9.5"])
|
32
|
+
s.add_runtime_dependency(%q<dm-timestamps>, [">= 0.9.5"])
|
33
|
+
s.add_runtime_dependency(%q<dm-aggregates>, [">= 0.9.5"])
|
34
|
+
s.add_runtime_dependency(%q<data_objects>, [">= 0.9.5"])
|
35
|
+
s.add_runtime_dependency(%q<do_sqlite3>, [">= 0.9.5"])
|
36
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
37
|
+
s.add_runtime_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
|
38
|
+
s.add_runtime_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
|
39
|
+
s.add_runtime_dependency(%q<thor>, [">= 0"])
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<sinatra>, [">= 0.3.2"])
|
42
|
+
s.add_dependency(%q<haml>, [">= 0"])
|
43
|
+
s.add_dependency(%q<dm-core>, [">= 0.9.5"])
|
44
|
+
s.add_dependency(%q<dm-validations>, [">= 0.9.5"])
|
45
|
+
s.add_dependency(%q<dm-types>, [">= 0.9.5"])
|
46
|
+
s.add_dependency(%q<dm-timestamps>, [">= 0.9.5"])
|
47
|
+
s.add_dependency(%q<dm-aggregates>, [">= 0.9.5"])
|
48
|
+
s.add_dependency(%q<data_objects>, [">= 0.9.5"])
|
49
|
+
s.add_dependency(%q<do_sqlite3>, [">= 0.9.5"])
|
50
|
+
s.add_dependency(%q<json>, [">= 0"])
|
51
|
+
s.add_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
|
52
|
+
s.add_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
|
53
|
+
s.add_dependency(%q<thor>, [">= 0"])
|
54
|
+
end
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<sinatra>, [">= 0.3.2"])
|
57
|
+
s.add_dependency(%q<haml>, [">= 0"])
|
58
|
+
s.add_dependency(%q<dm-core>, [">= 0.9.5"])
|
59
|
+
s.add_dependency(%q<dm-validations>, [">= 0.9.5"])
|
60
|
+
s.add_dependency(%q<dm-types>, [">= 0.9.5"])
|
61
|
+
s.add_dependency(%q<dm-timestamps>, [">= 0.9.5"])
|
62
|
+
s.add_dependency(%q<dm-aggregates>, [">= 0.9.5"])
|
63
|
+
s.add_dependency(%q<data_objects>, [">= 0.9.5"])
|
64
|
+
s.add_dependency(%q<do_sqlite3>, [">= 0.9.5"])
|
65
|
+
s.add_dependency(%q<json>, [">= 0"])
|
66
|
+
s.add_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
|
67
|
+
s.add_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
|
68
|
+
s.add_dependency(%q<thor>, [">= 0"])
|
69
|
+
end
|
70
|
+
end
|
data/lib/integrity.rb
ADDED
@@ -0,0 +1,71 @@
|
|
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 'logger'
|
14
|
+
require 'digest/sha1'
|
15
|
+
|
16
|
+
require 'core_ext/object'
|
17
|
+
require 'core_ext/string'
|
18
|
+
require 'core_ext/time'
|
19
|
+
|
20
|
+
require 'project'
|
21
|
+
require 'build'
|
22
|
+
require 'builder'
|
23
|
+
require 'scm'
|
24
|
+
require 'scm/git'
|
25
|
+
require 'notifier'
|
26
|
+
|
27
|
+
module Integrity
|
28
|
+
def self.new(config_file = nil)
|
29
|
+
self.config = config_file unless config_file.nil?
|
30
|
+
DataMapper.setup(:default, config[:database_uri])
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.root
|
34
|
+
File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.default_configuration
|
38
|
+
@defaults ||= { :database_uri => 'sqlite3::memory:',
|
39
|
+
:export_directory => root / 'exports',
|
40
|
+
:log => STDOUT,
|
41
|
+
:base_uri => 'http://localhost:8910',
|
42
|
+
:use_basic_auth => false,
|
43
|
+
:build_all_commits => true}
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.config
|
47
|
+
@config ||= default_configuration
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.config=(file)
|
51
|
+
@config = default_configuration.merge(YAML.load_file(file))
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.log(message, &block)
|
55
|
+
logger.info(message, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.logger
|
59
|
+
@logger ||= Logger.new(config[:log], "daily").tap do |logger|
|
60
|
+
logger.formatter = LogFormatter.new
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
class LogFormatter < Logger::Formatter
|
67
|
+
def call(severity, time, progname, msg)
|
68
|
+
time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
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,49 @@
|
|
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
|
+
Integrity.log "Building #{commit} (#{@branch}) of #{@build.project.name} in #{export_directory} using #{scm_name}"
|
17
|
+
@scm.with_revision(commit) { run_build_script }
|
18
|
+
@build
|
19
|
+
ensure
|
20
|
+
@build.commit_identifier = @scm.commit_identifier(commit)
|
21
|
+
@build.commit_metadata = @scm.commit_metadata(commit)
|
22
|
+
@build.save
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete_code
|
26
|
+
FileUtils.rm_r export_directory
|
27
|
+
rescue Errno::ENOENT
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def export_directory
|
33
|
+
Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def scm_name
|
37
|
+
@scm.name
|
38
|
+
end
|
39
|
+
|
40
|
+
def run_build_script
|
41
|
+
Integrity.log "Running `#{build_script}` in #{@scm.working_directory}"
|
42
|
+
|
43
|
+
IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe|
|
44
|
+
@build.output = pipe.read
|
45
|
+
end
|
46
|
+
@build.successful = $?.success?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
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
|