brycethornton-integrity 0.1.7.1
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 +66 -0
- data/Rakefile +110 -0
- data/VERSION.yml +4 -0
- data/app.rb +137 -0
- data/bin/integrity +4 -0
- data/config/config.sample.ru +31 -0
- data/config/config.sample.yml +38 -0
- data/config/thin.sample.yml +13 -0
- data/integrity.gemspec +76 -0
- data/lib/integrity.rb +82 -0
- data/lib/integrity/build.rb +61 -0
- data/lib/integrity/core_ext/object.rb +6 -0
- data/lib/integrity/core_ext/string.rb +5 -0
- data/lib/integrity/helpers.rb +16 -0
- data/lib/integrity/helpers/authorization.rb +33 -0
- data/lib/integrity/helpers/breadcrumbs.rb +20 -0
- data/lib/integrity/helpers/forms.rb +28 -0
- data/lib/integrity/helpers/pretty_output.rb +45 -0
- data/lib/integrity/helpers/rendering.rb +14 -0
- data/lib/integrity/helpers/resources.rb +13 -0
- data/lib/integrity/helpers/urls.rb +47 -0
- data/lib/integrity/installer.rb +132 -0
- data/lib/integrity/migrations.rb +152 -0
- data/lib/integrity/notifier.rb +50 -0
- data/lib/integrity/notifier/base.rb +55 -0
- data/lib/integrity/project.rb +117 -0
- data/lib/integrity/project_builder.rb +47 -0
- data/lib/integrity/scm.rb +19 -0
- data/lib/integrity/scm/git.rb +91 -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/test/helpers.rb +48 -0
- data/test/helpers/acceptance.rb +126 -0
- data/test/helpers/acceptance/git_helper.rb +99 -0
- data/test/helpers/acceptance/textfile_notifier.rb +26 -0
- data/test/helpers/expectations.rb +5 -0
- data/test/helpers/expectations/be_a.rb +23 -0
- data/test/helpers/expectations/change.rb +90 -0
- data/test/helpers/expectations/have.rb +105 -0
- data/test/helpers/expectations/have_tag.rb +128 -0
- data/test/helpers/expectations/predicates.rb +37 -0
- data/test/helpers/fixtures.rb +83 -0
- data/views/_build_info.haml +18 -0
- data/views/build.haml +2 -0
- data/views/error.haml +36 -0
- data/views/home.haml +23 -0
- data/views/integrity.sass +387 -0
- data/views/layout.haml +28 -0
- data/views/new.haml +51 -0
- data/views/not_found.haml +31 -0
- data/views/notifier.haml +7 -0
- data/views/project.builder +21 -0
- data/views/project.haml +28 -0
- data/views/unauthorized.haml +38 -0
- metadata +243 -0
@@ -0,0 +1,152 @@
|
|
1
|
+
require "dm-migrations"
|
2
|
+
require "migration_runner"
|
3
|
+
|
4
|
+
module Integrity
|
5
|
+
class Migrations
|
6
|
+
include DataMapper::Types
|
7
|
+
|
8
|
+
# not strictly necessary, but it makes it clear what is going on.
|
9
|
+
include DataMapper::MigrationRunner
|
10
|
+
|
11
|
+
migration 1, :initial, :verbose => false do
|
12
|
+
up do
|
13
|
+
create_table :integrity_projects do
|
14
|
+
column :id, Integer, :serial => true
|
15
|
+
column :name, String, :nullable => false
|
16
|
+
column :permalink, String
|
17
|
+
column :uri, URI, :nullable => false
|
18
|
+
column :branch, String, :nullable => false, :default => "master"
|
19
|
+
column :command, String, :nullable => false, :default => "rake"
|
20
|
+
column :public, Boolean, :default => true
|
21
|
+
column :building, Boolean, :default => false
|
22
|
+
column :created_at, DateTime
|
23
|
+
column :updated_at, DateTime
|
24
|
+
|
25
|
+
column :build_id, Integer
|
26
|
+
column :notifier_id, Integer
|
27
|
+
end
|
28
|
+
|
29
|
+
create_table :integrity_builds do
|
30
|
+
column :id, Integer, :serial => true
|
31
|
+
column :output, Text, :nullable => false, :default => ""
|
32
|
+
column :successful, Boolean, :nullable => false, :default => false
|
33
|
+
column :commit_identifier, String, :nullable => false
|
34
|
+
column :commit_metadata, Yaml, :nullable => false
|
35
|
+
column :project_id, Integer
|
36
|
+
column :created_at, DateTime
|
37
|
+
column :updated_at, DateTime
|
38
|
+
end
|
39
|
+
|
40
|
+
create_table :integrity_notifiers do
|
41
|
+
column :id, Integer, :serial => true
|
42
|
+
column :name, String, :nullable => false
|
43
|
+
column :config, Yaml, :nullable => false
|
44
|
+
|
45
|
+
column :project_id, Integer
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
down do
|
50
|
+
drop_table :integrity_notifiers
|
51
|
+
drop_table :integrity_projects
|
52
|
+
drop_table :integrity_builds
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
migration 2, :add_commits, :verbose => false do
|
57
|
+
up do
|
58
|
+
class ::Integrity::Build
|
59
|
+
property :commit_identifier, String
|
60
|
+
property :commit_metadata, Yaml, :lazy => false
|
61
|
+
property :project_id, Integer
|
62
|
+
end
|
63
|
+
|
64
|
+
create_table :integrity_commits do
|
65
|
+
column :id, Integer, :serial => true
|
66
|
+
column :identifier, String, :nullable => false
|
67
|
+
column :message, String, :nullable => false, :length => 255
|
68
|
+
column :author, String, :nullable => false, :length => 255
|
69
|
+
column :committed_at, DateTime, :nullable => false
|
70
|
+
column :created_at, DateTime
|
71
|
+
column :updated_at, DateTime
|
72
|
+
|
73
|
+
column :project_id, Integer
|
74
|
+
end
|
75
|
+
|
76
|
+
# Die, orphans, die
|
77
|
+
Build.all(:project_id => nil).destroy!
|
78
|
+
|
79
|
+
# sqlite hodgepockery
|
80
|
+
all_builds = Build.all.each {|b| b.freeze }
|
81
|
+
drop_table :integrity_builds
|
82
|
+
create_table :integrity_builds do
|
83
|
+
column :id, Integer, :serial => true
|
84
|
+
column :started_at, DateTime
|
85
|
+
column :completed_at, DateTime
|
86
|
+
column :successful, Boolean
|
87
|
+
column :output, Text, :nullable => false, :default => ""
|
88
|
+
column :created_at, DateTime
|
89
|
+
column :updated_at, DateTime
|
90
|
+
column :commit_id, Integer
|
91
|
+
column :commit_identifier, String, :nullable => false
|
92
|
+
column :commit_metadata, Yaml, :nullable => false
|
93
|
+
column :project_id, Integer
|
94
|
+
end
|
95
|
+
|
96
|
+
all_builds.each do |build|
|
97
|
+
commit = Commit.first(:identifier => build.commit_identifier)
|
98
|
+
|
99
|
+
if commit.nil?
|
100
|
+
commit = Commit.create(:identifier => build.commit_identifier,
|
101
|
+
:message => build.commit_metadata[:message],
|
102
|
+
:author => build.commit_metadata[:author],
|
103
|
+
:committed_at => build.commit_metadata[:date],
|
104
|
+
:project_id => build.project_id)
|
105
|
+
end
|
106
|
+
|
107
|
+
Build.create(:commit_id => commit.id,
|
108
|
+
:started_at => build.created_at,
|
109
|
+
:completed_at => build.updated_at,
|
110
|
+
:successful => build.successful,
|
111
|
+
:output => build.output)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
down do
|
116
|
+
modify_table :integrity_builds do
|
117
|
+
add_column :commit_identifier, String, :nullable => false
|
118
|
+
add_column :commit_metadata, Yaml, :nullable => false
|
119
|
+
add_column :project_id, Integer
|
120
|
+
end
|
121
|
+
|
122
|
+
# sqlite hodgepockery
|
123
|
+
all_builds = Build.all.map {|b| b.freeze }
|
124
|
+
drop_table :integrity_builds
|
125
|
+
create_table :integrity_builds do
|
126
|
+
column :id, Integer, :serial => true
|
127
|
+
column :output, Text, :nullable => false, :default => ""
|
128
|
+
column :successful, Boolean, :nullable => false, :default => false
|
129
|
+
column :commit_identifier, String, :nullable => false
|
130
|
+
column :commit_metadata, Yaml, :nullable => false
|
131
|
+
column :created_at, DateTime
|
132
|
+
column :updated_at, DateTime
|
133
|
+
column :project_id, Integer
|
134
|
+
end
|
135
|
+
|
136
|
+
all_builds.each do |build|
|
137
|
+
Build.create(:project_id => build.commit.project_id,
|
138
|
+
:output => build.output,
|
139
|
+
:successful => build.successful,
|
140
|
+
:commit_identifier => build.commit.identifier,
|
141
|
+
:commit_metadata => {
|
142
|
+
:message => build.commit.message,
|
143
|
+
:author => build.commit.author.full,
|
144
|
+
:date => commit.committed_at
|
145
|
+
}.to_yaml)
|
146
|
+
end
|
147
|
+
|
148
|
+
drop_table :commits
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Notifier
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial
|
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 { |notifier| valid_notifier?(notifier) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.enable_notifiers(project, enabled, config={})
|
19
|
+
all(:project_id => project).destroy!
|
20
|
+
list_of_enabled_notifiers(enabled).each do |name|
|
21
|
+
create! :project_id => project, :name => name, :config => config[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def notify_of_build(build)
|
27
|
+
to_const.notify_of_build(build, config)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def to_const
|
33
|
+
self.class.module_eval(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.list_of_enabled_notifiers(names)
|
37
|
+
[*names].reject { |n| n.nil? }
|
38
|
+
end
|
39
|
+
private_class_method :list_of_enabled_notifiers
|
40
|
+
|
41
|
+
def self.valid_notifier?(notifier)
|
42
|
+
notifier.respond_to?(:to_haml) && notifier.respond_to?(:notify_of_build) && notifier != Notifier::Base
|
43
|
+
end
|
44
|
+
private_class_method :valid_notifier?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
require File.dirname(__FILE__) / "notifier" / "base"
|
49
|
+
|
50
|
+
Dir["#{File.dirname(__FILE__)}/notifier/*.rb"].each &method(:require)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Notifier
|
3
|
+
class Base
|
4
|
+
def self.notify_of_build(build, config)
|
5
|
+
Timeout.timeout(8) { new(build, config).deliver! }
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.to_haml
|
9
|
+
raise NoMethodError, "you need to implement this method in your notifier"
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :build
|
13
|
+
|
14
|
+
def initialize(build, config)
|
15
|
+
@build = build
|
16
|
+
@config = config
|
17
|
+
end
|
18
|
+
|
19
|
+
def deliver!
|
20
|
+
raise NoMethodError, "you need to implement this method in your notifier"
|
21
|
+
end
|
22
|
+
|
23
|
+
def short_message
|
24
|
+
"Build #{build.short_commit_identifier} #{build.successful? ? "was successful" : "failed"}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def full_message
|
28
|
+
<<-EOM
|
29
|
+
"Build #{build.commit_identifier} #{build.successful? ? "was successful" : "failed"}"
|
30
|
+
|
31
|
+
Commit Message: #{build.commit_message}
|
32
|
+
Commit Date: #{build.commited_at}
|
33
|
+
Commit Author: #{build.commit_author.name}
|
34
|
+
|
35
|
+
Link: #{build_url}
|
36
|
+
|
37
|
+
Build Output:
|
38
|
+
|
39
|
+
#{stripped_build_output}
|
40
|
+
EOM
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_url
|
44
|
+
raise if Integrity.config[:base_uri].nil?
|
45
|
+
Integrity.config[:base_uri] / build.project.permalink / "builds" / build.commit_identifier
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def stripped_build_output
|
51
|
+
build.output.gsub("\e[0m", "").gsub(/\e\[3[1-7]m/, "")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Project
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial
|
6
|
+
property :name, String, :nullable => false
|
7
|
+
property :permalink, String
|
8
|
+
property :uri, URI, :nullable => false, :length => 255
|
9
|
+
property :branch, String, :nullable => false, :default => "master"
|
10
|
+
property :command, String, :nullable => false, :length => 255, :default => "rake"
|
11
|
+
property :public, Boolean, :default => true
|
12
|
+
property :building, Boolean, :default => false
|
13
|
+
property :created_at, DateTime
|
14
|
+
property :updated_at, DateTime
|
15
|
+
|
16
|
+
has n, :builds, :class_name => "Integrity::Build"
|
17
|
+
has n, :notifiers, :class_name => "Integrity::Notifier"
|
18
|
+
|
19
|
+
before :save, :set_permalink
|
20
|
+
before :destroy, :delete_code
|
21
|
+
|
22
|
+
validates_is_unique :name
|
23
|
+
|
24
|
+
def self.only_public_unless(condition)
|
25
|
+
if condition
|
26
|
+
all
|
27
|
+
else
|
28
|
+
all(:public => true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def build(commit_identifier="HEAD")
|
33
|
+
return if building?
|
34
|
+
update_attributes(:building => true)
|
35
|
+
ProjectBuilder.new(self).build(commit_identifier)
|
36
|
+
ensure
|
37
|
+
update_attributes(:building => false)
|
38
|
+
send_notifications
|
39
|
+
end
|
40
|
+
|
41
|
+
def push(payload)
|
42
|
+
payload = JSON.parse(payload || "")
|
43
|
+
|
44
|
+
if Integrity.config[:build_all_commits]
|
45
|
+
payload["commits"].sort_by { |commit| Time.parse(commit["timestamp"]) }.each do |commit|
|
46
|
+
build(commit["id"]) if payload["ref"] =~ /#{branch}/
|
47
|
+
end
|
48
|
+
else
|
49
|
+
build(payload["after"]) if payload["ref"] =~ /#{branch}/
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def last_build
|
54
|
+
all_builds.first
|
55
|
+
end
|
56
|
+
|
57
|
+
def previous_builds
|
58
|
+
all_builds.tap {|builds| builds.shift }
|
59
|
+
end
|
60
|
+
|
61
|
+
def status
|
62
|
+
last_build && last_build.status
|
63
|
+
end
|
64
|
+
|
65
|
+
def public=(flag)
|
66
|
+
attribute_set(:public, case flag
|
67
|
+
when "1", "0" then flag == "1"
|
68
|
+
else !!flag
|
69
|
+
end)
|
70
|
+
end
|
71
|
+
|
72
|
+
def config_for(notifier)
|
73
|
+
notifier = notifiers.first(:name => notifier.to_s.split(/::/).last)
|
74
|
+
notifier.blank? ? {} : notifier.config
|
75
|
+
end
|
76
|
+
|
77
|
+
def notifies?(notifier)
|
78
|
+
!notifiers.first(:name => notifier.to_s.split(/::/).last).blank?
|
79
|
+
end
|
80
|
+
|
81
|
+
def enable_notifiers(*args)
|
82
|
+
Notifier.enable_notifiers(id, *args)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def set_permalink
|
87
|
+
self.permalink = (name || "").downcase.
|
88
|
+
gsub(/'s/, "s").
|
89
|
+
gsub(/&/, "and").
|
90
|
+
gsub(/[^a-z0-9]+/, "-").
|
91
|
+
gsub(/-*$/, "")
|
92
|
+
end
|
93
|
+
|
94
|
+
def delete_code
|
95
|
+
builds.destroy!
|
96
|
+
ProjectBuilder.new(self).delete_code
|
97
|
+
rescue SCM::SCMUnknownError => error
|
98
|
+
Integrity.log "Problem while trying to deleting code: #{error}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def send_notifications
|
102
|
+
notifiers.each do |notifier|
|
103
|
+
begin
|
104
|
+
Integrity.log "Notifying of build #{last_build.short_commit_identifier} using the #{notifier.name} notifier"
|
105
|
+
notifier.notify_of_build last_build
|
106
|
+
rescue Timeout::Error
|
107
|
+
Integrity.log "#{notifier.name} notifier timed out"
|
108
|
+
next
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def all_builds
|
114
|
+
builds.all.sort_by {|b| b.commited_at }.reverse
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Integrity
|
2
|
+
class ProjectBuilder
|
3
|
+
attr_reader :build_script
|
4
|
+
|
5
|
+
def initialize(project)
|
6
|
+
@uri = project.uri
|
7
|
+
@build_script = project.command
|
8
|
+
@branch = project.branch
|
9
|
+
@scm = SCM.new(@uri, @branch, export_directory)
|
10
|
+
@build = Build.new(:project => project)
|
11
|
+
end
|
12
|
+
|
13
|
+
def build(commit)
|
14
|
+
Integrity.log "Building #{commit} (#{@branch}) of #{@build.project.name} in #{export_directory} using #{scm_name}"
|
15
|
+
@scm.with_revision(commit) { run_build_script }
|
16
|
+
@build
|
17
|
+
ensure
|
18
|
+
@build.commit_identifier = @scm.commit_identifier(commit)
|
19
|
+
@build.commit_metadata = @scm.commit_metadata(commit)
|
20
|
+
@build.save
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete_code
|
24
|
+
FileUtils.rm_r export_directory
|
25
|
+
rescue Errno::ENOENT
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def export_directory
|
31
|
+
Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def scm_name
|
35
|
+
@scm.name
|
36
|
+
end
|
37
|
+
|
38
|
+
def run_build_script
|
39
|
+
Integrity.log "Running `#{build_script}` in #{@scm.working_directory}"
|
40
|
+
|
41
|
+
IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe|
|
42
|
+
@build.output = pipe.read
|
43
|
+
end
|
44
|
+
@build.successful = $?.success?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Integrity
|
2
|
+
module SCM
|
3
|
+
class SCMUnknownError < StandardError; end
|
4
|
+
|
5
|
+
def self.new(uri, *args)
|
6
|
+
scm_class_for(uri).new(uri, *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.working_tree_path(uri)
|
10
|
+
scm_class_for(uri).working_tree_path(uri)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def self.scm_class_for(uri)
|
15
|
+
return Git if uri.scheme == "git" || uri.path =~ /\.git\/?/
|
16
|
+
raise SCMUnknownError, "could not find any SCM based on URI '#{uri.to_s}'"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|