october 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +35 -0
- data/README.md +14 -0
- data/Rakefile +38 -0
- data/bin/october +8 -0
- data/boot.rb +5 -0
- data/config/database.yml.example +3 -0
- data/config/irc.yml.example +9 -0
- data/config/plugins.yml.example +2 -0
- data/config/redis.yml.example +7 -0
- data/lib/october.rb +16 -0
- data/lib/october/base.rb +28 -0
- data/lib/october/config.rb +23 -0
- data/lib/october/debugger.rb +51 -0
- data/lib/october/environment.rb +24 -0
- data/lib/october/plugin.rb +26 -0
- data/lib/october/plugin/help.rb +20 -0
- data/lib/october/plugins.rb +94 -0
- data/lib/october/redis.rb +20 -0
- data/lib/october/version.rb +3 -0
- data/lib/october/watchdog.rb +36 -0
- data/october.gemspec +25 -0
- data/plugins/.gitkeep +0 -0
- data/plugins/autossh.rb +23 -0
- data/plugins/fortune.rb +13 -0
- data/plugins/help.rb +11 -0
- data/plugins/hudson.rb +73 -0
- data/plugins/hudson/config.rb +41 -0
- data/plugins/hudson/fetcher.rb +40 -0
- data/plugins/hudson/reporter.rb +104 -0
- data/plugins/hudson/test_run.rb +88 -0
- data/plugins/issues.rb +161 -0
- data/plugins/joke.rb +32 -0
- data/plugins/links.rb +42 -0
- data/plugins/links/link.rb +68 -0
- data/plugins/service.rb +11 -0
- data/plugins/update.rb +79 -0
- data/plugins/whisper.rb +70 -0
- data/spec/fixtures/hudson/console.log +37 -0
- data/spec/helpers/bot_context.rb +5 -0
- data/spec/october/environment_spec.rb +28 -0
- data/spec/plugins/hudson/reporter_spec.rb +27 -0
- data/spec/plugins/hudson/test_run_spec.rb +17 -0
- data/spec/plugins/hudson_spec.rb +44 -0
- data/spec/plugins/issues_spec.rb +26 -0
- data/spec/plugins/links_spec.rb +16 -0
- data/spec/plugins/whisper_spec.rb +6 -0
- data/spec/spec_helper.rb +13 -0
- metadata +185 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
module October
|
2
|
+
class Watchdog
|
3
|
+
attr_reader :child, :root, :env
|
4
|
+
|
5
|
+
def initialize(root, env = nil)
|
6
|
+
@root = root
|
7
|
+
@env = env
|
8
|
+
end
|
9
|
+
|
10
|
+
def spawn!
|
11
|
+
@child = fork do
|
12
|
+
ENV['OCTOBER_ENV'] ||= env
|
13
|
+
|
14
|
+
require File.join(root, "boot")
|
15
|
+
require 'october'
|
16
|
+
|
17
|
+
October::Base.start
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def loop!
|
22
|
+
loop do
|
23
|
+
begin
|
24
|
+
Process.wait(spawn!)
|
25
|
+
warn "process #{child} ended"
|
26
|
+
rescue Errno::ECHILD
|
27
|
+
warn "process #{child} died or was killed"
|
28
|
+
end
|
29
|
+
|
30
|
+
warn "spawning new one in next iteration"
|
31
|
+
|
32
|
+
sleep(1) # throttle spawning in case anything fails
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/october.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path('../lib/october/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ["Michal Cichra"]
|
5
|
+
gem.email = ["mikz@o2h.cz"]
|
6
|
+
gem.description = %q{IRC Bot}
|
7
|
+
gem.summary = gem.description
|
8
|
+
gem.homepage = "https://github.com/mikz/october"
|
9
|
+
|
10
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
11
|
+
gem.files = `git ls-files`.split("\n") - %w{.gitignore .rvmrc .travis.yml Guardfile Gemfile.lock}
|
12
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
gem.name = "october"
|
14
|
+
gem.require_paths = ["lib", "plugins"]
|
15
|
+
gem.version = October::VERSION
|
16
|
+
|
17
|
+
gem.add_dependency 'activesupport'
|
18
|
+
gem.add_dependency 'i18n'
|
19
|
+
|
20
|
+
gem.add_dependency 'cinch'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'rspec'
|
23
|
+
gem.add_development_dependency 'fakefs'
|
24
|
+
end
|
25
|
+
|
data/plugins/.gitkeep
ADDED
File without changes
|
data/plugins/autossh.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class Autossh
|
2
|
+
include October::Plugin
|
3
|
+
|
4
|
+
match /tunnels?$/, method: :reconnect
|
5
|
+
|
6
|
+
register_help 'tunnel[s]', 'Reconnects autossh tunnels'
|
7
|
+
|
8
|
+
def reconnect(m)
|
9
|
+
pids = IO.popen('ps ux | grep "auto[s]sh"') do |io|
|
10
|
+
io.readlines.map { |line|
|
11
|
+
line.split(/\s+/)[1].to_i
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
m.reply "Found #{pids.count} autossh processes"
|
16
|
+
|
17
|
+
unless pids.empty?
|
18
|
+
m.reply "Sending SIGUSR1 to pid: #{pids.join(", ")}"
|
19
|
+
Process.kill('USR1', *pids)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/plugins/fortune.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
gem 'typhoeus'
|
2
|
+
|
3
|
+
class Fortune
|
4
|
+
include October::Plugin
|
5
|
+
|
6
|
+
match /fortune?$/, method: :fortune
|
7
|
+
|
8
|
+
register_help 'fortune', 'fortune YO!'
|
9
|
+
def fortune(m)
|
10
|
+
response = Typhoeus::Request.get "http://www.fortunefortoday.com/getfortuneonly.php"
|
11
|
+
m.reply response.body.strip
|
12
|
+
end
|
13
|
+
end
|
data/plugins/help.rb
ADDED
data/plugins/hudson.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
|
3
|
+
class Hudson
|
4
|
+
include October::Plugin
|
5
|
+
|
6
|
+
autoload :Fetcher, 'hudson/fetcher'
|
7
|
+
autoload :Reporter, 'hudson/reporter'
|
8
|
+
autoload :TestRun, 'hudson/test_run'
|
9
|
+
autoload :Config, 'hudson/config'
|
10
|
+
|
11
|
+
HYDRA = Typhoeus::Hydra.new
|
12
|
+
|
13
|
+
FAILED = /(?:failures|failed|f)/
|
14
|
+
NUMBER = /(?:\/(\d+))?/
|
15
|
+
JOB = /([\w\-\.]+?)/
|
16
|
+
BUILD = /#{JOB}#{NUMBER}/
|
17
|
+
|
18
|
+
match /#{FAILED}(?:\s+#{BUILD})?$/, method: :failures
|
19
|
+
match /(?:failures|failed|f) #{BUILD} diff #{BUILD}$/, method: :diff
|
20
|
+
match /Project (.+?) build #(\d+): (?:SUCCESS|FIXED) (?:.+?): (.*)$/, method: :green, :use_prefix => false
|
21
|
+
match /(?:job|j) (\S*)(?: ?)(.*?)$/, method: :update_branch
|
22
|
+
match /(?:build|b)(?: ?)(.*?)$/, method: :build
|
23
|
+
|
24
|
+
register_help 'failures|failed|f project', 'list failed tests (cukes and test units) from last test run'
|
25
|
+
register_help 'failures|failed|f project/test_number', 'list failed tests from specific test run'
|
26
|
+
register_help 'failures|failed|f project/test_number diff another/test', 'list only difference between these two tests'
|
27
|
+
register_help 'build [job_name]', 'builds your personal project or the given job'
|
28
|
+
register_help 'job branch_name [job_name]', 'updates your personal project or the specified job to build the given branch'
|
29
|
+
|
30
|
+
def failures(m, job = nil, test_run = nil)
|
31
|
+
job ||= m.user
|
32
|
+
test = TestRun.new job, test_run
|
33
|
+
reporter = Reporter.new test
|
34
|
+
|
35
|
+
reporter.respond :report, m
|
36
|
+
end
|
37
|
+
|
38
|
+
def diff(m, *projects)
|
39
|
+
tests = projects.in_groups_of(2).map {|project, number|
|
40
|
+
TestRun.new project, number
|
41
|
+
}
|
42
|
+
reporter = Reporter.new *tests
|
43
|
+
|
44
|
+
reporter.respond :diff, m
|
45
|
+
end
|
46
|
+
|
47
|
+
def green(m, project_name, build, url)
|
48
|
+
if October::Plugins.registered["Hudson"]
|
49
|
+
tr = TestRun.new(project_name, build)
|
50
|
+
issues = @bot.plugins.detect{|a| a.is_a? Issues}
|
51
|
+
pull = issues.pull_request(m, tr.branch)
|
52
|
+
issues.comment(m, pull["number"], "Green: #{url}") if pull
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_branch(m, new_branch, job_name)
|
57
|
+
job_name = m.user.to_s if job_name.blank?
|
58
|
+
config = Config.new(job_name)
|
59
|
+
config.update_branch(new_branch)
|
60
|
+
m.reply "Job '#{job_name}' set to build branch '#{new_branch}'"
|
61
|
+
rescue
|
62
|
+
m.reply "Error updating job"
|
63
|
+
end
|
64
|
+
|
65
|
+
def build(m, job_name)
|
66
|
+
job_name = m.user.to_s if job_name.blank?
|
67
|
+
config = Config.new(job_name)
|
68
|
+
config.build
|
69
|
+
m.reply "Build of '#{job_name}' scheduled"
|
70
|
+
rescue
|
71
|
+
m.reply "Failed to schedule a build for '#{job_name}'"
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
class Hudson
|
4
|
+
class Config
|
5
|
+
BASE_URL = "http://localhost:8080"
|
6
|
+
BUILD_URL = "/job/<project>/build"
|
7
|
+
CONFIG_URL = "/job/<project>/config.xml"
|
8
|
+
|
9
|
+
attr_reader :job
|
10
|
+
|
11
|
+
def initialize(job)
|
12
|
+
@job = job
|
13
|
+
end
|
14
|
+
|
15
|
+
def config_url
|
16
|
+
(BASE_URL + CONFIG_URL).gsub('<project>', job)
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_url
|
20
|
+
(BASE_URL + BUILD_URL).gsub('<project>', job)
|
21
|
+
end
|
22
|
+
|
23
|
+
def config
|
24
|
+
@config ||= Nokogiri::XML(open(config_url))
|
25
|
+
end
|
26
|
+
|
27
|
+
def build
|
28
|
+
open(build_url)
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_branch(branch_name)
|
32
|
+
config.xpath('/project/scm/branches/hudson.plugins.git.BranchSpec/name').each do |branch|
|
33
|
+
branch.content = branch_name
|
34
|
+
end
|
35
|
+
|
36
|
+
Curl::Easy.http_post(config_url, config.to_s) do |curl|
|
37
|
+
curl.headers['Content-Type'] = 'text/xml'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Hudson
|
2
|
+
class Fetcher < Typhoeus::Request
|
3
|
+
BASE_URL = "http://localhost:8080"
|
4
|
+
JOB_URL = "/job/<project>/<test_run>/consoleText"
|
5
|
+
|
6
|
+
attr_reader :test_run
|
7
|
+
|
8
|
+
def initialize test_run, options = {}
|
9
|
+
@test_run = test_run
|
10
|
+
|
11
|
+
url = (BASE_URL + JOB_URL).
|
12
|
+
gsub('<project>', test_run.project.to_s).
|
13
|
+
gsub('<test_run>', test_run.number.to_s)
|
14
|
+
|
15
|
+
super(url, options)
|
16
|
+
|
17
|
+
self.authorize = {
|
18
|
+
:username => ENV['HUDSON_USER'].presence,
|
19
|
+
:password => ENV['HUDSON_PASS'].presence
|
20
|
+
}
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def authorize= options
|
25
|
+
@username = options[:username]
|
26
|
+
@password = options[:password]
|
27
|
+
end
|
28
|
+
|
29
|
+
def response
|
30
|
+
@response or run
|
31
|
+
end
|
32
|
+
|
33
|
+
def run
|
34
|
+
HYDRA.queue self
|
35
|
+
HYDRA.run
|
36
|
+
self.response
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
class Hudson
|
2
|
+
class Reporter
|
3
|
+
|
4
|
+
class Diff
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(base, other)
|
8
|
+
@base, @other = base, other
|
9
|
+
end
|
10
|
+
|
11
|
+
def header
|
12
|
+
"--- #{@base.project}/#{@base.number} <=> #{@other.project}/#{@other.number} ---"
|
13
|
+
end
|
14
|
+
|
15
|
+
def each(&block)
|
16
|
+
failures.each(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
[header, *surplus, *common, *missing].join("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
delegate :length, :to => :all
|
24
|
+
|
25
|
+
private
|
26
|
+
def surplus
|
27
|
+
prefix('+', @other.failures - @base.failures)
|
28
|
+
end
|
29
|
+
|
30
|
+
def missing
|
31
|
+
prefix('-', @base.failures - @other.failures)
|
32
|
+
end
|
33
|
+
|
34
|
+
def all
|
35
|
+
@failures ||= @base.failures | @other.failures
|
36
|
+
end
|
37
|
+
|
38
|
+
def common
|
39
|
+
prefix(' ', @base.failures & @other.failures)
|
40
|
+
end
|
41
|
+
|
42
|
+
def prefix(str, array)
|
43
|
+
array.map{ |line| str + " " << line }
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
class Report < Array
|
49
|
+
def initialize test
|
50
|
+
@project, @number = test.project, test.number
|
51
|
+
super(test.failures.flatten)
|
52
|
+
end
|
53
|
+
|
54
|
+
def header
|
55
|
+
"------- #{@project}/#{@number} -------"
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
[header, *self].join("\n")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :tests
|
64
|
+
|
65
|
+
def initialize *tests
|
66
|
+
@tests = tests.flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def report
|
70
|
+
Report.new(@tests.first)
|
71
|
+
end
|
72
|
+
|
73
|
+
def diff
|
74
|
+
Diff.new(@tests.first, @tests.last)
|
75
|
+
end
|
76
|
+
|
77
|
+
def respond format, message
|
78
|
+
|
79
|
+
if responses.all? &:success?
|
80
|
+
return message.user.msg self.send(format)
|
81
|
+
end
|
82
|
+
|
83
|
+
responses(&:timed_out?).each do |r|
|
84
|
+
message.reply 'Some requests timed out :('
|
85
|
+
end
|
86
|
+
|
87
|
+
responses{|r| r.code == 0 }.each do |r|
|
88
|
+
message.reply r.curl_error_message
|
89
|
+
end
|
90
|
+
|
91
|
+
responses{|r| r.code != 200 }.each do |r|
|
92
|
+
message.reply "HTTP request failed: #{r.code}"
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def responses(&block)
|
100
|
+
@tests.map(&:response).select &block
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class Hudson
|
2
|
+
class TestRun
|
3
|
+
|
4
|
+
class Cucumber
|
5
|
+
PATTERN = /^cucumber (?:.+?\/)(features\/.+?\.feature:\d+)/
|
6
|
+
|
7
|
+
def self.parse(log)
|
8
|
+
log.scan(PATTERN).flatten.uniq.map{ |name| self.new(name) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(file)
|
12
|
+
@file = file
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@file
|
17
|
+
end
|
18
|
+
alias :to_str :to_s
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
to_s == other.try(:to_s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TestUnit
|
26
|
+
PATTERN = /\d+\) (?:Failure|Error):\n(.+?)(?:\n{2})/m
|
27
|
+
NAME = /(.+)\((.+)\)[\n: ]+/
|
28
|
+
FILE = /(?:(test\/.+.[a-z]+):\d+)+(?::in `.+')?/
|
29
|
+
|
30
|
+
def self.parse(log)
|
31
|
+
blocks = log.scan(PATTERN).flatten
|
32
|
+
blocks.map do |block|
|
33
|
+
name = block.scan(NAME).first.first
|
34
|
+
file = block.scan(FILE).last.try(:join) || '(missing file)'
|
35
|
+
self.new(name, file)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(name, file)
|
40
|
+
@name, @file = name, file
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"#{@file} -n '#{@name}'"
|
45
|
+
end
|
46
|
+
alias :to_str :to_s
|
47
|
+
|
48
|
+
def ==(other)
|
49
|
+
to_s == other.try(:to_s)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
attr_reader :project, :number
|
55
|
+
|
56
|
+
def initialize(project, number = nil)
|
57
|
+
@project = project
|
58
|
+
@number = number.presence || 'lastBuild'
|
59
|
+
|
60
|
+
@fetcher = Fetcher.new self
|
61
|
+
end
|
62
|
+
|
63
|
+
def cucumbers
|
64
|
+
@cucumbers ||= Cucumber.parse(log)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_unit
|
68
|
+
@test_unit ||= TestUnit.parse(log)
|
69
|
+
end
|
70
|
+
|
71
|
+
def branch
|
72
|
+
log.scan(/Commencing build of Revision (?:.+?) \(origin\/(.+?)\)(.*)/).first.first
|
73
|
+
end
|
74
|
+
|
75
|
+
def all
|
76
|
+
cucumbers + test_unit
|
77
|
+
end
|
78
|
+
|
79
|
+
alias :failures :all
|
80
|
+
|
81
|
+
delegate :response, :to => :@fetcher
|
82
|
+
|
83
|
+
def log
|
84
|
+
response.body
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|