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.
Files changed (57) hide show
  1. data/README.markdown +66 -0
  2. data/Rakefile +110 -0
  3. data/VERSION.yml +4 -0
  4. data/app.rb +137 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +31 -0
  7. data/config/config.sample.yml +38 -0
  8. data/config/thin.sample.yml +13 -0
  9. data/integrity.gemspec +76 -0
  10. data/lib/integrity.rb +82 -0
  11. data/lib/integrity/build.rb +61 -0
  12. data/lib/integrity/core_ext/object.rb +6 -0
  13. data/lib/integrity/core_ext/string.rb +5 -0
  14. data/lib/integrity/helpers.rb +16 -0
  15. data/lib/integrity/helpers/authorization.rb +33 -0
  16. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  17. data/lib/integrity/helpers/forms.rb +28 -0
  18. data/lib/integrity/helpers/pretty_output.rb +45 -0
  19. data/lib/integrity/helpers/rendering.rb +14 -0
  20. data/lib/integrity/helpers/resources.rb +13 -0
  21. data/lib/integrity/helpers/urls.rb +47 -0
  22. data/lib/integrity/installer.rb +132 -0
  23. data/lib/integrity/migrations.rb +152 -0
  24. data/lib/integrity/notifier.rb +50 -0
  25. data/lib/integrity/notifier/base.rb +55 -0
  26. data/lib/integrity/project.rb +117 -0
  27. data/lib/integrity/project_builder.rb +47 -0
  28. data/lib/integrity/scm.rb +19 -0
  29. data/lib/integrity/scm/git.rb +91 -0
  30. data/lib/integrity/scm/git/uri.rb +57 -0
  31. data/public/buttons.css +82 -0
  32. data/public/reset.css +7 -0
  33. data/public/spinner.gif +0 -0
  34. data/test/helpers.rb +48 -0
  35. data/test/helpers/acceptance.rb +126 -0
  36. data/test/helpers/acceptance/git_helper.rb +99 -0
  37. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  38. data/test/helpers/expectations.rb +5 -0
  39. data/test/helpers/expectations/be_a.rb +23 -0
  40. data/test/helpers/expectations/change.rb +90 -0
  41. data/test/helpers/expectations/have.rb +105 -0
  42. data/test/helpers/expectations/have_tag.rb +128 -0
  43. data/test/helpers/expectations/predicates.rb +37 -0
  44. data/test/helpers/fixtures.rb +83 -0
  45. data/views/_build_info.haml +18 -0
  46. data/views/build.haml +2 -0
  47. data/views/error.haml +36 -0
  48. data/views/home.haml +23 -0
  49. data/views/integrity.sass +387 -0
  50. data/views/layout.haml +28 -0
  51. data/views/new.haml +51 -0
  52. data/views/not_found.haml +31 -0
  53. data/views/notifier.haml +7 -0
  54. data/views/project.builder +21 -0
  55. data/views/project.haml +28 -0
  56. data/views/unauthorized.haml +38 -0
  57. 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