alphasights-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.
- data/.gitignore +13 -0
- data/CHANGES +48 -0
- data/README.md +82 -0
- data/Rakefile +58 -0
- data/bin/integrity +4 -0
- data/config/config.sample.ru +21 -0
- data/config/config.sample.yml +41 -0
- data/config/heroku/.gems +1 -0
- data/config/heroku/Rakefile +6 -0
- data/config/heroku/config.ru +7 -0
- data/config/heroku/integrity-config.rb +14 -0
- data/config/thin.sample.yml +13 -0
- data/integrity.gemspec +137 -0
- data/lib/integrity.rb +77 -0
- data/lib/integrity/app.rb +138 -0
- data/lib/integrity/author.rb +39 -0
- data/lib/integrity/build.rb +52 -0
- data/lib/integrity/commit.rb +61 -0
- data/lib/integrity/core_ext/object.rb +6 -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/gravatar.rb +16 -0
- data/lib/integrity/helpers/pretty_output.rb +45 -0
- data/lib/integrity/helpers/rendering.rb +49 -0
- data/lib/integrity/helpers/resources.rb +19 -0
- data/lib/integrity/helpers/urls.rb +59 -0
- data/lib/integrity/installer.rb +138 -0
- data/lib/integrity/migrations.rb +153 -0
- data/lib/integrity/notifier.rb +44 -0
- data/lib/integrity/notifier/base.rb +74 -0
- data/lib/integrity/notifier/test.rb +52 -0
- data/lib/integrity/notifier/test/fixtures.rb +108 -0
- data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
- data/lib/integrity/project.rb +94 -0
- data/lib/integrity/project/notifiers.rb +31 -0
- data/lib/integrity/project/push.rb +43 -0
- data/lib/integrity/project_builder.rb +56 -0
- data/lib/integrity/scm.rb +19 -0
- data/lib/integrity/scm/git.rb +84 -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/acceptance/api_test.rb +97 -0
- data/test/acceptance/browse_project_builds_test.rb +65 -0
- data/test/acceptance/browse_project_test.rb +99 -0
- data/test/acceptance/build_notifications_test.rb +114 -0
- data/test/acceptance/create_project_test.rb +97 -0
- data/test/acceptance/delete_project_test.rb +53 -0
- data/test/acceptance/edit_project_test.rb +117 -0
- data/test/acceptance/error_page_test.rb +21 -0
- data/test/acceptance/installer_test.rb +81 -0
- data/test/acceptance/manual_build_project_test.rb +82 -0
- data/test/acceptance/not_found_page_test.rb +29 -0
- data/test/acceptance/project_syndication_test.rb +30 -0
- data/test/acceptance/stylesheet_test.rb +26 -0
- data/test/acceptance/unauthorized_page_test.rb +20 -0
- data/test/helpers.rb +75 -0
- data/test/helpers/acceptance.rb +82 -0
- data/test/helpers/acceptance/email_notifier.rb +52 -0
- data/test/helpers/acceptance/git_helper.rb +99 -0
- data/test/helpers/acceptance/notifier_helper.rb +47 -0
- data/test/helpers/acceptance/textfile_notifier.rb +26 -0
- data/test/helpers/expectations.rb +4 -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/predicates.rb +37 -0
- data/test/helpers/initial_migration_fixture.sql +44 -0
- data/test/unit/build_test.rb +72 -0
- data/test/unit/commit_test.rb +66 -0
- data/test/unit/helpers_test.rb +103 -0
- data/test/unit/integrity_test.rb +35 -0
- data/test/unit/migrations_test.rb +57 -0
- data/test/unit/notifier/base_test.rb +43 -0
- data/test/unit/notifier_test.rb +96 -0
- data/test/unit/project_builder_test.rb +118 -0
- data/test/unit/project_test.rb +344 -0
- data/test/unit/scm_test.rb +54 -0
- data/views/_commit_info.haml +30 -0
- data/views/build.haml +2 -0
- data/views/error.haml +37 -0
- data/views/home.haml +22 -0
- data/views/integrity.sass +424 -0
- data/views/layout.haml +29 -0
- data/views/new.haml +50 -0
- data/views/not_found.haml +31 -0
- data/views/notifier.haml +7 -0
- data/views/project.builder +21 -0
- data/views/project.haml +31 -0
- data/views/unauthorized.haml +38 -0
- metadata +324 -0
data/lib/integrity.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "haml"
|
5
|
+
require "dm-core"
|
6
|
+
require "dm-validations"
|
7
|
+
require "dm-types"
|
8
|
+
require "dm-timestamps"
|
9
|
+
require "dm-aggregates"
|
10
|
+
require "sinatra/base"
|
11
|
+
|
12
|
+
require "yaml"
|
13
|
+
require "logger"
|
14
|
+
require "digest/sha1"
|
15
|
+
require "timeout"
|
16
|
+
require "ostruct"
|
17
|
+
require "pathname"
|
18
|
+
|
19
|
+
require "integrity/core_ext/object"
|
20
|
+
|
21
|
+
require "integrity/project"
|
22
|
+
require "integrity/author"
|
23
|
+
require "integrity/commit"
|
24
|
+
require "integrity/build"
|
25
|
+
require "integrity/project_builder"
|
26
|
+
require "integrity/scm"
|
27
|
+
require "integrity/scm/git"
|
28
|
+
require "integrity/notifier"
|
29
|
+
require "integrity/helpers"
|
30
|
+
require "integrity/app"
|
31
|
+
|
32
|
+
module Integrity
|
33
|
+
def self.new(config=nil)
|
34
|
+
if config.is_a?(String) && File.file?(config)
|
35
|
+
self.config = YAML.load_file(config)
|
36
|
+
elsif config.is_a?(Hash)
|
37
|
+
self.config = config
|
38
|
+
end
|
39
|
+
|
40
|
+
DataMapper.setup(:default, self.config[:database_uri])
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.default_configuration
|
44
|
+
@defaults ||= { :database_uri => "sqlite3::memory:",
|
45
|
+
:export_directory => "/tmp/exports",
|
46
|
+
:log => STDOUT,
|
47
|
+
:base_uri => "http://localhost:8910",
|
48
|
+
:use_basic_auth => false,
|
49
|
+
:build_all_commits => true,
|
50
|
+
:log_debug_info => false }
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.config
|
54
|
+
@config ||= default_configuration.dup
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.config=(options)
|
58
|
+
@config = default_configuration.merge(options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.log(message, &block)
|
62
|
+
logger.info(message, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.logger
|
66
|
+
@logger ||= Logger.new(config[:log], "daily").tap do |logger|
|
67
|
+
logger.formatter = LogFormatter.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
private_class_method :logger
|
71
|
+
|
72
|
+
class LogFormatter < Logger::Formatter
|
73
|
+
def call(severity, time, progname, msg)
|
74
|
+
time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Integrity
|
2
|
+
class App < Sinatra::Default
|
3
|
+
set :root, File.dirname(__FILE__) + "/../.."
|
4
|
+
set :app_file, __FILE__
|
5
|
+
enable :sessions
|
6
|
+
|
7
|
+
include Integrity
|
8
|
+
include Integrity::Helpers
|
9
|
+
|
10
|
+
not_found do
|
11
|
+
status 404
|
12
|
+
show :not_found, :title => "lost, are we?"
|
13
|
+
end
|
14
|
+
|
15
|
+
error do
|
16
|
+
@error = request.env["sinatra.error"]
|
17
|
+
status 500
|
18
|
+
show :error, :title => "something has gone terribly wrong"
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
# The browser only sends http auth data for requests that are explicitly
|
23
|
+
# required to do so. This way we get the real values of +#logged_in?+ and
|
24
|
+
# +#current_user+
|
25
|
+
login_required if session[:user]
|
26
|
+
end
|
27
|
+
|
28
|
+
get "/integrity.css" do
|
29
|
+
response["Content-Type"] = "text/css; charset=utf-8"
|
30
|
+
etag stylesheet_hash
|
31
|
+
sass :integrity
|
32
|
+
end
|
33
|
+
|
34
|
+
get "/?" do
|
35
|
+
@projects = authorized? ? Project.all : Project.all(:public => true)
|
36
|
+
show :home, :title => "projects"
|
37
|
+
end
|
38
|
+
|
39
|
+
get "/login" do
|
40
|
+
login_required
|
41
|
+
|
42
|
+
session[:user] = current_user
|
43
|
+
redirect root_url.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
get "/new" do
|
47
|
+
login_required
|
48
|
+
|
49
|
+
@project = Project.new
|
50
|
+
show :new, :title => ["projects", "new project"]
|
51
|
+
end
|
52
|
+
|
53
|
+
post "/?" do
|
54
|
+
login_required
|
55
|
+
|
56
|
+
@project = Project.new(params[:project_data])
|
57
|
+
|
58
|
+
if @project.save
|
59
|
+
update_notifiers_of(@project)
|
60
|
+
redirect project_url(@project).to_s
|
61
|
+
else
|
62
|
+
show :new, :title => ["projects", "new project"]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
get "/:project.atom" do
|
67
|
+
login_required unless current_project.public?
|
68
|
+
response["Content-Type"] = "application/rss+xml; charset=utf-8"
|
69
|
+
builder :project
|
70
|
+
end
|
71
|
+
|
72
|
+
get "/:project" do
|
73
|
+
login_required unless current_project.public?
|
74
|
+
show :project, :title => ["projects", current_project.name]
|
75
|
+
end
|
76
|
+
|
77
|
+
put "/:project" do
|
78
|
+
login_required
|
79
|
+
|
80
|
+
if current_project.update_attributes(params[:project_data])
|
81
|
+
update_notifiers_of(current_project)
|
82
|
+
redirect project_url(current_project).to_s
|
83
|
+
else
|
84
|
+
show :new, :title => ["projects", current_project.permalink, "edit"]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
delete "/:project" do
|
89
|
+
login_required
|
90
|
+
|
91
|
+
current_project.destroy
|
92
|
+
redirect root_url.to_s
|
93
|
+
end
|
94
|
+
|
95
|
+
get "/:project/edit" do
|
96
|
+
login_required
|
97
|
+
|
98
|
+
show :new, :title => ["projects", current_project.permalink, "edit"]
|
99
|
+
end
|
100
|
+
|
101
|
+
post "/:project/push" do
|
102
|
+
login_required
|
103
|
+
|
104
|
+
content_type "text/plain"
|
105
|
+
|
106
|
+
begin
|
107
|
+
current_project.push(params[:payload])
|
108
|
+
201
|
109
|
+
rescue ArgumentError
|
110
|
+
[422, "Invalid Request"]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
post "/:project/builds" do
|
115
|
+
login_required
|
116
|
+
|
117
|
+
current_project.build
|
118
|
+
redirect project_url(current_project).to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
get "/:project/commits/:commit" do
|
122
|
+
login_required unless current_project.public?
|
123
|
+
|
124
|
+
show :build, :title => ["projects", current_project.permalink, current_commit.short_identifier]
|
125
|
+
end
|
126
|
+
|
127
|
+
get "/:project/builds/:commit" do
|
128
|
+
redirect "/#{params[:project]}/commits/#{params[:commit]}", 301
|
129
|
+
end
|
130
|
+
|
131
|
+
post "/:project/commits/:commit/builds" do
|
132
|
+
login_required
|
133
|
+
|
134
|
+
current_project.build(params[:commit])
|
135
|
+
redirect commit_url(current_commit).to_s
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Author < DataMapper::Type
|
3
|
+
primitive String
|
4
|
+
size 65535
|
5
|
+
lazy true
|
6
|
+
|
7
|
+
class AuthorStruct < Struct.new(:name, :email)
|
8
|
+
def self.parse(string)
|
9
|
+
raise ArgumentError.new("invalid author string") unless string =~ /^(.*) <(.*)>$/
|
10
|
+
|
11
|
+
new($1.strip, $2.strip)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
@full ||= "#{name} <#{email}>"
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :full, :to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.load(value, property)
|
22
|
+
AuthorStruct.parse(value) unless value.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.dump(value, property)
|
26
|
+
return nil if value.nil?
|
27
|
+
|
28
|
+
value.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.typecast(value, property)
|
32
|
+
case value
|
33
|
+
when AuthorStruct then value
|
34
|
+
when NilClass then load(nil, property)
|
35
|
+
else load(value.to_s, property)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,52 @@
|
|
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
|
+
:child_key => [:commit_id]
|
16
|
+
|
17
|
+
def self.pending
|
18
|
+
all(:started_at => nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.queue(commit)
|
22
|
+
commit.update_attributes(:build => new)
|
23
|
+
|
24
|
+
# Build on foreground (this will move away, I promise)
|
25
|
+
ProjectBuilder.build(commit)
|
26
|
+
end
|
27
|
+
|
28
|
+
def pending?
|
29
|
+
started_at.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def failed?
|
33
|
+
!successful?
|
34
|
+
end
|
35
|
+
|
36
|
+
def status
|
37
|
+
case
|
38
|
+
when pending? then :pending
|
39
|
+
when successful? then :success
|
40
|
+
when failed? then :failed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def start!(time=Time.now)
|
45
|
+
self.started_at = time
|
46
|
+
end
|
47
|
+
|
48
|
+
def complete!(time=Time.now)
|
49
|
+
self.completed_at = time
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,61 @@
|
|
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 :url, String
|
10
|
+
property :committed_at, DateTime
|
11
|
+
property :created_at, DateTime
|
12
|
+
property :updated_at, DateTime
|
13
|
+
|
14
|
+
has 1, :build, :class_name => "Integrity::Build",
|
15
|
+
:order => [:created_at.desc]
|
16
|
+
|
17
|
+
belongs_to :project, :class_name => "Integrity::Project",
|
18
|
+
:child_key => [:project_id]
|
19
|
+
|
20
|
+
def message
|
21
|
+
attribute_get(:message) || "<Commit message not loaded>"
|
22
|
+
end
|
23
|
+
|
24
|
+
def author
|
25
|
+
attribute_get(:author) ||
|
26
|
+
Author.load('<Commit author not loaded> <<Commit author not loaded>>', :author)
|
27
|
+
end
|
28
|
+
|
29
|
+
def short_identifier
|
30
|
+
identifier.to_s[0..6]
|
31
|
+
end
|
32
|
+
|
33
|
+
def status
|
34
|
+
build.nil? ? :pending : build.status
|
35
|
+
end
|
36
|
+
|
37
|
+
def successful?
|
38
|
+
status == :success
|
39
|
+
end
|
40
|
+
|
41
|
+
def failed?
|
42
|
+
status == :failed
|
43
|
+
end
|
44
|
+
|
45
|
+
def pending?
|
46
|
+
status == :pending
|
47
|
+
end
|
48
|
+
|
49
|
+
def human_readable_status
|
50
|
+
case status
|
51
|
+
when :success; "Built #{short_identifier} successfully"
|
52
|
+
when :failed; "Built #{short_identifier} and failed"
|
53
|
+
when :pending; "#{short_identifier} hasn't been built yet"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def output
|
58
|
+
build && build.output
|
59
|
+
end
|
60
|
+
end
|
61
|
+
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 PrettyOutput
|
8
|
+
include Rendering
|
9
|
+
include Resources
|
10
|
+
include Urls
|
11
|
+
include Gravatar
|
12
|
+
|
13
|
+
include Rack::Utils
|
14
|
+
alias :h :escape_html
|
15
|
+
end
|
16
|
+
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
|