summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVõ Anh Duy <voanhduy1512@live.com>2014-02-23 06:42:04 +0700
committerVõ Anh Duy <voanhduy1512@live.com>2014-02-25 00:21:41 +0700
commitf5fc67bc2c3f20f80aef701254542d17ea5ccd8a (patch)
treef4184b6cbbdc5eda524cfd261954c831e2c709fe
parenta1ecd0fbd6742bf827efe8c869a8b9115d2e4c05 (diff)
downloadgitlab-ci-f5fc67bc2c3f20f80aef701254542d17ea5ccd8a.tar.gz
create web hook
-rw-r--r--app/models/build.rb3
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/web_hook.rb30
-rw-r--r--app/services/web_hook_service.rb34
-rw-r--r--app/workers/web_hook_worker.rb9
-rw-r--r--db/migrate/20140222210357_create_web_hook.rb8
-rw-r--r--db/schema.rb5
-rw-r--r--spec/factories/web_hook.rb5
-rw-r--r--spec/models/web_hook_spec.rb58
-rw-r--r--spec/services/web_hook_service_spec.rb22
10 files changed, 179 insertions, 0 deletions
diff --git a/app/models/build.rb b/app/models/build.rb
index 793400b..090af88 100644
--- a/app/models/build.rb
+++ b/app/models/build.rb
@@ -78,6 +78,9 @@ class Build < ActiveRecord::Base
after_transition any => [:success, :failed, :canceled] do |build, transition|
build.update_attributes finished_at: Time.now
project = build.project
+ if project.web_hooks?
+ WebHookService.new.execute_hooks(build)
+ end
if project.email_notification?
if build.status.to_sym == :failed || !project.email_only_broken_builds
diff --git a/app/models/project.rb b/app/models/project.rb
index b916c9f..9486d23 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -31,6 +31,7 @@ class Project < ActiveRecord::Base
has_many :builds, dependent: :destroy
has_many :runner_projects, dependent: :destroy
has_many :runners, through: :runner_projects
+ has_many :web_hooks
#
# Validations
@@ -142,6 +143,10 @@ class Project < ActiveRecord::Base
email_add_committer || email_recipients.present?
end
+ def web_hooks?
+ web_hooks.any?
+ end
+
# onlu check for toggling build status within same ref.
def last_build_changed_status?
ref = last_build.ref
diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb
new file mode 100644
index 0000000..dc1d1db
--- /dev/null
+++ b/app/models/web_hook.rb
@@ -0,0 +1,30 @@
+class WebHook < ActiveRecord::Base
+ belongs_to :project
+ include HTTParty
+
+ attr_accessible :url
+
+ # HTTParty timeout
+ default_timeout 10
+
+ validates :url, presence: true,
+ format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
+
+ def execute(data)
+ parsed_url = URI.parse(url)
+ if parsed_url.userinfo.blank?
+ WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
+ else
+ post_url = url.gsub("#{parsed_url.userinfo}@", "")
+ auth = {
+ username: URI.decode(parsed_url.user),
+ password: URI.decode(parsed_url.password),
+ }
+ WebHook.post(post_url,
+ body: data.to_json,
+ headers: {"Content-Type" => "application/json"},
+ verify: false,
+ basic_auth: auth)
+ end
+ end
+end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
new file mode 100644
index 0000000..2e42350
--- /dev/null
+++ b/app/services/web_hook_service.rb
@@ -0,0 +1,34 @@
+class WebHookService
+ def build_end(build)
+ execute_hooks(build.project, build_data(build))
+ end
+
+ def execute_hooks(project, data)
+ project.web_hooks.each do |wh|
+ async_execute_hook wh, data
+ end
+ end
+
+ def async_execute_hook(hook, data)
+ Sidekiq::Client.enqueue(WebHookWorker, hook.id, data)
+ end
+
+ def build_data(build)
+ project = build.project
+ data = {}
+ data.merge!({
+ id: build.id,
+ project_id: project.id,
+ project_name: project.name,
+ gitlab_url: project.gitlab_url,
+ ref: build.ref,
+ status: build.status,
+ started_at: build.started_at,
+ finished_at: build.finished_at,
+ sha: build.sha,
+ before_sha: build.before_sha,
+ push_data: build.push_data
+
+ })
+ end
+end
diff --git a/app/workers/web_hook_worker.rb b/app/workers/web_hook_worker.rb
new file mode 100644
index 0000000..f51b975
--- /dev/null
+++ b/app/workers/web_hook_worker.rb
@@ -0,0 +1,9 @@
+class WebHookWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :web_hook
+
+ def perform(hook_id, data)
+ WebHook.find(hook_id).execute data
+ end
+end
diff --git a/db/migrate/20140222210357_create_web_hook.rb b/db/migrate/20140222210357_create_web_hook.rb
new file mode 100644
index 0000000..8edf8cf
--- /dev/null
+++ b/db/migrate/20140222210357_create_web_hook.rb
@@ -0,0 +1,8 @@
+class CreateWebHook < ActiveRecord::Migration
+ def change
+ create_table :web_hooks do |t|
+ t.string "url"
+ t.integer "project_id"
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6a40d19..8385d5b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -80,4 +80,9 @@ ActiveRecord::Schema.define(version: 20140130121538) do
add_index "sessions", ["session_id"], name: "index_sessions_on_session_id", using: :btree
add_index "sessions", ["updated_at"], name: "index_sessions_on_updated_at", using: :btree
+ create_table "web_hooks", force: true do |t|
+ t.string "url"
+ t.integer "project_id"
+ end
+
end
diff --git a/spec/factories/web_hook.rb b/spec/factories/web_hook.rb
new file mode 100644
index 0000000..23ca830
--- /dev/null
+++ b/spec/factories/web_hook.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :web_hook do
+ sequence(:url) { Faker::Internet.uri('http') }
+ end
+end
diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb
new file mode 100644
index 0000000..c8ca95d
--- /dev/null
+++ b/spec/models/web_hook_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe WebHook do
+ describe "Associations" do
+ it { should belong_to :project }
+ end
+
+ describe "Mass assignment" do
+ it { should_not allow_mass_assignment_of(:project_id) }
+ end
+
+ describe "Validations" do
+ it { should validate_presence_of(:url) }
+
+ context "url format" do
+ it { should allow_value("http://example.com").for(:url) }
+ it { should allow_value("https://excample.com").for(:url) }
+ it { should allow_value("http://test.com/api").for(:url) }
+ it { should allow_value("http://test.com/api?key=abc").for(:url) }
+ it { should allow_value("http://test.com/api?key=abc&type=def").for(:url) }
+
+ it { should_not allow_value("example.com").for(:url) }
+ it { should_not allow_value("ftp://example.com").for(:url) }
+ it { should_not allow_value("herp-and-derp").for(:url) }
+ end
+ end
+
+ describe "execute" do
+ before(:each) do
+ @web_hook = FactoryGirl.create(:web_hook)
+ @project = FactoryGirl.create(:project)
+ @project.web_hooks << [@web_hook]
+ @data = { before: 'oldrev', after: 'newrev', ref: 'ref'}
+
+ WebMock.stub_request(:post, @web_hook.url)
+ end
+
+ it "POSTs to the web hook URL" do
+ @web_hook.execute(@data)
+ WebMock.should have_requested(:post, @web_hook.url).once
+ end
+
+ it "POSTs the data as JSON" do
+ json = @data.to_json
+
+ @web_hook.execute(@data)
+ WebMock.should have_requested(:post, @web_hook.url).with(body: json).once
+ end
+
+ it "catches exceptions" do
+ WebHook.should_receive(:post).and_raise("Some HTTP Post error")
+
+ lambda {
+ @web_hook.execute(@data)
+ }.should raise_error
+ end
+ end
+end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
new file mode 100644
index 0000000..1ddb4dc
--- /dev/null
+++ b/spec/services/web_hook_service_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe WebHookService do
+ let (:project) { FactoryGirl.create :project }
+ let (:build) { FactoryGirl.create :build, project: project }
+ let (:hook) { FactoryGirl.create :web_hook, project: project }
+
+ describe :execute do
+ it "should execute successfully" do
+ stub_request(:post, hook.url).to_return(status: 200)
+ WebHookService.new.build_end(build).should be_true
+ end
+ end
+
+ context 'build_data' do
+ it { build_data(build).should include :id, :project_id, :ref, :status, :started_at, :finished_at, :before_sha, :project_name, :gitlab_url }
+ end
+
+ def build_data(build)
+ WebHookService.new.send :build_data, build
+ end
+end