Skip to content
Snippets Groups Projects
Commit f749a221 authored by Valery Sizov's avatar Valery Sizov
Browse files

Merge branch 'hipchat_service' into 'master'

Add HipChat Notification Service

My company is looking for a secure, private, stable and hopefully less-expensive alternative to our existing GitHub & Jenkins setup, and is currently focused on GitLab and GitLab CI after trying a number of other solutions. The biggest hurdle is integrations.

I saw that GitLab CI was lacking a service to notify HipChat of builds, and in my search to see if there was a workaround I saw [someone ask about it](http://feedback.gitlab.com/forums/176466-general/suggestions/5350117-gitlab-ci-should-push-notifications-to-configured), and figured it just hadn't been done yet. So, I did it.

* Move existing Slack service spec into a subdir, mirroring /app
* Wire up HipChat service to the project and services controller.
* Split the message building into own class.
* 'namespace' room and token variables.
* Enforce v2 client (bug in HipChat gem v1.5.0. fixed in 1.5.1). Note
  that I'm using the same version string as GitLab-CE, for shared
  installations. This does prevent 'old' room tokens from being reused.
  'v1' is more compatible, but there is rumblings about finally deprecating
  it and moving to v2 only on their GitHub issue tracker for this gem.
* Defer execution to a notifier worker, like the Slack service.
* Ensure passing specs (basically a Slack service spec copy, fwiw)
* Added change to the CHANGELOG

I'm not sure exactly how your feedback's "Accepting Merge Requests" tag is supposed to work, but I'm happy to learn and change my contribution procedure if anything is wrong here. Thanks!

![Screen_Shot_2015-04-26_at_8.48.19_AM](https://gitlab.com/uxp/gitlab-ci/uploads/296e9d86e369dd33ba99e44acbc8ea78/Screen_Shot_2015-04-26_at_8.48.19_AM.png)

See merge request !83
parents 3bbd2f55 f2e65ba1
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -4,6 +4,7 @@ v7.11.0
- Improved runners page
- Running and Pending tabs on admin builds page
- Fix [ci skip] tag, so you can skip CI triggering now
- Add HipChat notifications
 
v7.10.1
- Fix failing migration when update to 7.10 from 7.8 and older versions
Loading
Loading
Loading
Loading
@@ -63,6 +63,9 @@ gem "default_value_for", "~> 3.0.0"
# Slack integration
gem "slack-notifier", "~> 1.0.0"
 
# HipChat integration
gem 'hipchat', '~> 1.5.0'
# Other
gem 'rake'
gem 'foreman'
Loading
Loading
Loading
Loading
@@ -156,6 +156,9 @@ GEM
hashie (2.0.5)
highline (1.6.21)
hike (1.2.3)
hipchat (1.5.0)
httparty
mimemagic
httparty (0.11.0)
multi_json (~> 1.0)
multi_xml (>= 0.5.2)
Loading
Loading
@@ -186,6 +189,7 @@ GEM
mime-types (>= 1.16, < 3)
method_source (0.8.2)
mime-types (2.4.3)
mimemagic (0.3.0)
mini_portile (0.5.2)
minitest (5.5.1)
multi_json (1.11.0)
Loading
Loading
@@ -390,6 +394,7 @@ DEPENDENCIES
growl
guard-rspec
haml-rails (~> 0.5.3)
hipchat (~> 1.5.0)
httparty (= 0.11.0)
jquery-rails
jquery-turbolinks
Loading
Loading
Loading
Loading
@@ -50,7 +50,8 @@ class ServicesController < ApplicationController
def service_params
params.require(:service).permit(
:type, :active, :webhook, :notify_only_broken_builds,
:email_recipients, :email_only_broken_builds, :email_add_pusher
:email_recipients, :email_only_broken_builds, :email_add_pusher,
:hipchat_token, :hipchat_room, :hipchat_server
)
end
end
Loading
Loading
@@ -37,6 +37,7 @@ class Project < ActiveRecord::Base
 
# Project services
has_many :services, dependent: :destroy
has_one :hip_chat_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :mail_service, dependent: :destroy
 
Loading
Loading
@@ -210,7 +211,7 @@ ls -la
end
 
def available_services_names
%w(slack mail)
%w(slack mail hip_chat)
end
 
def build_missing_services
Loading
Loading
class HipChatMessage
attr_reader :build
def initialize(build)
@build = build
end
def to_s
lines = Array.new
lines.push("<a href=\"#{RoutesHelper.project_url(project)}\">#{project.name}</a> - ")
if commit.matrix?
lines.push("<a href=\"#{RoutesHelper.project_ref_commit_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>")
else
first_build = commit.builds_without_retry.first
lines.push("<a href=\"#{RoutesHelper.project_build_url(project, first_build)}\">Build '#{first_build.job_name}' ##{first_build.id}</a></br>")
end
lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
lines.join('')
end
def status_color(build_or_commit=nil)
build_or_commit ||= commit_status
case build_or_commit
when :success
'green'
when :failed, :canceled
'red'
else # :pending, :running or unknown
'yellow'
end
end
def notify?
[:failed, :canceled].include?(commit_status)
end
private
def commit
build.commit
end
def project
commit.project
end
def build_status
build.status.to_sym
end
def commit_status
commit.status.to_sym
end
def humanized_status(build_or_commit=nil)
build_or_commit ||= commit_status
case build_or_commit
when :pending
"Pending"
when :running
"Running"
when :failed
"Failed"
when :success
"Successful"
when :canceled
"Canceled"
else
"Unknown"
end
end
end
# == 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
#
class HipChatService < Service
prop_accessor :hipchat_token, :hipchat_room, :hipchat_server
boolean_accessor :notify_only_broken_builds
validates :hipchat_token, presence: true, if: :activated?
validates :hipchat_room, presence: true, if: :activated?
default_value_for :notify_only_broken_builds, true
def title
"HipChat"
end
def description
"Private group chat, video chat, instant messaging for teams"
end
def help
end
def to_param
'hip_chat'
end
def fields
[
{ type: 'text', name: 'hipchat_token', label: 'Token', placeholder: '' },
{ type: 'text', name: 'hipchat_room', label: 'Room', placeholder: '' },
{ type: 'text', name: 'hipchat_server', label: 'Server', placeholder: 'https://hipchat.example.com', help: 'Leave blank for default' },
{ type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
]
end
def execute build
commit = build.commit
return unless commit
return unless commit.builds_without_retry.include? build
return if commit.success? and notify_only_broken_builds?
return if commit.running?
msg = HipChatMessage.new(build)
opts = default_options.merge(
token: hipchat_token,
room: hipchat_room,
server: server_url,
color: msg.status_color,
notify: msg.notify?
)
HipChatNotifierWorker.perform_async(msg.to_s, opts)
end
private
def default_options
{
service_name: 'GitLab CI',
message_format: 'html'
}
end
def server_url
if hipchat_server.blank?
'https://api.hipchat.com'
else
hipchat_server
end
end
end
class HipChatNotifierWorker
include Sidekiq::Worker
def perform(message, options={})
room = options.delete('room')
token = options.delete('token')
server = options.delete('server')
name = options.delete('service_name')
client_opts = {
api_version: 'v2',
server_url: server
}
client = HipChat::Client.new(token, client_opts)
client[room].send(name, message, options.symbolize_keys)
end
end
require 'spec_helper'
describe HipChatMessage do
subject { HipChatMessage.new(build) }
let(:project) { FactoryGirl.create(:project) }
let(:commit) { FactoryGirl.create(:commit, project: project) }
let(:job) { FactoryGirl.create(:job, project: project) }
let(:build) { FactoryGirl.create(:build, commit: commit, job: job, status: 'success') }
context 'when build succeeds' do
before { build.save }
it 'returns a successful message' do
expect( subject.status_color ).to eq 'green'
expect( subject.notify? ).to be_false
expect( subject.to_s ).to match(/Build '[^']+' #\d+/)
expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./)
end
end
context 'when build fails' do
before do
build.status = 'failed'
build.save
end
it 'returns a failure message' do
expect( subject.status_color ).to eq 'red'
expect( subject.notify? ).to be_true
expect( subject.to_s ).to match(/Build '[^']+' #\d+/)
expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./)
end
end
context 'when all matrix builds succeed' do
let(:job2) { FactoryGirl.create(:job, project: project, name: 'Another Job') }
let(:build2) { FactoryGirl.create(:build, id: 10, commit: commit, job: job2, status: 'success') }
before { build.save; build2.save }
it 'returns a successful message' do
expect( subject.status_color ).to eq 'green'
expect( subject.notify? ).to be_false
expect( subject.to_s ).to match(/Commit #\d+/)
expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./)
end
end
context 'when at least one matrix build fails' do
let(:job2) { FactoryGirl.create(:job, project: project, name: 'Another Job') }
let(:build2) { FactoryGirl.create(:build, id: 10, commit: commit, job: job2, status: 'failed') }
before { build.save; build2.save }
it 'returns a failure message' do
expect( subject.status_color ).to eq 'red'
expect( subject.notify? ).to be_true
expect( subject.to_s ).to match(/Commit #\d+/)
expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./)
end
end
end
require 'spec_helper'
describe HipChatService do
describe "Validations" do
context "active" do
before do
subject.active = true
end
it { should validate_presence_of :hipchat_room }
it { should validate_presence_of :hipchat_token }
end
end
describe "Execute" do
let(:service) { HipChatService.new }
let(:project) { FactoryGirl.create :project }
let(:commit) { FactoryGirl.create :commit, project: project }
let(:build) { FactoryGirl.create :build, commit: commit, status: 'failed' }
let(:api_url) { 'https://api.hipchat.com/v2/room/123/notification?auth_token=a1b2c3d4e5f6' }
before do
service.stub(
project: project,
project_id: project.id,
notify_only_broken_builds: false,
hipchat_room: 123,
hipchat_token: 'a1b2c3d4e5f6'
)
WebMock.stub_request(:post, api_url)
end
it "should call the HipChat API" do
service.execute(build)
HipChatNotifierWorker.drain
expect( WebMock ).to have_requested(:post, api_url).once
end
it "calls the worker with expected arguments" do
expect( HipChatNotifierWorker ).to receive(:perform_async) \
.with(an_instance_of(String), hash_including(
token: 'a1b2c3d4e5f6',
room: 123,
server: 'https://api.hipchat.com',
color: 'red',
notify: true
))
service.execute(build)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment