october 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.
- 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
|