heidi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "thin"
4
+ gem "sinatra"
5
+ gem "simple_shell"
6
+
7
+ group :development do
8
+ gem "rspec", "~> 2.8.0"
9
+ gem "rdoc", "~> 3.12"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.8.3"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ daemons (1.1.6)
5
+ diff-lcs (1.1.3)
6
+ eventmachine (0.12.10)
7
+ git (1.2.5)
8
+ jeweler (1.8.3)
9
+ bundler (~> 1.0)
10
+ git (>= 1.2.5)
11
+ rake
12
+ rdoc
13
+ json (1.6.5)
14
+ rack (1.4.1)
15
+ rack-protection (1.2.0)
16
+ rack
17
+ rake (0.9.2.2)
18
+ rcov (0.9.10)
19
+ rdoc (3.12)
20
+ json (~> 1.4)
21
+ rspec (2.8.0)
22
+ rspec-core (~> 2.8.0)
23
+ rspec-expectations (~> 2.8.0)
24
+ rspec-mocks (~> 2.8.0)
25
+ rspec-core (2.8.0)
26
+ rspec-expectations (2.8.0)
27
+ diff-lcs (~> 1.1.2)
28
+ rspec-mocks (2.8.0)
29
+ simple_shell (1.0.0)
30
+ sinatra (1.3.2)
31
+ rack (~> 1.3, >= 1.3.6)
32
+ rack-protection (~> 1.2)
33
+ tilt (~> 1.3, >= 1.3.3)
34
+ thin (1.3.1)
35
+ daemons (>= 1.0.9)
36
+ eventmachine (>= 0.12.6)
37
+ rack (>= 1.0.0)
38
+ tilt (1.3.3)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ bundler (~> 1.0.0)
45
+ jeweler (~> 1.8.3)
46
+ rcov
47
+ rdoc (~> 3.12)
48
+ rspec (~> 2.8.0)
49
+ simple_shell
50
+ sinatra
51
+ thin
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Hartog C. de Mik
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = heidi
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to heidi
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2012 Hartog C. de Mik. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "heidi"
18
+ gem.homepage = "http://github.com/coffeeaddict/heid"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A naive CI system}
21
+ gem.description = %Q{CI-Joe alike CI system called Heidi.}
22
+ gem.email = "hartog@organisedminds.com"
23
+ gem.authors = ["Hartog C. de Mik"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "Heidi #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/heidi ADDED
@@ -0,0 +1,70 @@
1
+ #!/bin/sh
2
+
3
+ command=$1
4
+ for_a=$2
5
+
6
+ check_heidi_root()
7
+ {
8
+ if [ ! -e ./projects ] && [ ! -d ./projects ]
9
+ then
10
+ echo "You're not inside Heidi" >&2
11
+ exit 1
12
+ fi
13
+ }
14
+
15
+ case $command in
16
+ new)
17
+ echo "creating $for_a"
18
+ echo "creating $for_a/projects"
19
+ mkdir -p $for_a/projects
20
+ echo "creating $for_a/bin"
21
+ mkdir -p $for_a/bin
22
+ ;;
23
+
24
+ project)
25
+ check_heidi_root
26
+
27
+ # create a logs dir
28
+ echo "creating projects/$for_a"
29
+ echo "creating projects/$for_a/logs"
30
+ mkdir -p projects/$for_a/logs
31
+
32
+ echo "creating projects/$for_a/hooks/build"
33
+ echo "creating projects/$for_a/hooks/tests"
34
+ mkdir -p projects/$for_a/hooks/build
35
+ mkdir -p projects/$for_a/hooks/tests
36
+
37
+ # make a clone
38
+ cd projects/$for_a
39
+ echo "filling $for_a cache"
40
+ git clone $3 cached
41
+ cd cached
42
+
43
+ echo "setting the name of the project to: $for_a"
44
+ git config heidi.name $for_a
45
+ ;;
46
+
47
+ drop)
48
+ check_heidi_root
49
+
50
+ # remove build and cache dir, expose logs directly
51
+ echo "removing build dir"
52
+ rm -r projects/$for_a/build
53
+ echo "removing cache (preserving project config)"
54
+ cp -pr projects/$for_a/cached/.git/config projects/$for_a
55
+ rm -r projects/$for_a/cached
56
+ echo "exposing builds"
57
+ mv projects/$for_a/logs/* projects/$for_a/
58
+ rm -r projects/$for_a/logs
59
+ ;;
60
+ web)
61
+ check_heidi_root
62
+
63
+ heidi_web
64
+ ;;
65
+ console)
66
+ check_heidi_root
67
+
68
+ heidi_console
69
+ ;;
70
+ esac
data/bin/heidi_console ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ irb -I $HEIDI_LOAD_PATH -r heidi
data/bin/heidi_cron ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require './lib/heidi'
4
+
5
+ heidi = Heidi.new
6
+ heidi.projects.each do |project|
7
+ project.integrate
8
+ end
data/bin/heidi_web ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # hartog/20120131: Heidi, the CI mistress
4
+
5
+ require 'sinatra'
6
+ require 'heidi'
7
+ require 'heidi/web'
8
+
9
+ $heidi = Heidi.new
10
+
11
+ # modular setup with all the freebees of a non modular setup.
12
+ include Heidi::Web::Routes
data/heidi.gemspec ADDED
@@ -0,0 +1,96 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "heidi"
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Hartog C. de Mik"]
12
+ s.date = "2012-02-01"
13
+ s.description = "CI-Joe alike CI system called Heidi."
14
+ s.email = "hartog@organisedminds.com"
15
+ s.executables = ["heidi", "heidi_console", "heidi_cron", "heidi_web"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".rspec",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.rdoc",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/heidi",
30
+ "bin/heidi_console",
31
+ "bin/heidi_cron",
32
+ "bin/heidi_web",
33
+ "heidi.gemspec",
34
+ "lib/heidi.rb",
35
+ "lib/heidi/build.rb",
36
+ "lib/heidi/builder.rb",
37
+ "lib/heidi/git.rb",
38
+ "lib/heidi/hook.rb",
39
+ "lib/heidi/integrator.rb",
40
+ "lib/heidi/project.rb",
41
+ "lib/heidi/tester.rb",
42
+ "lib/heidi/web.rb",
43
+ "lib/heidi/web/routes.rb",
44
+ "lib/heidi/web/routes/home.rb",
45
+ "lib/heidi/web/routes/projects.rb",
46
+ "spec/heidi/build_spec.rb",
47
+ "spec/heidi/builder_spec.rb",
48
+ "spec/heidi/git_spec.rb",
49
+ "spec/heidi/hook_spec.rb",
50
+ "spec/heidi/integrator_spec.rb",
51
+ "spec/heidi/project_spec.rb",
52
+ "spec/heidi/tester_spec.rb",
53
+ "spec/heidi/web_spec.rb",
54
+ "spec/heidi_spec.rb",
55
+ "spec/spec_helper.rb"
56
+ ]
57
+ s.homepage = "http://github.com/coffeeaddict/heid"
58
+ s.licenses = ["MIT"]
59
+ s.require_paths = ["lib"]
60
+ s.rubygems_version = "1.8.10"
61
+ s.summary = "A naive CI system"
62
+
63
+ if s.respond_to? :specification_version then
64
+ s.specification_version = 3
65
+
66
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
67
+ s.add_runtime_dependency(%q<thin>, [">= 0"])
68
+ s.add_runtime_dependency(%q<sinatra>, [">= 0"])
69
+ s.add_runtime_dependency(%q<simple_shell>, [">= 0"])
70
+ s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
71
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
72
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
73
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
74
+ s.add_development_dependency(%q<rcov>, [">= 0"])
75
+ else
76
+ s.add_dependency(%q<thin>, [">= 0"])
77
+ s.add_dependency(%q<sinatra>, [">= 0"])
78
+ s.add_dependency(%q<simple_shell>, [">= 0"])
79
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
80
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
81
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
82
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
83
+ s.add_dependency(%q<rcov>, [">= 0"])
84
+ end
85
+ else
86
+ s.add_dependency(%q<thin>, [">= 0"])
87
+ s.add_dependency(%q<sinatra>, [">= 0"])
88
+ s.add_dependency(%q<simple_shell>, [">= 0"])
89
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
90
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
91
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
92
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
93
+ s.add_dependency(%q<rcov>, [">= 0"])
94
+ end
95
+ end
96
+
@@ -0,0 +1,147 @@
1
+ require 'simple_shell'
2
+ require 'time'
3
+
4
+ class Heidi
5
+ # An integration is called a build.
6
+ # The collections of builds is the log of the project.
7
+ # A single build lives in $project/logs
8
+ # A build is tied to a commit
9
+ #
10
+ class Build
11
+ attr_reader :project, :commit, :root, :shell, :hooks
12
+
13
+ def initialize(project, commit=project.commit)
14
+ @project = project
15
+ @commit = commit
16
+ @root = File.join(project.root, "logs", commit)
17
+
18
+ if !File.exists? @root
19
+ SimpleShell.new(project.root).mkdir %W(-p #{@root})
20
+ end
21
+
22
+ @shell = SimpleShell.new(@root)
23
+ end
24
+
25
+ def load_hooks
26
+ log :info, "Loading hooks"
27
+ @hooks = {
28
+ :build => [],
29
+ :tests => [],
30
+ :before => [],
31
+ :after => [],
32
+ :failed => [],
33
+ }
34
+
35
+ @hooks.keys.each do |key|
36
+ log :debug, "Loading #{key} hooks"
37
+
38
+ Dir[File.join(project.root, "hooks", key.to_s, "*")].each do |hook|
39
+ next if File.directory? hook
40
+ next unless File.executable? hook
41
+
42
+ log :debug, "Loaded hook: #{hook}"
43
+
44
+ @hooks[key] << Heidi::Hook.new(self.project, hook)
45
+ end
46
+ end
47
+
48
+ log :info, "Hooks loaded"
49
+ end
50
+
51
+ def clean
52
+ %w(heidi.info heidi.errors test.log builder.log build).each do |inode|
53
+ shell.rm("-r", "-f", inode) if File.exists? File.join(@root, inode)
54
+ end
55
+ end
56
+
57
+ def build_root
58
+ File.join(@root, "build")
59
+ end
60
+
61
+ def log(type, msg)
62
+ name = case type
63
+ when :error
64
+ "heidi.errors"
65
+ else
66
+ "heidi.#{type}"
67
+ end
68
+
69
+ File.open(
70
+ File.join(@root, name),
71
+ File::CREAT|File::WRONLY|File::APPEND
72
+ ) do |f|
73
+ f.puts "%s\t%s" % [ Time.now.strftime("%c"), msg ]
74
+ end
75
+ end
76
+
77
+ def lock_file
78
+ File.join(@root, ".lock")
79
+ end
80
+
81
+ def lock(&block)
82
+ log(:info, "Locking build")
83
+ File.open(lock_file, File::CREAT|File::TRUNC|File::WRONLY) do |f|
84
+ f.puts Time.now.ctime
85
+ end
86
+
87
+ if block_given?
88
+ yield
89
+ unlock
90
+ end
91
+ end
92
+
93
+ def unlock
94
+ log(:info, "Unlocking build")
95
+ File.unlink lock_file
96
+ end
97
+
98
+ def locked?
99
+ File.exists? lock_file
100
+ end
101
+
102
+ def record(what)
103
+ flags = File::CREAT|File::TRUNC|File::WRONLY
104
+ file = nil
105
+ case what
106
+ when :failure
107
+ project.build_status = "failed"
108
+ file = File.open(File.join(@root, "FAILURE"), flags)
109
+ when :success
110
+ project.build_status = "passed"
111
+ project.record_latest_build
112
+ file = File.open(File.join(@root, "SUCCESS"), flags)
113
+ end
114
+
115
+ unless file.nil?
116
+ file.puts Time.now.ctime
117
+ file.close
118
+ end
119
+ end
120
+
121
+ def failed?
122
+ File.exists?(File.join(@root, "FAILURE"))
123
+ end
124
+
125
+ def success?
126
+ File.exists?(File.join(@root, "SUCCESS"))
127
+ end
128
+
129
+ def status
130
+ self.failed? ?
131
+ "failed" :
132
+ self.success? ?
133
+ "passed" :
134
+ "DNF"
135
+ end
136
+
137
+ def logs(what)
138
+ File.read(File.join(@root, what))
139
+ rescue
140
+ ""
141
+ end
142
+
143
+ def create_tar_ball
144
+ # TODO
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,95 @@
1
+ require 'simple_shell'
2
+ require 'time'
3
+
4
+ class Heidi
5
+ class Builder
6
+ attr_reader :build, :project
7
+
8
+ def initialize(build)
9
+ @build = build
10
+ @project = build.project
11
+ end
12
+
13
+ def build!
14
+ return false if self.setup_build_dir != true
15
+
16
+ if build.hooks[:build].any?
17
+ build_failed = false
18
+ build.hooks[:build].each do |hook|
19
+ next if build_failed == true
20
+
21
+ res = hook.perform(build.build_root)
22
+ if res.S?.to_i != 0
23
+ log("--- Build hook #{hook.name} failed ---")
24
+ log(res.err)
25
+ build_failed = true
26
+ break
27
+
28
+ else
29
+ log(res.out)
30
+ end
31
+ end
32
+
33
+ if build_failed == true
34
+ build.log :error, "Build failed, revert to build.log for details"
35
+ return false
36
+ end
37
+ end
38
+
39
+ return true
40
+ end
41
+
42
+
43
+ def setup_build_dir
44
+ if File.exists? build.build_root
45
+ build.log(:info, "Removing previous build")
46
+ build.shell.do "rm", "-r", build.build_root
47
+ end
48
+
49
+ build.log(:info, "Creating new clone")
50
+ clone = build.shell.git %W(clone #{@project.cached_root} #{File.basename(build.build_root)})
51
+
52
+ if !File.exists?(build.build_root) || clone.S?.to_i != 0
53
+ build.log :error, "git clone failed: #{clone.err}"
54
+ return false
55
+ end
56
+
57
+ if project.integration_branch
58
+ @git = Heidi::Git.new(build.build_root)
59
+
60
+ branch = project.integration_branch
61
+ build.log(:info, "Switching to integration branch: #{branch}")
62
+ res = @git.switch(branch)
63
+ if res.nil?
64
+ build.log(:info, "Creating integration branch from origin/#{branch}")
65
+ @git.checkout(branch, "origin/#{branch}")
66
+ end
67
+
68
+ res = @git.switch(branch)
69
+ if res.S?.to_i != 0
70
+ build.log(:error, "switching to '#{branch}' failed: #{res.err}")
71
+ return false
72
+ end
73
+
74
+ else
75
+ build.log(:info, "Using master as the integration branch")
76
+
77
+ end
78
+
79
+ build.lock
80
+
81
+ return true
82
+ end
83
+
84
+
85
+ def log(string)
86
+ File.open(
87
+ File.join(project.log_root, "builder.log"),
88
+ File::CREAT|File::WRONLY|File::APPEND
89
+ ) do |f|
90
+ f.puts string
91
+ end
92
+ end
93
+
94
+ end
95
+ end