giraffesoft-integrity 0.1.4

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/bin/integrity ADDED
@@ -0,0 +1,82 @@
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 [PATH]",
11
+ "Copy template files to PATH. Next, go there and edit them."
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_db [CONFIG]",
23
+ "Checks the `database_uri` in CONFIG and creates and bootstraps a database for integrity"
24
+ def create_db(config)
25
+ Integrity.new(config)
26
+ DataMapper.auto_migrate!
27
+ end
28
+
29
+ private
30
+ attr_reader :root
31
+
32
+ def create_dir_structure
33
+ mkdir_p root
34
+ mkdir_p root / "builds"
35
+ mkdir_p root / "log"
36
+ end
37
+
38
+ def copy_template_files
39
+ cp Integrity.root / "config" / "config.sample.ru", root / "config.ru"
40
+ cp Integrity.root / "config" / "config.sample.yml", root / "config.yml"
41
+ cp Integrity.root / "config" / "thin.sample.yml", root / "thin.yml"
42
+ end
43
+
44
+ def edit_template_files
45
+ edit_integrity_configuration
46
+ edit_thin_configuration
47
+ end
48
+
49
+ def edit_integrity_configuration
50
+ config = File.read(root / "config.yml")
51
+ config.gsub! %r(sqlite3:///var/integrity.db), "sqlite3://#{root}/integrity.db"
52
+ config.gsub! %r(/path/to/scm/exports), "#{root}/builds"
53
+ config.gsub! %r(/var/log), "#{root}/log"
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
+ puts
78
+ puts %Q(Don't forget to tweak #{root / "config.yml"} to your needs.)
79
+ end
80
+ end
81
+
82
+ 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,34 @@
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
+ # Path to the integrity log file
17
+ :log: /var/log/integrity.log
18
+
19
+ # Enable or disable HTTP authentication for the app. BE AWARE that if you
20
+ # disable this anyone can delete and alter projects, so do it only if your
21
+ # app is running in a controlled environment (ie, behind your company's
22
+ # firewall.)
23
+ :use_basic_auth: false
24
+
25
+ # When `use_basic_auth` is true, the admin's username for HTTP authentication.
26
+ :admin_username: username
27
+
28
+ # When `use_basic_auth` is true, the admin's password. Usually saved as a
29
+ # SHA1 hash. See the next option.
30
+ :admin_password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
31
+
32
+ # If this is true, then whenever we authenticate the admin user, will hash
33
+ # it using SHA1. If not, we'll assume the provided password is in plain text.
34
+ :hash_admin_password: true
@@ -0,0 +1,13 @@
1
+ ---
2
+ environment: production
3
+ chdir: /apps/integrity
4
+ address: 127.0.0.1
5
+ port: 8910
6
+ pid: /apps/integrity/thin.pid
7
+ rackup: /apps/integrity/config.ru
8
+ log: /apps/integrity/log/thin.log
9
+ max_conns: 1024
10
+ timeout: 30
11
+ max_persistent_conns: 512
12
+ daemonize: true
13
+ servers: 2
data/integrity.gemspec ADDED
@@ -0,0 +1,70 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{integrity}
5
+ s.version = "0.1.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Nicol\303\241s Sanguinetti", "Simon Rozet"]
9
+ s.date = %q{2008-11-22}
10
+ s.default_executable = %q{integrity}
11
+ s.description = %q{Your Friendly Continuous Integration server. Easy, fun and painless!}
12
+ s.email = %q{contacto@nicolassanguinetti.info}
13
+ s.executables = ["integrity"]
14
+ s.files = ["README.markdown", "Rakefile", "VERSION.yml", "app.rb", "bin/integrity", "config/config.sample.ru", "config/config.sample.yml", "config/thin.sample.yml", "integrity.gemspec", "lib/integrity.rb", "lib/integrity/build.rb", "lib/integrity/builder.rb", "lib/integrity/core_ext/object.rb", "lib/integrity/core_ext/string.rb", "lib/integrity/core_ext/time.rb", "lib/integrity/notifier.rb", "lib/integrity/notifier/base.rb", "lib/integrity/project.rb", "lib/integrity/scm.rb", "lib/integrity/scm/git.rb", "lib/integrity/scm/git/uri.rb", "public/buttons.css", "public/reset.css", "public/spinner.gif", "vendor/sinatra-hacks/lib/hacks.rb", "views/build.haml", "views/build_info.haml", "views/error.haml", "views/home.haml", "views/integrity.sass", "views/layout.haml", "views/new.haml", "views/not_found.haml", "views/notifier.haml", "views/project.haml", "views/unauthorized.haml", "spec/spec_helper.rb", "spec/form_field_matchers.rb"]
15
+ s.homepage = %q{http://integrityapp.com}
16
+ s.post_install_message = %q{Run `integrity help` for information on how to setup Integrity.}
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{integrity}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{The easy and fun Continuous Integration server}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<sinatra>, [">= 0.3.2"])
28
+ s.add_runtime_dependency(%q<haml>, [">= 0"])
29
+ s.add_runtime_dependency(%q<dm-core>, [">= 0.9.5"])
30
+ s.add_runtime_dependency(%q<dm-validations>, [">= 0.9.5"])
31
+ s.add_runtime_dependency(%q<dm-types>, [">= 0.9.5"])
32
+ s.add_runtime_dependency(%q<dm-timestamps>, [">= 0.9.5"])
33
+ s.add_runtime_dependency(%q<dm-aggregates>, [">= 0.9.5"])
34
+ s.add_runtime_dependency(%q<data_objects>, [">= 0.9.5"])
35
+ s.add_runtime_dependency(%q<do_sqlite3>, [">= 0.9.5"])
36
+ s.add_runtime_dependency(%q<json>, [">= 0"])
37
+ s.add_runtime_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
38
+ s.add_runtime_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
39
+ s.add_runtime_dependency(%q<thor>, [">= 0"])
40
+ else
41
+ s.add_dependency(%q<sinatra>, [">= 0.3.2"])
42
+ s.add_dependency(%q<haml>, [">= 0"])
43
+ s.add_dependency(%q<dm-core>, [">= 0.9.5"])
44
+ s.add_dependency(%q<dm-validations>, [">= 0.9.5"])
45
+ s.add_dependency(%q<dm-types>, [">= 0.9.5"])
46
+ s.add_dependency(%q<dm-timestamps>, [">= 0.9.5"])
47
+ s.add_dependency(%q<dm-aggregates>, [">= 0.9.5"])
48
+ s.add_dependency(%q<data_objects>, [">= 0.9.5"])
49
+ s.add_dependency(%q<do_sqlite3>, [">= 0.9.5"])
50
+ s.add_dependency(%q<json>, [">= 0"])
51
+ s.add_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
52
+ s.add_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
53
+ s.add_dependency(%q<thor>, [">= 0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<sinatra>, [">= 0.3.2"])
57
+ s.add_dependency(%q<haml>, [">= 0"])
58
+ s.add_dependency(%q<dm-core>, [">= 0.9.5"])
59
+ s.add_dependency(%q<dm-validations>, [">= 0.9.5"])
60
+ s.add_dependency(%q<dm-types>, [">= 0.9.5"])
61
+ s.add_dependency(%q<dm-timestamps>, [">= 0.9.5"])
62
+ s.add_dependency(%q<dm-aggregates>, [">= 0.9.5"])
63
+ s.add_dependency(%q<data_objects>, [">= 0.9.5"])
64
+ s.add_dependency(%q<do_sqlite3>, [">= 0.9.5"])
65
+ s.add_dependency(%q<json>, [">= 0"])
66
+ s.add_dependency(%q<foca-sinatra-diddies>, [">= 0.0.2"])
67
+ s.add_dependency(%q<rspec_hpricot_matchers>, [">= 0"])
68
+ s.add_dependency(%q<thor>, [">= 0"])
69
+ end
70
+ end
data/lib/integrity.rb ADDED
@@ -0,0 +1,71 @@
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 'logger'
14
+ require 'digest/sha1'
15
+
16
+ require 'core_ext/object'
17
+ require 'core_ext/string'
18
+ require 'core_ext/time'
19
+
20
+ require 'project'
21
+ require 'build'
22
+ require 'builder'
23
+ require 'scm'
24
+ require 'scm/git'
25
+ require 'notifier'
26
+
27
+ module Integrity
28
+ def self.new(config_file = nil)
29
+ self.config = config_file unless config_file.nil?
30
+ DataMapper.setup(:default, config[:database_uri])
31
+ end
32
+
33
+ def self.root
34
+ File.expand_path(File.join(File.dirname(__FILE__), ".."))
35
+ end
36
+
37
+ def self.default_configuration
38
+ @defaults ||= { :database_uri => 'sqlite3::memory:',
39
+ :export_directory => root / 'exports',
40
+ :log => STDOUT,
41
+ :base_uri => 'http://localhost:8910',
42
+ :use_basic_auth => false,
43
+ :build_all_commits => true}
44
+ end
45
+
46
+ def self.config
47
+ @config ||= default_configuration
48
+ end
49
+
50
+ def self.config=(file)
51
+ @config = default_configuration.merge(YAML.load_file(file))
52
+ end
53
+
54
+ def self.log(message, &block)
55
+ logger.info(message, &block)
56
+ end
57
+
58
+ def self.logger
59
+ @logger ||= Logger.new(config[:log], "daily").tap do |logger|
60
+ logger.formatter = LogFormatter.new
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ class LogFormatter < Logger::Formatter
67
+ def call(severity, time, progname, msg)
68
+ time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
69
+ end
70
+ end
71
+ 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,49 @@
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
+ Integrity.log "Building #{commit} (#{@branch}) of #{@build.project.name} in #{export_directory} using #{scm_name}"
17
+ @scm.with_revision(commit) { run_build_script }
18
+ @build
19
+ ensure
20
+ @build.commit_identifier = @scm.commit_identifier(commit)
21
+ @build.commit_metadata = @scm.commit_metadata(commit)
22
+ @build.save
23
+ end
24
+
25
+ def delete_code
26
+ FileUtils.rm_r export_directory
27
+ rescue Errno::ENOENT
28
+ nil
29
+ end
30
+
31
+ private
32
+ def export_directory
33
+ Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
34
+ end
35
+
36
+ def scm_name
37
+ @scm.name
38
+ end
39
+
40
+ def run_build_script
41
+ Integrity.log "Running `#{build_script}` in #{@scm.working_directory}"
42
+
43
+ IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe|
44
+ @build.output = pipe.read
45
+ end
46
+ @build.successful = $?.success?
47
+ end
48
+ end
49
+ 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