imbriaco-integrity 0.1.9.2

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 (96) hide show
  1. data/.gitignore +12 -0
  2. data/CHANGES +36 -0
  3. data/README.markdown +79 -0
  4. data/Rakefile +79 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +21 -0
  7. data/config/config.sample.yml +41 -0
  8. data/config/heroku/.gems +1 -0
  9. data/config/heroku/Rakefile +6 -0
  10. data/config/heroku/config.ru +7 -0
  11. data/config/heroku/integrity-config.rb +14 -0
  12. data/config/thin.sample.yml +13 -0
  13. data/integrity.gemspec +140 -0
  14. data/lib/integrity/app.rb +138 -0
  15. data/lib/integrity/author.rb +39 -0
  16. data/lib/integrity/build.rb +91 -0
  17. data/lib/integrity/commit.rb +63 -0
  18. data/lib/integrity/core_ext/object.rb +6 -0
  19. data/lib/integrity/helpers/authorization.rb +33 -0
  20. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  21. data/lib/integrity/helpers/forms.rb +29 -0
  22. data/lib/integrity/helpers/pretty_output.rb +45 -0
  23. data/lib/integrity/helpers/rendering.rb +25 -0
  24. data/lib/integrity/helpers/resources.rb +19 -0
  25. data/lib/integrity/helpers/urls.rb +59 -0
  26. data/lib/integrity/helpers.rb +16 -0
  27. data/lib/integrity/installer.rb +136 -0
  28. data/lib/integrity/migrations.rb +151 -0
  29. data/lib/integrity/notifier/base.rb +74 -0
  30. data/lib/integrity/notifier/test/fixtures.rb +108 -0
  31. data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
  32. data/lib/integrity/notifier/test.rb +59 -0
  33. data/lib/integrity/notifier.rb +34 -0
  34. data/lib/integrity/project/deprecated.rb +17 -0
  35. data/lib/integrity/project/notifiers.rb +33 -0
  36. data/lib/integrity/project/push.rb +44 -0
  37. data/lib/integrity/project.rb +102 -0
  38. data/lib/integrity/project_builder.rb +56 -0
  39. data/lib/integrity/scm/git/uri.rb +57 -0
  40. data/lib/integrity/scm/git.rb +84 -0
  41. data/lib/integrity/scm.rb +19 -0
  42. data/lib/integrity.rb +101 -0
  43. data/public/buttons.css +82 -0
  44. data/public/reset.css +7 -0
  45. data/public/spinner.gif +0 -0
  46. data/test/acceptance/api_test.rb +97 -0
  47. data/test/acceptance/browse_project_builds_test.rb +65 -0
  48. data/test/acceptance/browse_project_test.rb +99 -0
  49. data/test/acceptance/build_notifications_test.rb +95 -0
  50. data/test/acceptance/create_project_test.rb +97 -0
  51. data/test/acceptance/delete_project_test.rb +53 -0
  52. data/test/acceptance/edit_project_test.rb +117 -0
  53. data/test/acceptance/error_page_test.rb +18 -0
  54. data/test/acceptance/installer_test.rb +79 -0
  55. data/test/acceptance/manual_build_project_test.rb +82 -0
  56. data/test/acceptance/not_found_page_test.rb +29 -0
  57. data/test/acceptance/project_syndication_test.rb +30 -0
  58. data/test/acceptance/stylesheet_test.rb +26 -0
  59. data/test/acceptance/unauthorized_page_test.rb +20 -0
  60. data/test/helpers/acceptance/email_notifier.rb +55 -0
  61. data/test/helpers/acceptance/git_helper.rb +99 -0
  62. data/test/helpers/acceptance/notifier_helper.rb +47 -0
  63. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  64. data/test/helpers/acceptance.rb +79 -0
  65. data/test/helpers/expectations/be_a.rb +23 -0
  66. data/test/helpers/expectations/change.rb +90 -0
  67. data/test/helpers/expectations/have.rb +105 -0
  68. data/test/helpers/expectations/predicates.rb +37 -0
  69. data/test/helpers/expectations.rb +4 -0
  70. data/test/helpers/fixtures.rb +87 -0
  71. data/test/helpers/initial_migration_fixture.sql +44 -0
  72. data/test/helpers.rb +87 -0
  73. data/test/unit/build_test.rb +86 -0
  74. data/test/unit/commit_test.rb +62 -0
  75. data/test/unit/helpers_test.rb +103 -0
  76. data/test/unit/integrity_test.rb +52 -0
  77. data/test/unit/migrations_test.rb +57 -0
  78. data/test/unit/notifier/base_test.rb +43 -0
  79. data/test/unit/notifier/test_test.rb +29 -0
  80. data/test/unit/notifier_test.rb +85 -0
  81. data/test/unit/project_builder_test.rb +118 -0
  82. data/test/unit/project_test.rb +371 -0
  83. data/test/unit/scm_test.rb +54 -0
  84. data/views/_commit_info.haml +24 -0
  85. data/views/build.haml +2 -0
  86. data/views/error.haml +37 -0
  87. data/views/home.haml +21 -0
  88. data/views/integrity.sass +400 -0
  89. data/views/layout.haml +29 -0
  90. data/views/new.haml +50 -0
  91. data/views/not_found.haml +31 -0
  92. data/views/notifier.haml +7 -0
  93. data/views/project.builder +21 -0
  94. data/views/project.haml +30 -0
  95. data/views/unauthorized.haml +38 -0
  96. metadata +327 -0
@@ -0,0 +1,91 @@
1
+ module Integrity
2
+ class Build
3
+ include DataMapper::Resource
4
+
5
+ property :id, Integer, :serial => true
6
+ property :output, Text, :default => "", :lazy => false
7
+ property :successful, Boolean, :default => false
8
+ property :commit_id, Integer, :nullable => false
9
+ property :created_at, DateTime
10
+ property :updated_at, DateTime
11
+ property :started_at, DateTime
12
+ property :completed_at, DateTime
13
+
14
+ belongs_to :commit, :class_name => "Integrity::Commit"
15
+
16
+ def self.pending
17
+ all(:started_at => nil)
18
+ end
19
+
20
+ def self.queue(commit)
21
+ commit.update_attributes(:build => new)
22
+
23
+ # Build on foreground (this will move away, I promise)
24
+ ProjectBuilder.build(commit)
25
+ end
26
+
27
+ def pending?
28
+ started_at.nil?
29
+ end
30
+
31
+ def failed?
32
+ !successful?
33
+ end
34
+
35
+ def status
36
+ case
37
+ when pending? then :pending
38
+ when successful? then :success
39
+ when failed? then :failed
40
+ end
41
+ end
42
+
43
+ def start!(time=Time.now)
44
+ self.started_at = time
45
+ end
46
+
47
+ def complete!(time=Time.now)
48
+ self.completed_at = time
49
+ end
50
+
51
+ #
52
+ # Deprecated methods
53
+ #
54
+ def short_commit_identifier
55
+ warn "Build#short_commit_identifier is deprecated, use Commit#short_identifier (#{caller[0]})"
56
+ commit.short_identifier
57
+ end
58
+
59
+ def commit_identifier
60
+ warn "Build#commit_identifier is deprecated, use Commit#identifier (#{caller[0]})"
61
+ commit.identifier
62
+ end
63
+
64
+ def commit_author
65
+ warn "Build#commit_author is deprecated, use Commit#author (#{caller[0]})"
66
+ commit.author
67
+ end
68
+
69
+ def commit_message
70
+ warn "Build#commit_message is deprecated, use Commit#message (#{caller[0]})"
71
+ commit.message
72
+ end
73
+
74
+ def commited_at
75
+ warn "Build#commited_at is deprecated, use Commit#committed_at (#{caller[0]})"
76
+ commit.committed_at
77
+ end
78
+
79
+ def project_id
80
+ warn "Build#project_id is deprecated, use Commit#project_id (#{caller[0]})"
81
+ commit.project_id
82
+ end
83
+
84
+ def commit_metadata
85
+ warn "Build#commit_metadata is deprecated, use the different methods in Commit instead (#{caller[0]})"
86
+ { :message => commit.message,
87
+ :author => commit.author,
88
+ :date => commit.committed_at }
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,63 @@
1
+ module Integrity
2
+ class Commit
3
+ include DataMapper::Resource
4
+
5
+ property :id, Integer, :serial => true
6
+ property :identifier, String, :nullable => false
7
+ property :message, String, :length => 255
8
+ property :author, Author, :length => 255
9
+ property :committed_at, DateTime
10
+ property :created_at, DateTime
11
+ property :updated_at, DateTime
12
+
13
+ has 1, :build, :class_name => "Integrity::Build", :order => [:created_at.desc]
14
+ belongs_to :project, :class_name => "Integrity::Project"
15
+
16
+ def message
17
+ attribute_get(:message) || "<Commit message not loaded>"
18
+ end
19
+
20
+ def author
21
+ attribute_get(:author) || Author.load('<Commit author not loaded> <<Commit author not loaded>>', :author)
22
+ end
23
+
24
+ def short_identifier
25
+ identifier.to_s[0..6]
26
+ end
27
+
28
+ def status
29
+ build.nil? ? :pending : build.status
30
+ end
31
+
32
+ def successful?
33
+ status == :success
34
+ end
35
+
36
+ def failed?
37
+ status == :failed
38
+ end
39
+
40
+ def pending?
41
+ status == :pending
42
+ end
43
+
44
+ def human_readable_status
45
+ case status
46
+ when :success; "Built #{short_identifier} successfully"
47
+ when :failed; "Built #{short_identifier} and failed"
48
+ when :pending; "#{short_identifier} hasn't been built yet"
49
+ end
50
+ end
51
+
52
+ def output
53
+ build && build.output
54
+ end
55
+
56
+ # Deprecation layer
57
+ alias :short_commit_identifier :short_identifier
58
+ alias :commit_identifier :identifier
59
+ alias :commit_author :author
60
+ alias :commit_message :message
61
+ alias :commited_at :committed_at
62
+ end
63
+ end
@@ -0,0 +1,6 @@
1
+ class Object
2
+ def tap
3
+ yield self
4
+ self
5
+ end
6
+ end
@@ -0,0 +1,33 @@
1
+ require "sinatra/authorization"
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", root_path("/")], ["new project", root_path("/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,29 @@
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[:checked] = !!condition
16
+ attrs.update(extras)
17
+ end
18
+
19
+ def notifier_form
20
+ Notifier.available.each_pair { |name, klass|
21
+ haml_concat haml(klass.to_haml, :layout => :notifier, :locals => {
22
+ :notifier => name,
23
+ :enabled => current_project.notifies?(name),
24
+ :config => current_project.config_for(name) })
25
+ }
26
+ end
27
+ end
28
+ end
29
+ 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,25 @@
1
+ module Integrity
2
+ module Helpers
3
+ module Rendering
4
+ def stylesheets(*sheets)
5
+ sheets.each { |sheet|
6
+ haml_tag(:link, :href => root_path("/#{sheet}.css"),
7
+ :type => "text/css", :rel => "stylesheet")
8
+ }
9
+ end
10
+
11
+ def stylesheet_hash
12
+ @_hash ||= Digest::MD5.file(options.views + "/integrity.sass").hexdigest
13
+ end
14
+
15
+ def show(view, options={})
16
+ @title = breadcrumbs(*options[:title])
17
+ haml view
18
+ end
19
+
20
+ def partial(template, locals={})
21
+ haml("_#{template}".to_sym, :locals => locals, :layout => false)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
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_commit
9
+ @commit ||= current_project.commits.first(:identifier => params[:commit]) or raise Sinatra::NotFound
10
+ end
11
+
12
+ def update_notifiers_of(project)
13
+ if params["notifiers"]
14
+ project.update_notifiers(params["enabled_notifiers"] || [], params["notifiers"])
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,59 @@
1
+ module Integrity
2
+ module Helpers
3
+ module Urls
4
+ def root_url
5
+ @url ||= Addressable::URI.parse(base_url)
6
+ end
7
+
8
+ def root_path(path="")
9
+ url(path).path
10
+ end
11
+
12
+ def project_url(project, *path)
13
+ url("/" << [project.permalink, *path].flatten.join("/"))
14
+ end
15
+
16
+ def project_path(project, *path)
17
+ project_url(project, path).path
18
+ end
19
+
20
+ def commit_url(commit)
21
+ project_url(commit.project, "commits", commit.identifier)
22
+ end
23
+
24
+ def commit_path(commit, *path)
25
+ commit_url(commit).path
26
+ end
27
+
28
+ def build_path(build, *path)
29
+ warn "#build_path is deprecated, use #commit_path instead (#{caller[0]})"
30
+ commit_path build.commit, *path
31
+ end
32
+
33
+ def build_url(build)
34
+ warn "#build_url is deprecated, use #commit_url instead (#{caller[0]})"
35
+ commit_url build.commit
36
+ end
37
+
38
+ def push_url_for(project)
39
+ Addressable::URI.parse(project_url(project, "push")).tap do |url|
40
+ if Integrity.config[:use_basic_auth]
41
+ url.user = Integrity.config[:admin_username]
42
+ url.password = Integrity.config[:hash_admin_password] ?
43
+ "<password>" : Integrity.config[:admin_password]
44
+ end
45
+ end.to_s
46
+ end
47
+
48
+ private
49
+ def url(path="")
50
+ root_url.dup.tap { |url| url.path = root_url.path + path }
51
+ end
52
+
53
+ def base_url
54
+ Integrity.config[:base_uri] || ((respond_to?(:request) &&
55
+ request.respond_to?(:url)) ? request.url : fail("set base_uri"))
56
+ end
57
+ end
58
+ end
59
+ 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,136 @@
1
+ require "thor"
2
+ require File.dirname(__FILE__) + "/../integrity"
3
+
4
+ module Integrity
5
+ class Installer < Thor
6
+ include FileUtils
7
+
8
+ desc "install [PATH]",
9
+ "Copy template files to PATH for desired deployement strategy
10
+ (either Thin, Passenger or Heroku). Next, go there and edit them."
11
+ method_options :passenger => :boolean,
12
+ :thin => :boolean,
13
+ :heroku => :boolean
14
+ def install(path)
15
+ @root = Pathname(path).expand_path
16
+
17
+ if options[:heroku]
18
+ cp_r Pathname(__FILE__).join("../../../config/heroku"), root
19
+ puts post_heroku_install_message
20
+ else
21
+ create_dir_structure
22
+ copy_template_files
23
+ edit_template_files
24
+ puts post_install_message
25
+ end
26
+ end
27
+
28
+ desc "migrate_db [CONFIG]",
29
+ "Checks the `database_uri` in CONFIG and migrates the
30
+ database up to the lastest version."
31
+ def migrate_db(config)
32
+ Integrity.new(config)
33
+
34
+ require "integrity/migrations"
35
+ Integrity.migrate_db
36
+ end
37
+
38
+ desc "launch [CONFIG]",
39
+ "Launch Integrity real quick."
40
+ method_options :config => :optional, :port => 4567
41
+ def launch
42
+ require "thin"
43
+ require "do_sqlite3"
44
+
45
+ File.file?(options[:config].to_s) ?
46
+ Integrity.new(options[:config]) : Integrity.new
47
+
48
+ DataMapper.auto_migrate!
49
+
50
+ Thin::Server.start("0.0.0.0", options[:port], Integrity::App)
51
+ rescue LoadError => boom
52
+ missing_dependency = boom.message.split("--").last.lstrip
53
+ puts "Please install #{missing_dependency} to launch Integrity"
54
+ end
55
+
56
+ private
57
+ attr_reader :root
58
+
59
+ def create_dir_structure
60
+ mkdir_p root
61
+
62
+ mkdir_p root / "builds"
63
+ mkdir_p root / "log"
64
+
65
+ if options[:passenger]
66
+ mkdir_p root / "public"
67
+ mkdir_p root / "tmp"
68
+ end
69
+ end
70
+
71
+ def copy_template_files
72
+ copy "config.sample.ru"
73
+ copy "config.sample.yml"
74
+ copy "thin.sample.yml" if options[:thin]
75
+ end
76
+
77
+ def edit_template_files
78
+ edit_integrity_configuration
79
+ edit_thin_configuration if options[:thin]
80
+ end
81
+
82
+ def edit_integrity_configuration
83
+ config = File.read(root / "config.yml")
84
+ config.gsub! %r(sqlite3:///var/integrity.db), "sqlite3://#{root}/integrity.db"
85
+ config.gsub! %r(/path/to/scm/exports), "#{root}/builds"
86
+ config.gsub! %r(/var/log), "#{root}/log"
87
+ File.open(root / "config.yml", "w") { |f| f.puts config }
88
+ end
89
+
90
+ def edit_thin_configuration
91
+ config = File.read(root / "thin.yml")
92
+ config.gsub! %r(/apps/integrity), root
93
+ File.open(root / "thin.yml", 'w') { |f| f.puts config }
94
+ end
95
+
96
+ def post_heroku_install_message
97
+ <<EOF
98
+ Your Integrity install is ready to be deployed onto Heroku. Next steps:
99
+
100
+ 1. git init && git add . && git commit -am "Initial import"
101
+ 2. heroku create
102
+ 3. git push heroku master
103
+ 4. heroku rake db:migrate
104
+ EOF
105
+ end
106
+
107
+ def post_install_message
108
+ <<EOF
109
+ Awesome! Integrity was installed successfully!
110
+
111
+ To complete the installation, please configure the `database_uri` in
112
+ #{root.join("config.yml")} and install the matching DataMapper adapter if
113
+ necessary. Then, run `integrity migrate_db #{root.join("config.yml")}
114
+
115
+ == Notifiers
116
+ If you want to enable notifiers, install the gems and then require them
117
+ in #{root}/config.ru
118
+
119
+ For example:
120
+
121
+ sudo gem install -s http://gems.github.com foca-integrity-email
122
+
123
+ And then in #{root}/config.ru add:
124
+
125
+ require "notifier/email"
126
+
127
+ Don't forget to tweak #{root / "config.yml"} to your needs.
128
+ EOF
129
+ end
130
+
131
+ def copy(source)
132
+ cp(Pathname(__FILE__).dirname.join("../../config", source),
133
+ root.join(File.basename(source).gsub(/\.sample/, "")))
134
+ end
135
+ end
136
+ end