Commit 161a419e authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'feature/system_hook_push' into 'master'

Added System Hooks for push and tag_push

Implements `push` and `tag_push` events for System Hooks.

They are based on Webhooks Events but without any deprecated item. We don't send commits too.
We are implementing this, to be used by Geo repository syncing (gitlab-org/gitlab-ee#76).

These hook events will be sent only when the respective configuration flags are set to true.
UI changes to admin screen will be made in another MR.

See merge request !3744
parents 2234ac52 8ead3d0d
...@@ -87,6 +87,8 @@ v 8.7.0 (unreleased) ...@@ -87,6 +87,8 @@ v 8.7.0 (unreleased)
- Use GitHub Issue/PR number as iid to keep references - Use GitHub Issue/PR number as iid to keep references
- Import GitHub labels - Import GitHub labels
- Import GitHub milestones - Import GitHub milestones
- Fix emoji catgories in the emoji picker
- Execute system web hooks on push to the project
v 8.6.6 v 8.6.6
- Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
......
...@@ -21,8 +21,6 @@ ...@@ -21,8 +21,6 @@
class ProjectHook < WebHook class ProjectHook < WebHook
belongs_to :project belongs_to :project
scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
scope :issue_hooks, -> { where(issues_events: true) } scope :issue_hooks, -> { where(issues_events: true) }
scope :note_hooks, -> { where(note_events: true) } scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) }
......
...@@ -19,4 +19,7 @@ ...@@ -19,4 +19,7 @@
# #
class SystemHook < WebHook class SystemHook < WebHook
def async_execute(data, hook_name)
Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
end
end end
...@@ -30,6 +30,9 @@ class WebHook < ActiveRecord::Base ...@@ -30,6 +30,9 @@ class WebHook < ActiveRecord::Base
default_value_for :build_events, false default_value_for :build_events, false
default_value_for :enable_ssl_verification, true default_value_for :enable_ssl_verification, true
scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
# HTTParty timeout # HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout default_timeout Gitlab.config.gitlab.webhook_timeout
......
...@@ -831,8 +831,8 @@ class Project < ActiveRecord::Base ...@@ -831,8 +831,8 @@ class Project < ActiveRecord::Base
end end
end end
def hook_attrs def hook_attrs(backward: true)
{ attrs = {
name: name, name: name,
description: description, description: description,
web_url: web_url, web_url: web_url,
...@@ -843,12 +843,19 @@ class Project < ActiveRecord::Base ...@@ -843,12 +843,19 @@ class Project < ActiveRecord::Base
visibility_level: visibility_level, visibility_level: visibility_level,
path_with_namespace: path_with_namespace, path_with_namespace: path_with_namespace,
default_branch: default_branch, default_branch: default_branch,
}
# Backward compatibility # Backward compatibility
if backward
attrs.merge!({
homepage: web_url, homepage: web_url,
url: url_to_repo, url: url_to_repo,
ssh_url: ssh_url_to_repo, ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo http_url: http_url_to_repo
} })
end
attrs
end end
# Reset events cache related to this project # Reset events cache related to this project
......
...@@ -73,6 +73,7 @@ class GitPushService < BaseService ...@@ -73,6 +73,7 @@ class GitPushService < BaseService
@project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user) @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
EventCreateService.new.push(@project, current_user, build_push_data) EventCreateService.new.push(@project, current_user, build_push_data)
SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks)
CreateCommitBuildsService.new.execute(@project, current_user, build_push_data) CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
...@@ -138,6 +139,11 @@ class GitPushService < BaseService ...@@ -138,6 +139,11 @@ class GitPushService < BaseService
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits) build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
end end
def build_push_data_system_hook
@push_data_system ||= Gitlab::PushDataBuilder.
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], [])
end
def push_to_existing_branch? def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits) # Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
......
class GitTagPushService class GitTagPushService < BaseService
attr_accessor :project, :user, :push_data attr_accessor :push_data
def execute(project, user, oldrev, newrev, ref) def execute
project.repository.before_push_tag project.repository.before_push_tag
@project, @user = project, user @push_data = build_push_data
@push_data = build_push_data(oldrev, newrev, ref)
EventCreateService.new.push(project, user, @push_data) EventCreateService.new.push(project, current_user, @push_data)
SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks)
CreateCommitBuildsService.new.execute(project, @user, @push_data) CreateCommitBuildsService.new.execute(project, current_user, @push_data)
ProjectCacheWorker.perform_async(project.id) ProjectCacheWorker.perform_async(project.id)
true true
...@@ -18,14 +18,14 @@ class GitTagPushService ...@@ -18,14 +18,14 @@ class GitTagPushService
private private
def build_push_data(oldrev, newrev, ref) def build_push_data
commits = [] commits = []
message = nil message = nil
if !Gitlab::Git.blank_ref?(newrev) if !Gitlab::Git.blank_ref?(params[:newrev])
tag_name = Gitlab::Git.ref_name(ref) tag_name = Gitlab::Git.ref_name(params[:ref])
tag = project.repository.find_tag(tag_name) tag = project.repository.find_tag(tag_name)
if tag && tag.target == newrev if tag && tag.target == params[:newrev]
commit = project.commit(tag.target) commit = project.commit(tag.target)
commits = [commit].compact commits = [commit].compact
message = tag.message message = tag.message
...@@ -33,6 +33,11 @@ class GitTagPushService ...@@ -33,6 +33,11 @@ class GitTagPushService
end end
Gitlab::PushDataBuilder. Gitlab::PushDataBuilder.
build(project, user, oldrev, newrev, ref, commits, message) build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message)
end
def build_system_push_data
Gitlab::PushDataBuilder.
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '')
end end
end end
...@@ -3,17 +3,13 @@ class SystemHooksService ...@@ -3,17 +3,13 @@ class SystemHooksService
execute_hooks(build_event_data(model, event)) execute_hooks(build_event_data(model, event))
end end
private def execute_hooks(data, hooks_scope = :all)
SystemHook.send(hooks_scope).each do |hook|
def execute_hooks(data) hook.async_execute(data, 'system_hooks')
SystemHook.all.each do |sh|
async_execute_hook(sh, data, 'system_hooks')
end end
end end
def async_execute_hook(hook, data, hook_name) private
Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name)
end
def build_event_data(model, event) def build_event_data(model, event)
data = { data = {
......
...@@ -39,7 +39,7 @@ class PostReceive ...@@ -39,7 +39,7 @@ class PostReceive
end end
if Gitlab::Git.tag_ref?(ref) if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref) GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
elsif Gitlab::Git.branch_ref?(ref) elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end end
......
...@@ -4,6 +4,12 @@ Your GitLab instance can perform HTTP POST requests on the following events: `pr ...@@ -4,6 +4,12 @@ Your GitLab instance can perform HTTP POST requests on the following events: `pr
System hooks can be used, e.g. for logging or changing information in a LDAP server. System hooks can be used, e.g. for logging or changing information in a LDAP server.
> **Note:**
>
> We follow the same structure from Webhooks for Push and Tag events, but we never display commits.
>
> Same deprecations from Webhooks are valid here.
## Hooks request example ## Hooks request example
**Request header**: **Request header**:
...@@ -240,3 +246,110 @@ X-Gitlab-Event: System Hook ...@@ -240,3 +246,110 @@ X-Gitlab-Event: System Hook
"user_id": 41 "user_id": 41
} }
``` ```
## Push events
Triggered when you push to the repository except when pushing tags.
**Request header**:
```
X-Gitlab-Event: System Hook
```
**Request body:**
```json
{
"event_name": "push",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master",
"checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"user_id": 4,
"user_name": "John Smith",
"user_email": "john@example.com",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 15,
"project":{
"name":"Diaspora",
"description":"",
"web_url":"http://example.com/mike/diaspora",
"avatar_url":null,
"git_ssh_url":"git@example.com:mike/diaspora.git",
"git_http_url":"http://example.com/mike/diaspora.git",
"namespace":"Mike",
"visibility_level":0,
"path_with_namespace":"mike/diaspora",
"default_branch":"master",
"homepage":"http://example.com/mike/diaspora",
"url":"git@example.com:mike/diaspora.git",
"ssh_url":"git@example.com:mike/diaspora.git",
"http_url":"http://example.com/mike/diaspora.git"
},
"repository":{
"name": "Diaspora",
"url": "git@example.com:mike/diaspora.git",
"description": "",
"homepage": "http://example.com/mike/diaspora",
"git_http_url":"http://example.com/mike/diaspora.git",
"git_ssh_url":"git@example.com:mike/diaspora.git",
"visibility_level":0
},
"commits": [],
"total_commits_count": 0
}
```
## Tag events
Triggered when you create (or delete) tags to the repository.
**Request header**:
```
X-Gitlab-Event: System Hook
```
**Request body:**
```json
{
"event_name": "tag_push",
"before": "0000000000000000000000000000000000000000",
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"ref": "refs/tags/v1.0.0",
"checkout_sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
"user_id": 1,
"user_name": "John Smith",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 1,
"project":{
"name":"Example",
"description":"",
"web_url":"http://example.com/jsmith/example",
"avatar_url":null,
"git_ssh_url":"git@example.com:jsmith/example.git",
"git_http_url":"http://example.com/jsmith/example.git",
"namespace":"Jsmith",
"visibility_level":0,
"path_with_namespace":"jsmith/example",
"default_branch":"master",
"homepage":"http://example.com/jsmith/example",
"url":"git@example.com:jsmith/example.git",
"ssh_url":"git@example.com:jsmith/example.git",
"http_url":"http://example.com/jsmith/example.git"
},
"repository":{
"name": "Example",
"url": "ssh://git@example.com/jsmith/example.git",
"description": "",
"homepage": "http://example.com/jsmith/example",
"git_http_url":"http://example.com/jsmith/example.git",
"git_ssh_url":"git@example.com:jsmith/example.git",
"visibility_level":0
},
"commits": [],
"total_commits_count": 0
}
```
...@@ -41,6 +41,7 @@ X-Gitlab-Event: Push Hook ...@@ -41,6 +41,7 @@ X-Gitlab-Event: Push Hook
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22", "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master", "ref": "refs/heads/master",
"checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"user_id": 4, "user_id": 4,
"user_name": "John Smith", "user_name": "John Smith",
"user_email": "john@example.com", "user_email": "john@example.com",
...@@ -118,9 +119,10 @@ X-Gitlab-Event: Tag Push Hook ...@@ -118,9 +119,10 @@ X-Gitlab-Event: Tag Push Hook
```json ```json
{ {
"object_kind": "tag_push", "object_kind": "tag_push",
"ref": "refs/tags/v1.0.0",
"before": "0000000000000000000000000000000000000000", "before": "0000000000000000000000000000000000000000",
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7", "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"ref": "refs/tags/v1.0.0",
"checkout_sha": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"user_id": 1, "user_id": 1,
"user_name": "John Smith", "user_name": "John Smith",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80", "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
......
...@@ -36,11 +36,12 @@ module Gitlab ...@@ -36,11 +36,12 @@ module Gitlab
commit.hook_attrs(with_changed_files: true) commit.hook_attrs(with_changed_files: true)
end end
type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push" type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push'
# Hash to be passed as post_receive_data # Hash to be passed as post_receive_data
data = { data = {
object_kind: type, object_kind: type,
event_name: type,
before: oldrev, before: oldrev,
after: newrev, after: newrev,
ref: ref, ref: ref,
......
...@@ -14,11 +14,11 @@ describe Gitlab::PushDataBuilder, lib: true do ...@@ -14,11 +14,11 @@ describe Gitlab::PushDataBuilder, lib: true do
it { expect(data[:ref]).to eq('refs/heads/master') } it { expect(data[:ref]).to eq('refs/heads/master') }
it { expect(data[:commits].size).to eq(3) } it { expect(data[:commits].size).to eq(3) }
it { expect(data[:total_commits_count]).to eq(3) } it { expect(data[:total_commits_count]).to eq(3) }
it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) } it { expect(data[:commits].first[:added]).to eq(['gitlab-grack']) }
it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) } it { expect(data[:commits].first[:modified]).to eq(['.gitmodules']) }
it { expect(data[:commits].first[:removed]).to eq([]) } it { expect(data[:commits].first[:removed]).to eq([]) }
include_examples 'project hook data' include_examples 'project hook data with deprecateds'
include_examples 'deprecated repository hook data' include_examples 'deprecated repository hook data'
end end
...@@ -34,9 +34,18 @@ describe Gitlab::PushDataBuilder, lib: true do ...@@ -34,9 +34,18 @@ describe Gitlab::PushDataBuilder, lib: true do
it { expect(data[:checkout_sha]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } it { expect(data[:checkout_sha]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { expect(data[:after]).to eq('8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b') } it { expect(data[:after]).to eq('8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b') }
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:user_id]).to eq(user.id) }
it { expect(data[:user_name]).to eq(user.name) }
it { expect(data[:user_email]).to eq(user.email) }
it { expect(data[:user_avatar]).to eq(user.avatar_url) }
it { expect(data[:project_id]).to eq(project.id) }
it { expect(data[:project]).to be_a(Hash) }
it { expect(data[:commits]).to be_empty } it { expect(data[:commits]).to be_empty }
it { expect(data[:total_commits_count]).to be_zero } it { expect(data[:total_commits_count]).to be_zero }
include_examples 'project hook data with deprecateds'
include_examples 'deprecated repository hook data'
it 'does not raise an error when given nil commits' do it 'does not raise an error when given nil commits' do
expect { described_class.build(spy, spy, spy, spy, spy, nil) }. expect { described_class.build(spy, spy, spy, spy, spy, nil) }.
not_to raise_error not_to raise_error
......
...@@ -5,19 +5,17 @@ describe GitTagPushService, services: true do ...@@ -5,19 +5,17 @@ describe GitTagPushService, services: true do
let(:user) { create :user } let(:user) { create :user }
let(:project) { create :project } let(:project) { create :project }
let(:service) { GitTagPushService.new } let(:service) { GitTagPushService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
before do let(:oldrev) { Gitlab::Git::BLANK_SHA }
@oldrev = Gitlab::Git::BLANK_SHA let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
@newrev = "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" # gitlab-test: git rev-parse refs/tags/v1.1.0 let(:ref) { 'refs/tags/v1.1.0' }
@ref = 'refs/tags/v1.1.0'
end
describe "Git Tag Push Data" do describe "Git Tag Push Data" do
before do before do
service.execute(project, user, @oldrev, @newrev, @ref) service.execute
@push_data = service.push_data @push_data = service.push_data
@tag_name = Gitlab::Git.ref_name(@ref) @tag_name = Gitlab::Git.ref_name(ref)
@tag = project.repository.find_tag(@tag_name) @tag = project.repository.find_tag(@tag_name)
@commit = project.commit(@tag.target) @commit = project.commit(@tag.target)
end end
...@@ -25,9 +23,9 @@ describe GitTagPushService, services: true do ...@@ -25,9 +23,9 @@ describe GitTagPushService, services: true do
subject { @push_data } subject { @push_data }
it { is_expected.to include(object_kind: 'tag_push') } it { is_expected.to include(object_kind: 'tag_push') }
it { is_expected.to include(ref: @ref) } it { is_expected.to include(ref: ref) }
it { is_expected.to include(before: @oldrev) } it { is_expected.to include(before: oldrev) }
it { is_expected.to include(after: @newrev) } it { is_expected.to include(after: newrev) }
it { is_expected.to include(message: @tag.message) } it { is_expected.to include(message: @tag.message) }
it { is_expected.to include(user_id: user.id) } it { is_expected.to include(user_id: user.id) }
it { is_expected.to include(user_name: user.name) } it { is_expected.to include(user_name: user.name) }
...@@ -80,9 +78,11 @@ describe GitTagPushService, services: true do ...@@ -80,9 +78,11 @@ describe GitTagPushService, services: true do
describe "Webhooks" do describe "Webhooks" do
context "execute webhooks" do context "execute webhooks" do
let(:service) { GitTagPushService.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') }
it "when pushing tags" do it "when pushing tags" do
expect(project).to receive(:execute_hooks) expect(project).to receive(:execute_hooks)
service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0') service.execute
end end
end end
end end
......
RSpec.shared_examples 'project hook data' do |project_key: :project| RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :project|
it 'contains project data' do it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name) expect(data[project_key][:name]).to eq(project.name)
expect(data[project_key][:description]).to eq(project.description) expect(data[project_key][:description]).to eq(project.description)
...@@ -17,6 +17,21 @@ RSpec.shared_examples 'project hook data' do |project_key: :project| ...@@ -17,6 +17,21 @@ RSpec.shared_examples 'project hook data' do |project_key: :project|
end end
end end
RSpec.shared_examples 'project hook data' do |project_key: :project|
it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name)
expect(data[project_key][:description]).to eq(project.description)
expect(data[project_key][:web_url]).to eq(project.web_url)
expect(data[project_key][:avatar_url]).to eq(project.avatar_url)
expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo)
expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo)
expect(data[project_key][:namespace]).to eq(project.namespace.name)
expect(data[project_key][:visibility_level]).to eq(project.visibility_level)
expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace)
expect(data[project_key][:default_branch]).to eq(project.default_branch)
end
end
RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project| RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project|
it 'contains deprecated repository data' do it 'contains deprecated repository data' do
expect(data[:repository][:name]).to eq(project.name) expect(data[:repository][:name]).to eq(project.name)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment