integrity-integrity 0.1.9.3

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 (92) hide show
  1. data/.gitignore +12 -0
  2. data/CHANGES +48 -0
  3. data/README.md +82 -0
  4. data/Rakefile +58 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +21 -0
  7. data/config/config.sample.yml +45 -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 +15 -0
  12. data/config/thin.sample.yml +13 -0
  13. data/integrity.gemspec +136 -0
  14. data/lib/integrity.rb +77 -0
  15. data/lib/integrity/app.rb +138 -0
  16. data/lib/integrity/author.rb +39 -0
  17. data/lib/integrity/build.rb +52 -0
  18. data/lib/integrity/commit.rb +60 -0
  19. data/lib/integrity/core_ext/object.rb +6 -0
  20. data/lib/integrity/helpers.rb +15 -0
  21. data/lib/integrity/helpers/authorization.rb +33 -0
  22. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  23. data/lib/integrity/helpers/pretty_output.rb +45 -0
  24. data/lib/integrity/helpers/rendering.rb +49 -0
  25. data/lib/integrity/helpers/resources.rb +19 -0
  26. data/lib/integrity/helpers/urls.rb +59 -0
  27. data/lib/integrity/installer.rb +145 -0
  28. data/lib/integrity/migrations.rb +151 -0
  29. data/lib/integrity/notifier.rb +44 -0
  30. data/lib/integrity/notifier/base.rb +74 -0
  31. data/lib/integrity/notifier/test.rb +52 -0
  32. data/lib/integrity/notifier/test/fixtures.rb +108 -0
  33. data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
  34. data/lib/integrity/project.rb +93 -0
  35. data/lib/integrity/project/notifiers.rb +31 -0
  36. data/lib/integrity/project/push.rb +43 -0
  37. data/lib/integrity/project_builder.rb +56 -0
  38. data/lib/integrity/scm.rb +19 -0
  39. data/lib/integrity/scm/git.rb +84 -0
  40. data/lib/integrity/scm/git/uri.rb +57 -0
  41. data/public/buttons.css +82 -0
  42. data/public/reset.css +7 -0
  43. data/public/spinner.gif +0 -0
  44. data/test/acceptance/api_test.rb +97 -0
  45. data/test/acceptance/browse_project_builds_test.rb +65 -0
  46. data/test/acceptance/browse_project_test.rb +99 -0
  47. data/test/acceptance/build_notifications_test.rb +114 -0
  48. data/test/acceptance/create_project_test.rb +97 -0
  49. data/test/acceptance/delete_project_test.rb +53 -0
  50. data/test/acceptance/edit_project_test.rb +117 -0
  51. data/test/acceptance/error_page_test.rb +21 -0
  52. data/test/acceptance/installer_test.rb +81 -0
  53. data/test/acceptance/manual_build_project_test.rb +82 -0
  54. data/test/acceptance/not_found_page_test.rb +29 -0
  55. data/test/acceptance/project_syndication_test.rb +30 -0
  56. data/test/acceptance/stylesheet_test.rb +26 -0
  57. data/test/acceptance/unauthorized_page_test.rb +20 -0
  58. data/test/helpers.rb +75 -0
  59. data/test/helpers/acceptance.rb +82 -0
  60. data/test/helpers/acceptance/email_notifier.rb +52 -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/expectations.rb +4 -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/initial_migration_fixture.sql +44 -0
  70. data/test/unit/build_test.rb +72 -0
  71. data/test/unit/commit_test.rb +66 -0
  72. data/test/unit/helpers_test.rb +103 -0
  73. data/test/unit/integrity_test.rb +35 -0
  74. data/test/unit/migrations_test.rb +57 -0
  75. data/test/unit/notifier/base_test.rb +43 -0
  76. data/test/unit/notifier_test.rb +96 -0
  77. data/test/unit/project_builder_test.rb +118 -0
  78. data/test/unit/project_test.rb +344 -0
  79. data/test/unit/scm_test.rb +54 -0
  80. data/views/_commit_info.haml +24 -0
  81. data/views/build.haml +2 -0
  82. data/views/error.haml +37 -0
  83. data/views/home.haml +21 -0
  84. data/views/integrity.sass +400 -0
  85. data/views/layout.haml +29 -0
  86. data/views/new.haml +50 -0
  87. data/views/not_found.haml +31 -0
  88. data/views/notifier.haml +7 -0
  89. data/views/project.builder +21 -0
  90. data/views/project.haml +30 -0
  91. data/views/unauthorized.haml +38 -0
  92. metadata +323 -0
@@ -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,49 @@
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
+
24
+ def errors_on(object, field)
25
+ return "" unless errors = object.errors.on(field)
26
+ errors.map {|e| e.gsub(/#{field} /i, "") }.join(", ")
27
+ end
28
+
29
+ def error_class(object, field)
30
+ object.errors.on(field).nil? ? "" : "with_errors"
31
+ end
32
+
33
+ def checkbox(name, condition, extras={})
34
+ attrs = { :name => name, :type => "checkbox", :value => "1" }
35
+ attrs[:checked] = !!condition
36
+ attrs.update(extras)
37
+ end
38
+
39
+ def notifier_form
40
+ Notifier.available.each_pair { |name, klass|
41
+ haml_concat haml(klass.to_haml, :layout => :notifier, :locals => {
42
+ :notifier => name,
43
+ :enabled => current_project.notifies?(name),
44
+ :config => current_project.config_for(name) })
45
+ }
46
+ end
47
+ end
48
+ end
49
+ 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,145 @@
1
+ require "thor"
2
+ require File.dirname(__FILE__) + "/../integrity"
3
+
4
+ module Integrity
5
+ class Installer < Thor
6
+ include FileUtils
7
+
8
+ def self.database_path
9
+ File.join(ENV["HOME"], ".integrity.sqlite3")
10
+ end
11
+ private_class_method :database_path
12
+
13
+ desc "install [PATH]",
14
+ "Copy template files to PATH for desired deployement strategy
15
+ (either Thin, Passenger or Heroku). Next, go there and edit them."
16
+ method_options :passenger => :boolean,
17
+ :thin => :boolean,
18
+ :heroku => :boolean
19
+ def install(path)
20
+ @root = Pathname(path).expand_path
21
+
22
+ if options[:heroku]
23
+ cp_r Pathname(__FILE__).join("../../../config/heroku"), root
24
+ puts post_heroku_install_message
25
+ else
26
+ create_dir_structure
27
+ copy_template_files
28
+ edit_template_files
29
+ puts post_install_message
30
+ end
31
+ end
32
+
33
+ desc "migrate_db [CONFIG]",
34
+ "Checks the `database_uri` in CONFIG and migrates the
35
+ database up to the lastest version."
36
+ def migrate_db(config)
37
+ Integrity.new(config)
38
+
39
+ require "integrity/migrations"
40
+ Integrity.migrate_db
41
+ end
42
+
43
+ desc "launch [CONFIG]",
44
+ "Launch Integrity real quick. Database is saved in #{database_path}."
45
+ method_options :config => :optional, :port => :optional
46
+ def launch
47
+ require "thin"
48
+ require "do_sqlite3"
49
+
50
+ options[:port] ||= 4567
51
+ config = { :database_uri => "sqlite3://#{ENV["HOME"]}/.integrity.db",
52
+ :base_uri => "http://0.0.0.0:#{options[:port]}",
53
+ :export_directory => "/tmp/integrity-exports" }
54
+ config.merge!(YAML.load_file(options[:config])) if options[:config]
55
+
56
+ migrate_db(config)
57
+
58
+ Thin::Server.start("0.0.0.0", options[:port], Integrity::App)
59
+ rescue LoadError => boom
60
+ $stderr << "Make sure thin and do_sqlite3 are insatalled\n\n"
61
+ raise
62
+ end
63
+
64
+ private
65
+ attr_reader :root
66
+
67
+ def create_dir_structure
68
+ mkdir_p root
69
+
70
+ mkdir_p root / "builds"
71
+ mkdir_p root / "log"
72
+
73
+ if options[:passenger]
74
+ mkdir_p root / "public"
75
+ mkdir_p root / "tmp"
76
+ end
77
+ end
78
+
79
+ def copy_template_files
80
+ copy "config.sample.ru"
81
+ copy "config.sample.yml"
82
+ copy "thin.sample.yml" if options[:thin]
83
+ end
84
+
85
+ def edit_template_files
86
+ edit_integrity_configuration
87
+ edit_thin_configuration if options[:thin]
88
+ end
89
+
90
+ def edit_integrity_configuration
91
+ config = File.read(root / "config.yml")
92
+ config.gsub! %r(sqlite3:///var/integrity.db), "sqlite3://#{root}/integrity.db"
93
+ config.gsub! %r(/path/to/scm/exports), "#{root}/builds"
94
+ config.gsub! %r(/var/log), "#{root}/log"
95
+ File.open(root / "config.yml", "w") { |f| f.puts config }
96
+ end
97
+
98
+ def edit_thin_configuration
99
+ config = File.read(root / "thin.yml")
100
+ config.gsub! %r(/apps/integrity), root
101
+ File.open(root / "thin.yml", 'w') { |f| f.puts config }
102
+ end
103
+
104
+ def post_heroku_install_message
105
+ <<EOF
106
+ Your Integrity install is ready to be deployed onto Heroku. Next steps:
107
+
108
+ 1. git init && git add . && git commit -am "Initial import"
109
+ 2. heroku create
110
+ 3. Add heroku-given domain as :base_uri in integrity-config.rb
111
+ 4. git push heroku master
112
+ 5. heroku rake db:migrate
113
+ EOF
114
+ end
115
+
116
+ def post_install_message
117
+ <<EOF
118
+ Awesome! Integrity was installed successfully!
119
+
120
+ To complete the installation, please configure the `database_uri` in
121
+ #{root.join("config.yml")} and install the matching DataMapper adapter if
122
+ necessary. Then, run `integrity migrate_db #{root.join("config.yml")}
123
+
124
+ == Notifiers
125
+ If you want to enable notifiers, install the gems and then require them
126
+ in #{root}/config.ru
127
+
128
+ For example:
129
+
130
+ sudo gem install -s http://gems.github.com foca-integrity-email
131
+
132
+ And then in #{root}/config.ru add:
133
+
134
+ require "notifier/email"
135
+
136
+ Don't forget to tweak #{root / "config.yml"} to your needs.
137
+ EOF
138
+ end
139
+
140
+ def copy(source)
141
+ cp(Pathname(__FILE__).dirname.join("../../config", source),
142
+ root.join(File.basename(source).gsub(/\.sample/, "")))
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,151 @@
1
+ require "dm-migrations"
2
+ require "migration_runner"
3
+
4
+ module Integrity
5
+ def self.migrate_db
6
+ setup_initial_migration if pre_migrations?
7
+ Integrity::Migrations.migrate_up!
8
+ end
9
+
10
+ def self.setup_initial_migration
11
+ database_adapter.execute %q(CREATE TABLE "migration_info" ("migration_name" VARCHAR(255));)
12
+ database_adapter.execute %q(INSERT INTO "migration_info" ("migration_name") VALUES ("initial"))
13
+ end
14
+
15
+ def self.pre_migrations?
16
+ !table_exists?("migration_info") &&
17
+ ( table_exists?("integrity_projects") &&
18
+ table_exists?("integrity_builds") &&
19
+ table_exists?("integrity_notifiers") )
20
+ end
21
+
22
+ def self.table_exists?(table_name)
23
+ database_adapter.storage_exists?(table_name)
24
+ end
25
+
26
+ def self.database_adapter
27
+ DataMapper.repository(:default).adapter
28
+ end
29
+
30
+ module Migrations
31
+ # This is what is actually happening:
32
+ # include DataMapper::MigrationRunner
33
+
34
+ include DataMapper::Types
35
+
36
+ migration 1, :initial, :verbose => true do
37
+ up do
38
+ create_table :integrity_projects do
39
+ column :id, Integer, :serial => true
40
+ column :name, String, :nullable => false
41
+ column :permalink, String
42
+ column :uri, URI, :nullable => false
43
+ column :branch, String, :nullable => false, :default => "master"
44
+ column :command, String, :nullable => false, :default => "rake"
45
+ column :public, Boolean, :default => true
46
+ column :building, Boolean, :default => false
47
+ column :created_at, DateTime
48
+ column :updated_at, DateTime
49
+
50
+ column :build_id, Integer
51
+ column :notifier_id, Integer
52
+ end
53
+
54
+ create_table :integrity_builds do
55
+ column :id, Integer, :serial => true
56
+ column :output, Text, :nullable => false, :default => ""
57
+ column :successful, Boolean, :nullable => false, :default => false
58
+ column :commit_identifier, String, :nullable => false
59
+ column :commit_metadata, Yaml, :nullable => false
60
+ column :created_at, DateTime
61
+ column :updated_at, DateTime
62
+
63
+ column :project_id, Integer
64
+ end
65
+
66
+ create_table :integrity_notifiers do
67
+ column :id, Integer, :serial => true
68
+ column :name, String, :nullable => false
69
+ column :config, Yaml, :nullable => false
70
+
71
+ column :project_id, Integer
72
+ end
73
+ end
74
+ end
75
+
76
+ migration 2, :add_commits, :verbose => true do
77
+ up do
78
+ class ::Integrity::Build
79
+ property :commit_identifier, String
80
+ property :commit_metadata, Yaml, :lazy => false
81
+ property :project_id, Integer
82
+ end
83
+
84
+ create_table :integrity_commits do
85
+ column :id, Integer, :serial => true
86
+ column :identifier, String, :nullable => false
87
+ column :message, String, :nullable => true, :length => 255
88
+ column :author, String, :nullable => true, :length => 255
89
+ column :committed_at, DateTime, :nullable => false
90
+ column :created_at, DateTime
91
+ column :updated_at, DateTime
92
+
93
+ column :project_id, Integer
94
+ end
95
+
96
+ modify_table :integrity_builds do
97
+ add_column :commit_id, Integer
98
+ add_column :started_at, DateTime
99
+ add_column :completed_at, DateTime
100
+ end
101
+
102
+ # Die, orphans, die
103
+ Build.all(:project_id => nil).destroy!
104
+
105
+ # sqlite hodgepockery
106
+ all_builds = Build.all.each {|b| b.freeze }
107
+ drop_table :integrity_builds
108
+ create_table :integrity_builds do
109
+ column :id, Integer, :serial => true
110
+ column :started_at, DateTime
111
+ column :completed_at, DateTime
112
+ column :successful, Boolean
113
+ column :output, Text, :nullable => false, :default => ""
114
+ column :created_at, DateTime
115
+ column :updated_at, DateTime
116
+
117
+ column :commit_id, Integer
118
+ end
119
+
120
+ all_builds.each do |build|
121
+ commit = Commit.first(:identifier => build.commit_identifier)
122
+
123
+ if commit.nil?
124
+ commit = Commit.create(:identifier => build.commit_identifier,
125
+ :message => build.commit_metadata[:message],
126
+ :author => build.commit_metadata[:author],
127
+ :committed_at => build.commit_metadata[:date],
128
+ :project_id => build.project_id)
129
+ end
130
+
131
+ Build.create(:commit_id => commit.id,
132
+ :started_at => build.created_at,
133
+ :completed_at => build.updated_at,
134
+ :successful => build.successful,
135
+ :output => build.output)
136
+ end
137
+ end
138
+ end
139
+
140
+ migration 3, :add_enabled_column do
141
+ up do
142
+ modify_table(:integrity_notifiers) { add_column :enabled, Boolean }
143
+ end
144
+
145
+ down do
146
+ # TODO: sqlite doesn't support DROP COLUMN ...
147
+ # modify_table(:integrity_notifiers) { drop_column :enabled }
148
+ end
149
+ end
150
+ end
151
+ end