Merge branch 'add-irker-service' of https://github.com/Aorimn/gitlabhq into Aorimn-add-irker-service
Conflicts: app/controllers/projects/services_controller.rb
This commit is contained in:
commit
f00feb14ec
|
@ -14,6 +14,7 @@ v 7.9.0 (unreleased)
|
|||
- Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger)
|
||||
- Fix mass-unassignment of issues (Robert Speicher)
|
||||
- Allow user confirmation to be skipped for new users via API
|
||||
- Add a service to send updates to an Irker gateway (Romain Coltel)
|
||||
|
||||
v 7.8.1
|
||||
- Fix run of custom post receive hooks
|
||||
|
|
|
@ -50,7 +50,8 @@ class Projects::ServicesController < Projects::ApplicationController
|
|||
:room, :recipients, :project_url, :webhook,
|
||||
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
|
||||
:build_key, :server, :teamcity_url, :build_type,
|
||||
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel
|
||||
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
|
||||
:colorize_messages, :channels
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -73,6 +73,7 @@ class Project < ActiveRecord::Base
|
|||
has_one :gitlab_ci_service, dependent: :destroy
|
||||
has_one :campfire_service, dependent: :destroy
|
||||
has_one :emails_on_push_service, dependent: :destroy
|
||||
has_one :irker_service, dependent: :destroy
|
||||
has_one :pivotaltracker_service, dependent: :destroy
|
||||
has_one :hipchat_service, dependent: :destroy
|
||||
has_one :flowdock_service, dependent: :destroy
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: services
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# type :string(255)
|
||||
# title :string(255)
|
||||
# project_id :integer
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# active :boolean default(FALSE), not null
|
||||
# properties :text
|
||||
# template :boolean default(FALSE)
|
||||
|
||||
require 'uri'
|
||||
|
||||
class IrkerService < Service
|
||||
prop_accessor :colorize_messages, :recipients, :channels
|
||||
validates :recipients, presence: true, if: :activated?
|
||||
validate :check_recipients_count, if: :activated?
|
||||
|
||||
before_validation :get_channels
|
||||
after_initialize :initialize_settings
|
||||
|
||||
# Writer for RSpec tests
|
||||
attr_writer :settings
|
||||
|
||||
def initialize_settings
|
||||
# See the documentation (doc/project_services/irker.md) for possible values
|
||||
# here
|
||||
@settings ||= {
|
||||
server_ip: 'localhost',
|
||||
server_port: 6659,
|
||||
max_channels: 3,
|
||||
default_irc_uri: nil
|
||||
}
|
||||
end
|
||||
|
||||
def title
|
||||
'Irker (IRC gateway)'
|
||||
end
|
||||
|
||||
def description
|
||||
'Send IRC messages, on update, to a list of recipients through an Irker '\
|
||||
'gateway.'
|
||||
end
|
||||
|
||||
def help
|
||||
msg = 'Recipients have to be specified with a full URI: '\
|
||||
'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\
|
||||
'the channel to be a nickname instead, append ",isnick" to the channel '\
|
||||
'name; if the channel is protected by a secret password, append '\
|
||||
'"?key=secretpassword" to the URI.'
|
||||
|
||||
unless @settings[:default_irc].nil?
|
||||
msg += ' Note that a default IRC URI is provided by this service\'s '\
|
||||
"administrator: #{default_irc}. You can thus just give a channel name."
|
||||
end
|
||||
msg
|
||||
end
|
||||
|
||||
def to_param
|
||||
'irker'
|
||||
end
|
||||
|
||||
def execute(push_data)
|
||||
IrkerWorker.perform_async(project_id, channels,
|
||||
colorize_messages, push_data, @settings)
|
||||
end
|
||||
|
||||
def fields
|
||||
[
|
||||
{ type: 'textarea', name: 'recipients',
|
||||
placeholder: 'Recipients/channels separated by whitespaces' },
|
||||
{ type: 'checkbox', name: 'colorize_messages' },
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_recipients_count
|
||||
return true if recipients.nil? || recipients.empty?
|
||||
|
||||
if recipients.split(/\s+/).count > max_chans
|
||||
errors.add(:recipients, "are limited to #{max_chans}")
|
||||
end
|
||||
end
|
||||
|
||||
def max_chans
|
||||
@settings[:max_channels]
|
||||
end
|
||||
|
||||
def get_channels
|
||||
return true unless :activated?
|
||||
return true if recipients.nil? || recipients.empty?
|
||||
|
||||
map_recipients
|
||||
|
||||
errors.add(:recipients, 'are all invalid') if channels.empty?
|
||||
true
|
||||
end
|
||||
|
||||
def map_recipients
|
||||
self.channels = recipients.split(/\s+/).map do |recipient|
|
||||
format_channel default_irc_uri, recipient
|
||||
end
|
||||
channels.reject! &:nil?
|
||||
end
|
||||
|
||||
def default_irc_uri
|
||||
default_irc = @settings[:default_irc_uri]
|
||||
if !(default_irc.nil? || default_irc[-1] == '/')
|
||||
default_irc += '/'
|
||||
end
|
||||
default_irc
|
||||
end
|
||||
|
||||
def format_channel(default_irc, recipient)
|
||||
cnt = 0
|
||||
url = nil
|
||||
|
||||
# Try to parse the chan as a full URI
|
||||
begin
|
||||
uri = URI.parse(recipient)
|
||||
raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0
|
||||
rescue URI::InvalidURIError
|
||||
unless default_irc.nil?
|
||||
cnt += 1
|
||||
recipient = "#{default_irc}#{recipient}"
|
||||
retry if cnt == 1
|
||||
end
|
||||
else
|
||||
url = consider_uri uri
|
||||
end
|
||||
url
|
||||
end
|
||||
|
||||
def consider_uri(uri)
|
||||
# Authorize both irc://domain.com/#chan and irc://domain.com/chan
|
||||
if uri.is_a?(URI) && uri.scheme[/^ircs?$/] && !uri.path.nil?
|
||||
# Do not authorize irc://domain.com/
|
||||
if uri.fragment.nil? && uri.path.length > 1
|
||||
uri.to_s
|
||||
else
|
||||
# Authorize irc://domain.com/smthg#chan
|
||||
# The irker daemon will deal with it by concatenating smthg and
|
||||
# chan, thus sending messages on #smthgchan
|
||||
uri.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -100,7 +100,8 @@ class Service < ActiveRecord::Base
|
|||
|
||||
def self.available_services_names
|
||||
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla asana
|
||||
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker)
|
||||
emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira
|
||||
redmine custom_issue_tracker irker)
|
||||
end
|
||||
|
||||
def self.create_from_template(project_id, template)
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
require 'json'
|
||||
require 'socket'
|
||||
|
||||
class IrkerWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
def perform(project_id, chans, colors, push_data, settings)
|
||||
project = Project.find(project_id)
|
||||
|
||||
# Get config parameters
|
||||
return false unless init_perform settings, chans, colors
|
||||
|
||||
repo_name = push_data['repository']['name']
|
||||
committer = push_data['user_name']
|
||||
branch = push_data['ref'].gsub(%r'refs/[^/]*/', '')
|
||||
|
||||
if @colors
|
||||
repo_name = "\x0304#{repo_name}\x0f"
|
||||
branch = "\x0305#{branch}\x0f"
|
||||
end
|
||||
|
||||
# Firsts messages are for branch creation/deletion
|
||||
send_branch_updates push_data, project, repo_name, committer, branch
|
||||
|
||||
# Next messages are for commits
|
||||
send_commits push_data, project, repo_name, committer, branch
|
||||
|
||||
close_connection
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_perform(set, chans, colors)
|
||||
@colors = colors
|
||||
@channels = chans
|
||||
start_connection set['server_ip'], set['server_port']
|
||||
end
|
||||
|
||||
def start_connection(irker_server, irker_port)
|
||||
begin
|
||||
@socket = TCPSocket.new irker_server, irker_port
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
logger.fatal "Can't connect to Irker daemon: #{e}"
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def sendtoirker(privmsg)
|
||||
to_send = { to: @channels, privmsg: privmsg }
|
||||
@socket.puts JSON.dump(to_send)
|
||||
end
|
||||
|
||||
def close_connection
|
||||
@socket.close
|
||||
end
|
||||
|
||||
def send_branch_updates(push_data, project, repo_name, committer, branch)
|
||||
if push_data['before'] =~ /^000000/
|
||||
send_new_branch project, repo_name, committer, branch
|
||||
elsif push_data['after'] =~ /^000000/
|
||||
send_del_branch repo_name, committer, branch
|
||||
end
|
||||
end
|
||||
|
||||
def send_new_branch(project, repo_name, committer, branch)
|
||||
repo_path = project.path_with_namespace
|
||||
newbranch = "#{Gitlab.config.gitlab.url}/#{repo_path}/branches"
|
||||
newbranch = "\x0302\x1f#{newbranch}\x0f" if @colors
|
||||
|
||||
privmsg = "[#{repo_name}] #{committer} has created a new branch "
|
||||
privmsg += "#{branch}: #{newbranch}"
|
||||
sendtoirker privmsg
|
||||
end
|
||||
|
||||
def send_del_branch(repo_name, committer, branch)
|
||||
privmsg = "[#{repo_name}] #{committer} has deleted the branch #{branch}"
|
||||
sendtoirker privmsg
|
||||
end
|
||||
|
||||
def send_commits(push_data, project, repo_name, committer, branch)
|
||||
return if push_data['total_commits_count'] == 0
|
||||
|
||||
# Next message is for number of commit pushed, if any
|
||||
if push_data['before'] =~ /^000000/
|
||||
# Tweak on push_data["before"] in order to have a nice compare URL
|
||||
push_data['before'] = before_on_new_branch push_data, project
|
||||
end
|
||||
|
||||
send_commits_count(push_data, project, repo_name, committer, branch)
|
||||
|
||||
# One message per commit, limited by 3 messages (same limit as the
|
||||
# github irc hook)
|
||||
commits = push_data['commits'].first(3)
|
||||
commits.each do |hook_attrs|
|
||||
send_one_commit project, hook_attrs, repo_name, branch
|
||||
end
|
||||
end
|
||||
|
||||
def before_on_new_branch(push_data, project)
|
||||
commit = commit_from_id project, push_data['commits'][0]['id']
|
||||
parents = commit.parents
|
||||
# Return old value if there's no new one
|
||||
return push_data['before'] if parents.empty?
|
||||
# Or return the first parent-commit
|
||||
parents[0].id
|
||||
end
|
||||
|
||||
def send_commits_count(data, project, repo, committer, branch)
|
||||
url = compare_url data, project.path_with_namespace
|
||||
commits = colorize_commits data['total_commits_count']
|
||||
|
||||
new_commits = 'new commit'
|
||||
new_commits += 's' if data['total_commits_count'] > 1
|
||||
|
||||
sendtoirker "[#{repo}] #{committer} pushed #{commits} #{new_commits} " \
|
||||
"to #{branch}: #{url}"
|
||||
end
|
||||
|
||||
def compare_url(data, repo_path)
|
||||
sha1 = Commit::truncate_sha(data['before'])
|
||||
sha2 = Commit::truncate_sha(data['after'])
|
||||
compare_url = "#{Gitlab.config.gitlab.url}/#{repo_path}/compare"
|
||||
compare_url += "/#{sha1}...#{sha2}"
|
||||
colorize_url compare_url
|
||||
end
|
||||
|
||||
def send_one_commit(project, hook_attrs, repo_name, branch)
|
||||
commit = commit_from_id project, hook_attrs['id']
|
||||
sha = colorize_sha Commit::truncate_sha(hook_attrs['id'])
|
||||
author = hook_attrs['author']['name']
|
||||
files = colorize_nb_files(files_count commit)
|
||||
title = commit.title
|
||||
|
||||
sendtoirker "#{repo_name}/#{branch} #{sha} #{author} (#{files}): #{title}"
|
||||
end
|
||||
|
||||
def commit_from_id(project, id)
|
||||
commit = Gitlab::Git::Commit.find(project.repository, id)
|
||||
Commit.new(commit)
|
||||
end
|
||||
|
||||
def files_count(commit)
|
||||
files = "#{commit.diffs.count} file"
|
||||
files += 's' if commit.diffs.count > 1
|
||||
files
|
||||
end
|
||||
|
||||
def colorize_sha(sha)
|
||||
sha = "\x0314#{sha}\x0f" if @colors
|
||||
sha
|
||||
end
|
||||
|
||||
def colorize_nb_files(nb_files)
|
||||
nb_files = "\x0312#{nb_files}\x0f" if @colors
|
||||
nb_files
|
||||
end
|
||||
|
||||
def colorize_url(url)
|
||||
url = "\x0302\x1f#{url}\x0f" if @colors
|
||||
url
|
||||
end
|
||||
|
||||
def colorize_commits(commits)
|
||||
commits = "\x02#{commits}\x0f" if @colors
|
||||
commits
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
# Irker IRC Gateway
|
||||
|
||||
GitLab provides a way to push update messages to an Irker server. When
|
||||
configured, pushes to a project will trigger the service to send data directly
|
||||
to the Irker server.
|
||||
|
||||
See the project homepage for further info: http://www.catb.org/esr/irker/
|
||||
|
||||
## Needed setup
|
||||
|
||||
You will first need an Irker daemon. You can download the Irker code from its
|
||||
gitorious repository on https://gitorious.org/irker: `git clone
|
||||
git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can
|
||||
run the python script named `irkerd`. This script is the gateway script, it acts
|
||||
both as an IRC client, for sending messages to an IRC server obviously, and as a
|
||||
TCP server, for receiving messages from the GitLab service.
|
||||
|
||||
If the Irker server runs on the same machine, you are done. If not, you will
|
||||
need to follow the firsts steps of the next section.
|
||||
|
||||
## Optional setup
|
||||
|
||||
In the `app/models/project_services/irker_service.rb` file, you can modify some
|
||||
options in the `initialize_settings` method:
|
||||
- **server_ip** (defaults to `localhost`): the server IP address where the
|
||||
`irkerd` daemon runs;
|
||||
- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon;
|
||||
- **max_channels** (defaults to `3`): the maximum number of recipients the
|
||||
client is authorized to join, per project;
|
||||
- **default_irc_uri** (no default) : if this option is set, it has to be in the
|
||||
format `irc[s]://domain.name` and will be prepend to each and every channel
|
||||
provided by the user which is not a full URI.
|
||||
|
||||
If the Irker server and the GitLab application do not run on the same host, you
|
||||
will **need** to setup at least the **server_ip** option.
|
||||
|
||||
## Note on Irker recipients
|
||||
|
||||
Irker accepts channel names of the form `chan` and `#chan`, both for the
|
||||
`#chan` channel. If you want to send messages in query, you will need to add
|
||||
`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter
|
||||
case, `Aorimn` is treated as a nick and no more as a channel name.
|
||||
|
||||
Irker can also join password-protected channels. Users need to append
|
||||
`?key=thesecretpassword` to the chan name.
|
||||
|
|
@ -13,6 +13,7 @@ __Project integrations with external services for continuous integration and mor
|
|||
- Gemnasium
|
||||
- GitLab CI
|
||||
- HipChat
|
||||
- [Irker](irker.md) An IRC gateway to receive messages on repository updates.
|
||||
- Pivotal Tracker
|
||||
- Pushover
|
||||
- Slack
|
||||
|
|
|
@ -61,6 +61,12 @@ Feature: Project Services
|
|||
And I fill email on push settings
|
||||
Then I should see email on push service settings saved
|
||||
|
||||
Scenario: Activate Irker (IRC Gateway) service
|
||||
When I visit project "Shop" services page
|
||||
And I click Irker service link
|
||||
And I fill Irker settings
|
||||
Then I should see Irker service settings saved
|
||||
|
||||
Scenario: Activate Atlassian Bamboo CI service
|
||||
When I visit project "Shop" services page
|
||||
And I click Atlassian Bamboo CI service link
|
||||
|
|
|
@ -17,6 +17,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
|
|||
page.should have_content 'Atlassian Bamboo'
|
||||
page.should have_content 'JetBrains TeamCity'
|
||||
page.should have_content 'Asana'
|
||||
page.should have_content 'Irker (IRC gateway)'
|
||||
end
|
||||
|
||||
step 'I click gitlab-ci service link' do
|
||||
|
@ -132,6 +133,22 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
|
|||
find_field('Recipients').value.should == 'qa@company.name'
|
||||
end
|
||||
|
||||
step 'I click Irker service link' do
|
||||
click_link 'Irker (IRC gateway)'
|
||||
end
|
||||
|
||||
step 'I fill Irker settings' do
|
||||
check 'Active'
|
||||
fill_in 'Recipients', with: 'irc://chat.freenode.net/#commits'
|
||||
check 'Colorize messages'
|
||||
click_button 'Save'
|
||||
end
|
||||
|
||||
step 'I should see Irker service settings saved' do
|
||||
find_field('Recipients').value.should == 'irc://chat.freenode.net/#commits'
|
||||
find_field('Colorize messages').value.should == '1'
|
||||
end
|
||||
|
||||
step 'I click Slack service link' do
|
||||
click_link 'Slack'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: services
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# type :string(255)
|
||||
# title :string(255)
|
||||
# project_id :integer not null
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# active :boolean default(FALSE), not null
|
||||
# properties :text
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
require 'socket'
|
||||
require 'json'
|
||||
|
||||
describe IrkerService do
|
||||
describe 'Associations' do
|
||||
it { should belong_to :project }
|
||||
it { should have_one :service_hook }
|
||||
end
|
||||
|
||||
describe 'Validations' do
|
||||
before do
|
||||
subject.active = true
|
||||
subject.properties['recipients'] = _recipients
|
||||
end
|
||||
|
||||
context 'active' do
|
||||
let(:_recipients) { nil }
|
||||
it { should validate_presence_of :recipients }
|
||||
end
|
||||
|
||||
context 'too many recipients' do
|
||||
let(:_recipients) { 'a b c d' }
|
||||
it 'should add an error if there is too many recipients' do
|
||||
subject.send :check_recipients_count
|
||||
subject.errors.should_not be_blank
|
||||
end
|
||||
end
|
||||
|
||||
context '3 recipients' do
|
||||
let(:_recipients) { 'a b c' }
|
||||
it 'should not add an error if there is 3 recipients' do
|
||||
subject.send :check_recipients_count
|
||||
subject.errors.should be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Execute' do
|
||||
let(:irker) { IrkerService.new }
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
|
||||
|
||||
let(:recipients) { '#commits' }
|
||||
let(:colorize_messages) { '1' }
|
||||
|
||||
before do
|
||||
irker.stub(
|
||||
active: true,
|
||||
project: project,
|
||||
project_id: project.id,
|
||||
service_hook: true,
|
||||
properties: {
|
||||
'recipients' => recipients,
|
||||
'colorize_messages' => colorize_messages
|
||||
}
|
||||
)
|
||||
irker.settings = {
|
||||
server_ip: 'localhost',
|
||||
server_port: 6659,
|
||||
max_channels: 3,
|
||||
default_irc_uri: 'irc://chat.freenode.net/'
|
||||
}
|
||||
irker.valid?
|
||||
@irker_server = TCPServer.new 'localhost', 6659
|
||||
end
|
||||
|
||||
after do
|
||||
@irker_server.close
|
||||
end
|
||||
|
||||
it 'should send valid JSON messages to an Irker listener' do
|
||||
irker.execute(sample_data)
|
||||
|
||||
conn = @irker_server.accept
|
||||
conn.readlines.each do |line|
|
||||
msg = JSON.load(line.chomp("\n"))
|
||||
msg.keys.should match_array(['to', 'privmsg'])
|
||||
if msg['to'].is_a?(String)
|
||||
msg['to'].should == 'irc://chat.freenode.net/#commits'
|
||||
else
|
||||
msg['to'].should match_array(['irc://chat.freenode.net/#commits'])
|
||||
end
|
||||
end
|
||||
conn.close
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue