defunkt-integrity 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "thor"
4
+
5
+ require File.dirname(__FILE__) + "/../lib/integrity"
6
+
7
+ class WithIntegrity < Thor
8
+ include FileUtils
9
+
10
+ desc "Install integrity at PATH",
11
+ "Copy template files at PATH. After this, edit the files to your convenience"
12
+ def install(path)
13
+ @root = File.expand_path(path)
14
+
15
+ create_dir_structure
16
+ copy_template_files
17
+ edit_template_files
18
+ create_db(root / 'config.yml')
19
+ after_setup_message
20
+ end
21
+
22
+ desc "Create databases for CONFIG",
23
+ "Create the database necessary to run Integrity"
24
+ def create_db(config)
25
+ Integrity.config = config
26
+ Integrity.new
27
+ DataMapper.auto_migrate!
28
+ end
29
+
30
+ private
31
+ attr_reader :root
32
+
33
+ def create_dir_structure
34
+ mkdir_p root
35
+ mkdir_p root / "builds"
36
+ mkdir_p root / "log"
37
+ end
38
+
39
+ def copy_template_files
40
+ cp Integrity.root / "config" / "config.sample.ru", root / "config.ru"
41
+ cp Integrity.root / "config" / "config.sample.yml", root / "config.yml"
42
+ cp Integrity.root / "config" / "thin.sample.yml", root / "thin.yml"
43
+ end
44
+
45
+ def edit_template_files
46
+ edit_integrity_configuration
47
+ edit_thin_configuration
48
+ end
49
+
50
+ def edit_integrity_configuration
51
+ config = File.read(root / "config.yml")
52
+ config.gsub!(%r(sqlite3:///var/integrity.db), "sqlite3://#{root}/integrity.db")
53
+ config.gsub!(%r(/path/to/scm/exports), "#{root}/builds")
54
+ File.open(root / "config.yml", "w") {|f| f.puts config }
55
+ end
56
+
57
+ def edit_thin_configuration
58
+ config = File.read(root / 'thin.yml')
59
+ config.gsub!(%r(/apps/integrity), root)
60
+ File.open(root / 'thin.yml', 'w') { |f| f.puts config }
61
+ end
62
+
63
+ def after_setup_message
64
+ puts
65
+ puts %Q(Awesome! Integrity was installed successfully!)
66
+ puts
67
+ puts %Q(If you want to enable notifiers, install the gems and then require them)
68
+ puts %Q(in #{root}/config.ru)
69
+ puts
70
+ puts %Q(For example:)
71
+ puts
72
+ puts %Q( sudo gem install -s http://gems.github.com foca-integrity-email)
73
+ puts
74
+ puts %Q(And then in #{root}/config.ru add:)
75
+ puts
76
+ puts %Q( require "notifier/email")
77
+ end
78
+ end
79
+
80
+ WithIntegrity.start
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'integrity'
4
+
5
+ # If you want to add any notifiers, install the gems and then require them here
6
+ # For example, to enable the Email notifier: install the gem (from github:
7
+ #
8
+ # sudo gem install -s http://gems.github.com foca-integrity-email
9
+ #
10
+ # And then uncomment the following line:
11
+ #
12
+ # require "notifier/email"
13
+
14
+ # Load integrity's configuration.
15
+ Integrity.config = File.expand_path('./config.yml')
16
+
17
+ #######################################################################
18
+ ## ##
19
+ ## == DON'T EDIT ANYTHING BELOW UNLESS YOU KNOW WHAT YOU'RE DOING == ##
20
+ ## ##
21
+ #######################################################################
22
+ require Integrity.root / 'app'
23
+
24
+ set :public, Integrity.root / 'public'
25
+ set :views, Integrity.root / 'views'
26
+ set :port, 8910
27
+ set :env, :production
28
+ disable :run, :reload
29
+
30
+ run Sinatra.application
@@ -0,0 +1,31 @@
1
+ # Domain where integrity will be running from. This is used to have
2
+ # nice URLs in your notifications.
3
+ # For example:
4
+ # http://builder.integrityapp.com
5
+ :base_uri: http://integrity.domain.tld
6
+
7
+ # This should be a complete connection string to your database. For example
8
+ # `mysql://user@localhost/integrity` (you need an `integrity` db created in
9
+ # localhost, of course).
10
+ :database_uri: sqlite3:///var/integrity.db
11
+
12
+ # This is where your project's code will be checked out to. Make sure it's
13
+ # writable by the user that runs Integrity.
14
+ :export_directory: /path/to/scm/exports
15
+
16
+ # Enable or disable HTTP authentication for the app. BE AWARE that if you
17
+ # disable this anyone can delete and alter projects, so do it only if your
18
+ # app is running in a controlled environment (ie, behind your company's
19
+ # firewall.)
20
+ :use_basic_auth: true
21
+
22
+ # When `use_basic_auth` is true, the admin's username for HTTP authentication.
23
+ :admin_username: username
24
+
25
+ # When `use_basic_auth` is true, the admin's password. Usually saved as a
26
+ # SHA1 hash. See the next option.
27
+ :admin_password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
28
+
29
+ # If this is true, then whenever we authenticate the admin user, will hash
30
+ # it using SHA1. If not, we'll assume the provided password is in plain text.
31
+ :hash_admin_password: true
@@ -0,0 +1,14 @@
1
+ ---
2
+ environment: production
3
+ chdir: /apps/integrity
4
+ address: 127.0.0.1
5
+ user: deploy
6
+ group: deploy
7
+ port: 8910
8
+ pid: /apps/integrity/thin.pid
9
+ rackup: /apps/integrity/config.ru
10
+ log: /apps/integrity/log/thin.log
11
+ max_conns: 1024
12
+ timeout: 30
13
+ max_persistent_conns: 512
14
+ daemonize: true
@@ -0,0 +1,66 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'integrity'
3
+ s.version = '0.1.1'
4
+ s.date = '2008-11-14'
5
+ s.summary = 'The easy and fun Continuous Integration server'
6
+ s.description = 'Your Friendly Continuous Integration server. Easy, fun and painless!'
7
+ s.homepage = 'http://integrityapp.com'
8
+ s.rubyforge_project = 'integrity'
9
+ s.email = 'contacto@nicolassanguinetti.info'
10
+ s.authors = ['Nicolás Sanguinetti', 'Simon Rozet']
11
+ s.has_rdoc = false
12
+ s.executables = ['integrity']
13
+ s.post_install_message = 'Run `integrity help` for information on how to setup Integrity.'
14
+ s.files = %w(
15
+ README.markdown
16
+ Rakefile
17
+ app.rb
18
+ bin/integrity
19
+ config/config.sample.ru
20
+ config/config.sample.yml
21
+ config/thin.sample.yml
22
+ integrity.gemspec
23
+ lib/integrity.rb
24
+ lib/integrity/build.rb
25
+ lib/integrity/builder.rb
26
+ lib/integrity/core_ext/object.rb
27
+ lib/integrity/core_ext/string.rb
28
+ lib/integrity/core_ext/time.rb
29
+ lib/integrity/notifier.rb
30
+ lib/integrity/notifier/base.rb
31
+ lib/integrity/project.rb
32
+ lib/integrity/scm.rb
33
+ lib/integrity/scm/git.rb
34
+ lib/integrity/scm/git/uri.rb
35
+ lib/integrity/version.rb
36
+ public/buttons.css
37
+ public/reset.css
38
+ public/spinner.gif
39
+ vendor/sinatra-hacks/lib/hacks.rb
40
+ views/build.haml
41
+ views/build_info.haml
42
+ views/home.haml
43
+ views/integrity.sass
44
+ views/layout.haml
45
+ views/new.haml
46
+ views/not_found.haml
47
+ views/notifier.haml
48
+ views/project.haml
49
+ views/unauthorized.haml
50
+ spec/spec_helper.rb
51
+ spec/form_field_matchers.rb
52
+ )
53
+
54
+ s.add_dependency 'sinatra', ['>= 0.3.2']
55
+ s.add_dependency 'dm-core', ['>= 0.9.5']
56
+ s.add_dependency 'dm-validations', ['>= 0.9.5']
57
+ s.add_dependency 'dm-types', ['>= 0.9.5']
58
+ s.add_dependency 'dm-timestamps', ['>= 0.9.5']
59
+ s.add_dependency 'dm-aggregates', ['>= 0.9.5']
60
+ s.add_dependency 'data_objects', ['>= 0.9.5']
61
+ s.add_dependency 'do_sqlite3', ['>= 0.9.5']
62
+ s.add_dependency 'json'
63
+ s.add_dependency 'foca-sinatra-diddies', ['>= 0.0.2']
64
+ s.add_dependency 'rspec_hpricot_matchers'
65
+ s.add_dependency 'thor'
66
+ end
@@ -0,0 +1,44 @@
1
+ __DIR__ = File.dirname(__FILE__)
2
+ $:.unshift "#{__DIR__}/integrity", *Dir["#{__DIR__}/../vendor/**/lib"].to_a
3
+
4
+ require 'rubygems'
5
+ require 'json'
6
+ require 'dm-core'
7
+ require 'dm-validations'
8
+ require 'dm-types'
9
+ require 'dm-timestamps'
10
+ require 'dm-aggregates'
11
+
12
+ require 'yaml'
13
+ require 'digest/sha1'
14
+
15
+ require "core_ext/object"
16
+ require "core_ext/string"
17
+ require "core_ext/time"
18
+
19
+ %w(project build builder scm scm/git notifier version).each &method(:require)
20
+
21
+ module Integrity
22
+ def self.new
23
+ DataMapper.setup(:default, config[:database_uri])
24
+ end
25
+
26
+ def self.root
27
+ File.expand_path(File.join(File.dirname(__FILE__), ".."))
28
+ end
29
+
30
+ def self.default_configuration
31
+ @defaults ||= { :database_uri => 'sqlite3::memory:',
32
+ :export_directory => root / 'exports',
33
+ :base_uri => 'http://localhost:8910',
34
+ :use_basic_auth => false }
35
+ end
36
+
37
+ def self.config
38
+ @config ||= default_configuration
39
+ end
40
+
41
+ def self.config=(file)
42
+ @config = default_configuration.merge(YAML.load_file(file))
43
+ end
44
+ end
@@ -0,0 +1,56 @@
1
+ require 'ostruct'
2
+
3
+ module Integrity
4
+ class Build
5
+ include DataMapper::Resource
6
+
7
+ property :id, Integer, :serial => true
8
+ property :output, Text, :nullable => false, :default => ''
9
+ property :successful, Boolean, :nullable => false, :default => false
10
+ property :commit_identifier, String, :nullable => false
11
+ property :commit_metadata, Yaml, :nullable => false, :lazy => false
12
+ property :created_at, DateTime
13
+ property :updated_at, DateTime
14
+
15
+ belongs_to :project, :class_name => "Integrity::Project"
16
+
17
+ def failed?
18
+ !successful?
19
+ end
20
+
21
+ def status
22
+ successful? ? :success : :failed
23
+ end
24
+
25
+ def human_readable_status
26
+ successful? ? 'Build Successful' : 'Build Failed'
27
+ end
28
+
29
+ def short_commit_identifier
30
+ sha1?(commit_identifier) ? commit_identifier[0..6] : commit_identifier
31
+ end
32
+
33
+ def commit_author
34
+ @author ||= begin
35
+ commit_metadata[:author] =~ /^(.*) <(.*)>$/
36
+ OpenStruct.new(:name => $1.strip, :email => $2.strip, :full => commit_metadata[:author])
37
+ end
38
+ end
39
+
40
+ def commit_message
41
+ commit_metadata[:message]
42
+ end
43
+
44
+ def commited_at
45
+ case commit_metadata[:date]
46
+ when String then Time.parse(commit_metadata[:date])
47
+ else commit_metadata[:date]
48
+ end
49
+ end
50
+
51
+ private
52
+ def sha1?(string)
53
+ string =~ /^[a-f0-9]{40}$/
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ require 'fileutils'
2
+
3
+ module Integrity
4
+ class Builder
5
+ attr_reader :build_script
6
+
7
+ def initialize(project)
8
+ @uri = project.uri
9
+ @build_script = project.command
10
+ @branch = project.branch
11
+ @scm = SCM.new(@uri, @branch, export_directory)
12
+ @build = Build.new(:project => project)
13
+ end
14
+
15
+ def build(commit)
16
+ @scm.with_revision(commit) { run_build_script }
17
+ @build
18
+ ensure
19
+ @build.commit_identifier = @scm.commit_identifier(commit)
20
+ @build.commit_metadata = @scm.commit_metadata(commit)
21
+ @build.save
22
+ end
23
+
24
+ def delete_code
25
+ FileUtils.rm_r export_directory
26
+ rescue Errno::ENOENT
27
+ nil
28
+ end
29
+
30
+ private
31
+ def export_directory
32
+ Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
33
+ end
34
+
35
+ def run_build_script
36
+ IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe|
37
+ @build.output = pipe.read
38
+ end
39
+ @build.successful = $?.success?
40
+ end
41
+ end
42
+ 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,13 @@
1
+ class Time
2
+ alias :strftime_without_ordinals :strftime
3
+ def strftime(format_string)
4
+ format_string.gsub! "%o", case day
5
+ when 1, 21, 31 then "st"
6
+ when 2, 22 then "nd"
7
+ when 3, 23 then "rd"
8
+ else "th"
9
+ end
10
+
11
+ strftime_without_ordinals(format_string)
12
+ end
13
+ end
@@ -0,0 +1,49 @@
1
+ module Integrity
2
+ class Notifier
3
+ include DataMapper::Resource
4
+
5
+ property :id, Integer, :serial => true
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 do |notifier|
16
+ notifier.respond_to?(:to_haml) && notifier.respond_to?(:notify_of_build)
17
+ end - [Notifier::Base]
18
+ end
19
+
20
+ def self.enable_notifiers(project, enabled, config={})
21
+ Project.get(project).notifiers.destroy!
22
+ list_of_enabled_notifiers(enabled).each do |name|
23
+ create! :project_id => project, :name => name, :config => config[name]
24
+ end
25
+ end
26
+
27
+ def notify_of_build(build)
28
+ to_const.notify_of_build(build, config)
29
+ end
30
+
31
+ private
32
+
33
+ def to_const
34
+ self.class.module_eval(name)
35
+ end
36
+
37
+ def self.list_of_enabled_notifiers(names)
38
+ case names
39
+ when Array then names
40
+ when NilClass then []
41
+ else [names]
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ require File.dirname(__FILE__) / 'notifier' / 'base'
48
+
49
+ Dir["#{File.dirname(__FILE__)}/notifier/*.rb"].each &method(:require)