summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValery Sizov <valery@gitlab.com>2015-05-04 11:30:47 +0000
committerValery Sizov <valery@gitlab.com>2015-05-04 11:30:47 +0000
commitf749a221520ef1e8013059910a98f69f4c1d08da (patch)
tree381e049168afd0e6d127314ccc3a1bf9d8847ceb
parent3bbd2f55a299b31a702b7ae6aa2624e5116bc8e8 (diff)
parentf2e65ba17b6b707111d9848d54f97f4fc4febe21 (diff)
downloadgitlab-ci-f749a221520ef1e8013059910a98f69f4c1d08da.tar.gz
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
-rw-r--r--CHANGELOG1
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock5
-rw-r--r--app/controllers/services_controller.rb3
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/project_services/hip_chat_message.rb76
-rw-r--r--app/models/project_services/hip_chat_service.rb80
-rw-r--r--app/workers/hip_chat_notifier_worker.rb18
-rw-r--r--spec/models/project_services/hip_chat_message_spec.rb65
-rw-r--r--spec/models/project_services/hip_chat_service_spec.rb61
-rw-r--r--spec/models/project_services/slack_message_spec.rb (renamed from spec/models/slack_message_spec.rb)0
-rw-r--r--spec/models/project_services/slack_service_spec.rb (renamed from spec/models/slack_service_spec.rb)0
12 files changed, 313 insertions, 2 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 84499ee..5f157e0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
diff --git a/Gemfile b/Gemfile
index b0b803e..9950761 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
diff --git a/Gemfile.lock b/Gemfile.lock
index 41bc118..d2c9158 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
@@ -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)
@@ -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
diff --git a/app/controllers/services_controller.rb b/app/controllers/services_controller.rb
index 39cf306..64bc698 100644
--- a/app/controllers/services_controller.rb
+++ b/app/controllers/services_controller.rb
@@ -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
diff --git a/app/models/project.rb b/app/models/project.rb
index 31ce6ab..c48b079 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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
@@ -210,7 +211,7 @@ ls -la
end
def available_services_names
- %w(slack mail)
+ %w(slack mail hip_chat)
end
def build_missing_services
diff --git a/app/models/project_services/hip_chat_message.rb b/app/models/project_services/hip_chat_message.rb
new file mode 100644
index 0000000..8350bb6
--- /dev/null
+++ b/app/models/project_services/hip_chat_message.rb
@@ -0,0 +1,76 @@
+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
+
diff --git a/app/models/project_services/hip_chat_service.rb b/app/models/project_services/hip_chat_service.rb
new file mode 100644
index 0000000..8e5f024
--- /dev/null
+++ b/app/models/project_services/hip_chat_service.rb
@@ -0,0 +1,80 @@
+# == 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
diff --git a/app/workers/hip_chat_notifier_worker.rb b/app/workers/hip_chat_notifier_worker.rb
new file mode 100644
index 0000000..0403578
--- /dev/null
+++ b/app/workers/hip_chat_notifier_worker.rb
@@ -0,0 +1,18 @@
+
+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
diff --git a/spec/models/project_services/hip_chat_message_spec.rb b/spec/models/project_services/hip_chat_message_spec.rb
new file mode 100644
index 0000000..6096b5c
--- /dev/null
+++ b/spec/models/project_services/hip_chat_message_spec.rb
@@ -0,0 +1,65 @@
+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
diff --git a/spec/models/project_services/hip_chat_service_spec.rb b/spec/models/project_services/hip_chat_service_spec.rb
new file mode 100644
index 0000000..5642221
--- /dev/null
+++ b/spec/models/project_services/hip_chat_service_spec.rb
@@ -0,0 +1,61 @@
+
+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
+
diff --git a/spec/models/slack_message_spec.rb b/spec/models/project_services/slack_message_spec.rb
index 1fa2e31..1fa2e31 100644
--- a/spec/models/slack_message_spec.rb
+++ b/spec/models/project_services/slack_message_spec.rb
diff --git a/spec/models/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index e1c1428..e1c1428 100644
--- a/spec/models/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb