integrity 0.1.8

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 +165 -0
  3. data/VERSION.yml +4 -0
  4. data/app.rb +138 -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 +80 -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 +157 -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 +83 -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 +47 -0
  35. data/test/helpers/acceptance.rb +127 -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 +258 -0
@@ -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,76 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{integrity}
5
+ s.version = "0.1.8"
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{2009-02-12}
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/core_ext/object.rb", "lib/integrity/core_ext/string.rb", "lib/integrity/helpers.rb", "lib/integrity/helpers/authorization.rb", "lib/integrity/helpers/breadcrumbs.rb", "lib/integrity/helpers/forms.rb", "lib/integrity/helpers/pretty_output.rb", "lib/integrity/helpers/rendering.rb", "lib/integrity/helpers/resources.rb", "lib/integrity/helpers/urls.rb", "lib/integrity/installer.rb", "lib/integrity/migrations.rb", "lib/integrity/notifier.rb", "lib/integrity/notifier/base.rb", "lib/integrity/project.rb", "lib/integrity/project_builder.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", "test/helpers.rb", "test/helpers/acceptance.rb", "test/helpers/acceptance/git_helper.rb", "test/helpers/acceptance/textfile_notifier.rb", "test/helpers/expectations.rb", "test/helpers/expectations/be_a.rb", "test/helpers/expectations/change.rb", "test/helpers/expectations/have.rb", "test/helpers/expectations/have_tag.rb", "test/helpers/expectations/predicates.rb", "test/helpers/fixtures.rb", "views/_build_info.haml", "views/build.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.builder", "views/project.haml", "views/unauthorized.haml"]
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.9.0.3"])
28
+ s.add_runtime_dependency(%q<haml>, [">= 2.0.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<dm-migrations>, [">= 0.9.5"])
35
+ s.add_runtime_dependency(%q<data_objects>, [">= 0.9.5"])
36
+ s.add_runtime_dependency(%q<do_sqlite3>, [">= 0.9.5"])
37
+ s.add_runtime_dependency(%q<json>, [">= 0"])
38
+ s.add_runtime_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
39
+ s.add_runtime_dependency(%q<thor>, [">= 0"])
40
+ s.add_runtime_dependency(%q<uuidtools>, [">= 0"])
41
+ s.add_runtime_dependency(%q<bcrypt-ruby>, [">= 0"])
42
+ else
43
+ s.add_dependency(%q<sinatra>, [">= 0.9.0.3"])
44
+ s.add_dependency(%q<haml>, [">= 2.0.0"])
45
+ s.add_dependency(%q<dm-core>, [">= 0.9.5"])
46
+ s.add_dependency(%q<dm-validations>, [">= 0.9.5"])
47
+ s.add_dependency(%q<dm-types>, [">= 0.9.5"])
48
+ s.add_dependency(%q<dm-timestamps>, [">= 0.9.5"])
49
+ s.add_dependency(%q<dm-aggregates>, [">= 0.9.5"])
50
+ s.add_dependency(%q<dm-migrations>, [">= 0.9.5"])
51
+ s.add_dependency(%q<data_objects>, [">= 0.9.5"])
52
+ s.add_dependency(%q<do_sqlite3>, [">= 0.9.5"])
53
+ s.add_dependency(%q<json>, [">= 0"])
54
+ s.add_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
55
+ s.add_dependency(%q<thor>, [">= 0"])
56
+ s.add_dependency(%q<uuidtools>, [">= 0"])
57
+ s.add_dependency(%q<bcrypt-ruby>, [">= 0"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<sinatra>, [">= 0.9.0.3"])
61
+ s.add_dependency(%q<haml>, [">= 2.0.0"])
62
+ s.add_dependency(%q<dm-core>, [">= 0.9.5"])
63
+ s.add_dependency(%q<dm-validations>, [">= 0.9.5"])
64
+ s.add_dependency(%q<dm-types>, [">= 0.9.5"])
65
+ s.add_dependency(%q<dm-timestamps>, [">= 0.9.5"])
66
+ s.add_dependency(%q<dm-aggregates>, [">= 0.9.5"])
67
+ s.add_dependency(%q<dm-migrations>, [">= 0.9.5"])
68
+ s.add_dependency(%q<data_objects>, [">= 0.9.5"])
69
+ s.add_dependency(%q<do_sqlite3>, [">= 0.9.5"])
70
+ s.add_dependency(%q<json>, [">= 0"])
71
+ s.add_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
72
+ s.add_dependency(%q<thor>, [">= 0"])
73
+ s.add_dependency(%q<uuidtools>, [">= 0"])
74
+ s.add_dependency(%q<bcrypt-ruby>, [">= 0"])
75
+ end
76
+ end
data/lib/integrity.rb ADDED
@@ -0,0 +1,80 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require "json"
4
+ require "dm-core"
5
+ require "dm-validations"
6
+ require "dm-types"
7
+ require "dm-timestamps"
8
+ require "dm-aggregates"
9
+
10
+ require "yaml"
11
+ require "logger"
12
+ require "digest/sha1"
13
+ require "timeout"
14
+ require "ostruct"
15
+ require "fileutils"
16
+
17
+ require "integrity/core_ext/object"
18
+ require "integrity/core_ext/string"
19
+
20
+ require "integrity/project"
21
+ require "integrity/build"
22
+ require "integrity/project_builder"
23
+ require "integrity/scm"
24
+ require "integrity/scm/git"
25
+ require "integrity/notifier"
26
+
27
+ module Integrity
28
+ def self.new(config_file = nil)
29
+ self.config = config_file unless config_file.nil?
30
+ DataMapper.logger = self.logger if config[:log_debug_info]
31
+ DataMapper.setup(:default, config[:database_uri])
32
+ end
33
+
34
+ def self.root
35
+ File.expand_path(File.join(File.dirname(__FILE__), ".."))
36
+ end
37
+
38
+ def self.default_configuration
39
+ @defaults ||= { :database_uri => "sqlite3::memory:",
40
+ :export_directory => root / "exports",
41
+ :log => STDOUT,
42
+ :base_uri => "http://localhost:8910",
43
+ :use_basic_auth => false,
44
+ :build_all_commits => true,
45
+ :log_debug_info => false }
46
+ end
47
+
48
+ def self.config
49
+ @config ||= default_configuration
50
+ end
51
+
52
+ def self.config=(file)
53
+ @config = default_configuration.merge(YAML.load_file(file))
54
+ end
55
+
56
+ def self.log(message, &block)
57
+ logger.info(message, &block)
58
+ end
59
+
60
+ def self.logger
61
+ @logger ||= Logger.new(config[:log], "daily").tap do |logger|
62
+ logger.formatter = LogFormatter.new
63
+ end
64
+ end
65
+
66
+ def self.version
67
+ @version ||= begin
68
+ file = YAML.load_file(Integrity.root / "VERSION.yml")
69
+ "#{file['major']}.#{file['minor']}.#{file['patch']}"
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ class LogFormatter < Logger::Formatter
76
+ def call(severity, time, progname, msg)
77
+ time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,61 @@
1
+ module Integrity
2
+ class Build
3
+ include DataMapper::Resource
4
+
5
+ property :id, Serial
6
+ property :output, Text, :nullable => false, :default => ""
7
+ property :successful, Boolean, :nullable => false, :default => false
8
+ property :commit_identifier, String, :nullable => false
9
+ property :commit_metadata, Yaml, :nullable => false, :lazy => false
10
+ property :created_at, DateTime
11
+ property :updated_at, DateTime
12
+
13
+ belongs_to :project, :class_name => "Integrity::Project"
14
+
15
+ def failed?
16
+ !successful?
17
+ end
18
+
19
+ def status
20
+ successful? ? :success : :failed
21
+ end
22
+
23
+ def human_readable_status
24
+ successful? ? "Build Successful" : "Build Failed"
25
+ end
26
+
27
+ def short_commit_identifier
28
+ sha1?(commit_identifier) ? commit_identifier[0..6] : commit_identifier
29
+ end
30
+
31
+ def commit_metadata
32
+ case data = attribute_get(:commit_metadata)
33
+ when String; YAML.load(data)
34
+ else data
35
+ end
36
+ end
37
+
38
+ def commit_author
39
+ @author ||= begin
40
+ commit_metadata[:author] =~ /^(.*) <(.*)>$/
41
+ OpenStruct.new(:name => $1.strip, :email => $2.strip, :full => commit_metadata[:author])
42
+ end
43
+ end
44
+
45
+ def commit_message
46
+ commit_metadata[:message]
47
+ end
48
+
49
+ def commited_at
50
+ case commit_metadata[:date]
51
+ when String then Time.parse(commit_metadata[:date])
52
+ else commit_metadata[:date]
53
+ end
54
+ end
55
+
56
+ private
57
+ def sha1?(string)
58
+ string =~ /^[a-f0-9]{40}$/
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,6 @@
1
+ class Object
2
+ def tap
3
+ yield self
4
+ self
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def /(other)
3
+ File.join(self, other)
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ Dir["#{File.dirname(__FILE__)}/helpers/*.rb"].each &method(:require)
2
+
3
+ module Integrity
4
+ module Helpers
5
+ include Authorization
6
+ include Breadcrumbs
7
+ include Forms
8
+ include PrettyOutput
9
+ include Rendering
10
+ include Resources
11
+ include Urls
12
+
13
+ include Rack::Utils
14
+ alias :h :escape_html
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ require "diddies"
2
+
3
+ module Integrity
4
+ module Helpers
5
+ module Authorization
6
+ include Sinatra::Authorization
7
+
8
+ def authorization_realm
9
+ "Integrity"
10
+ end
11
+
12
+ def authorized?
13
+ return true unless Integrity.config[:use_basic_auth]
14
+ !!request.env["REMOTE_USER"]
15
+ end
16
+
17
+ def authorize(user, password)
18
+ if Integrity.config[:hash_admin_password]
19
+ password = Digest::SHA1.hexdigest(password)
20
+ end
21
+
22
+ !Integrity.config[:use_basic_auth] ||
23
+ (Integrity.config[:admin_username] == user &&
24
+ Integrity.config[:admin_password] == password)
25
+ end
26
+
27
+ def unauthorized!(realm=authorization_realm)
28
+ response["WWW-Authenticate"] = %(Basic realm="#{realm}")
29
+ throw :halt, [401, show(:unauthorized, :title => "incorrect credentials")]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ module Integrity
2
+ module Helpers
3
+ module Breadcrumbs
4
+ def pages
5
+ @pages ||= [["projects", "/"], ["new project", "/new"]]
6
+ end
7
+
8
+ def breadcrumbs(*crumbs)
9
+ crumbs[0..-2].map do |crumb|
10
+ if page_data = pages.detect {|c| c.first == crumb }
11
+ %Q(<a href="#{page_data.last}">#{page_data.first}</a>)
12
+ elsif @project && @project.permalink == crumb
13
+ %Q(<a href="#{project_url(@project)}">#{@project.permalink}</a>)
14
+ end
15
+ end + [crumbs.last]
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module Integrity
2
+ module Helpers
3
+ module Forms
4
+ def errors_on(object, field)
5
+ return "" unless errors = object.errors.on(field)
6
+ errors.map {|e| e.gsub(/#{field} /i, "") }.join(", ")
7
+ end
8
+
9
+ def error_class(object, field)
10
+ object.errors.on(field).nil? ? "" : "with_errors"
11
+ end
12
+
13
+ def checkbox(name, condition, extras={})
14
+ attrs = { :name => name, :type => "checkbox", :value => "1" }
15
+ attrs.merge!(:checked => condition ? true : nil)
16
+ attrs.merge(extras)
17
+ end
18
+
19
+ def notifier_form(notifier)
20
+ haml(notifier.to_haml, :layout => :notifier, :locals => {
21
+ :config => current_project.config_for(notifier),
22
+ :notifier => "#{notifier.to_s.split(/::/).last}",
23
+ :enabled => current_project.notifies?(notifier)
24
+ })
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,45 @@
1
+ module Integrity
2
+ module Helpers
3
+ module PrettyOutput
4
+ def cycle(*values)
5
+ @cycles ||= {}
6
+ @cycles[values] ||= -1 # first value returned is 0
7
+ next_value = @cycles[values] = (@cycles[values] + 1) % values.size
8
+ values[next_value]
9
+ end
10
+
11
+ def bash_color_codes(string)
12
+ string.gsub("\e[0m", '</span>').
13
+ gsub("\e[31m", '<span class="color31">').
14
+ gsub("\e[32m", '<span class="color32">').
15
+ gsub("\e[33m", '<span class="color33">').
16
+ gsub("\e[34m", '<span class="color34">').
17
+ gsub("\e[35m", '<span class="color35">').
18
+ gsub("\e[36m", '<span class="color36">').
19
+ gsub("\e[37m", '<span class="color37">')
20
+ end
21
+
22
+ def pretty_date(date_time)
23
+ days_away = (Date.today - Date.new(date_time.year, date_time.month, date_time.day)).to_i
24
+ if days_away == 0
25
+ "today"
26
+ elsif days_away == 1
27
+ "yesterday"
28
+ else
29
+ strftime_with_ordinal(date_time, "on %b %d%o")
30
+ end
31
+ end
32
+
33
+ def strftime_with_ordinal(date_time, format_string)
34
+ ordinal = case date_time.day
35
+ when 1, 21, 31 then "st"
36
+ when 2, 22 then "nd"
37
+ when 3, 23 then "rd"
38
+ else "th"
39
+ end
40
+
41
+ date_time.strftime(format_string.gsub("%o", ordinal))
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ module Integrity
2
+ module Helpers
3
+ module Rendering
4
+ def show(view, options={})
5
+ @title = breadcrumbs(*options[:title])
6
+ haml view
7
+ end
8
+
9
+ def partial(template, locals={})
10
+ haml("_#{template}".to_sym, :locals => locals, :layout => false)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Integrity
2
+ module Helpers
3
+ module Resources
4
+ def current_project
5
+ @project ||= Project.first(:permalink => params[:project]) or raise Sinatra::NotFound
6
+ end
7
+
8
+ def current_build
9
+ @build ||= current_project.builds.first(:commit_identifier => params[:build]) or raise Sinatra::NotFound
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ module Integrity
2
+ module Helpers
3
+ module Urls
4
+ def url(path)
5
+ url = "#{request.scheme}://#{request.host}"
6
+
7
+ if request.scheme == "https" && request.port != 443 ||
8
+ request.scheme == "http" && request.port != 80
9
+ url << ":#{request.port}"
10
+ end
11
+
12
+ url << "/" unless path.index("/").zero?
13
+ url << path
14
+ end
15
+
16
+ def root_url
17
+ url("/")
18
+ end
19
+
20
+ def project_path(project, *path)
21
+ "/" << [project.permalink, *path].join("/")
22
+ end
23
+
24
+ def project_url(project, *path)
25
+ url project_path(project, *path)
26
+ end
27
+
28
+ def push_url_for(project)
29
+ Addressable::URI.parse(project_url(project, "push")).tap do |url|
30
+ if Integrity.config[:use_basic_auth]
31
+ url.user = Integrity.config[:admin_username]
32
+ url.password = Integrity.config[:hash_admin_password] ?
33
+ "<password>" : Integrity.config[:admin_password]
34
+ end
35
+ end.to_s
36
+ end
37
+
38
+ def build_path(build)
39
+ "/#{build.project.permalink}/builds/#{build.commit_identifier}"
40
+ end
41
+
42
+ def build_url(build)
43
+ url build_path(build)
44
+ end
45
+ end
46
+ end
47
+ end