defunkt-integrity 0.1.1

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.
@@ -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)