Commit 3ad645a2 authored by Thong Kuah's avatar Thong Kuah

Merge branch...

Merge branch '17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option' into 'master'

Skip TeamCity trigger on branch delete and support MR triggers

Closes #13871 and #17690

See merge request gitlab-org/gitlab-ce!29836
parents 4ec1720f 6027375c
# frozen_string_literal: true
# This concern is used by registerd services such as TeamCityService and
# DroneCiService and add methods to perform validations on the received
# data.
module ServicePushDataValidations
extend ActiveSupport::Concern
def merge_request_valid?(data)
data.dig(:object_attributes, :state) == 'opened' && merge_request_unchecked?(data)
end
def push_valid?(data)
data[:total_commits_count] > 0 &&
!branch_removed?(data) &&
# prefer merge request trigger over push to avoid double builds
!opened_merge_requests?(data)
end
def tag_push_valid?(data)
data[:total_commits_count] > 0 && !branch_removed?(data)
end
private
def branch_removed?(data)
Gitlab::Git.blank_ref?(data[:after])
end
def opened_merge_requests?(data)
project.merge_requests
.opened
.from_project(project)
.from_source_branches(Gitlab::Git.ref_name(data[:ref]))
.exists?
end
def merge_request_unchecked?(data)
MergeRequest.state_machines[:merge_status]
.check_state?(data.dig(:object_attributes, :merge_status))
end
end
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class DroneCiService < CiService class DroneCiService < CiService
include ReactiveService include ReactiveService
include ServicePushDataValidations
prop_accessor :drone_url, :token prop_accessor :drone_url, :token
boolean_accessor :enable_ssl_verification boolean_accessor :enable_ssl_verification
...@@ -96,23 +97,4 @@ class DroneCiService < CiService ...@@ -96,23 +97,4 @@ class DroneCiService < CiService
{ type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
] ]
end end
private
def tag_push_valid?(data)
data[:total_commits_count] > 0 && !Gitlab::Git.blank_ref?(data[:after])
end
def push_valid?(data)
opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
source_branch: Gitlab::Git.ref_name(data[:ref]))
opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
!Gitlab::Git.blank_ref?(data[:after])
end
def merge_request_valid?(data)
data[:object_attributes][:state] == 'opened' &&
MergeRequest.state_machines[:merge_status].check_state?(data[:object_attributes][:merge_status])
end
end end
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class TeamcityService < CiService class TeamcityService < CiService
include ReactiveService include ReactiveService
include ServicePushDataValidations
prop_accessor :teamcity_url, :build_type, :username, :password prop_accessor :teamcity_url, :build_type, :username, :password
...@@ -19,6 +20,25 @@ class TeamcityService < CiService ...@@ -19,6 +20,25 @@ class TeamcityService < CiService
after_save :compose_service_hook, if: :activated? after_save :compose_service_hook, if: :activated?
before_update :reset_password before_update :reset_password
class << self
def to_param
'teamcity'
end
def supported_events
%w(push merge_request)
end
def event_description(event)
case event
when 'push', 'push_events'
'TeamCity CI will be triggered after every push to the repository except branch delete'
when 'merge_request', 'merge_request_events'
'TeamCity CI will be triggered after a merge request has been created or updated'
end
end
end
def compose_service_hook def compose_service_hook
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
hook.save hook.save
...@@ -43,10 +63,6 @@ class TeamcityService < CiService ...@@ -43,10 +63,6 @@ class TeamcityService < CiService
'requests build, that setting is in the vsc root advanced settings.' 'requests build, that setting is in the vsc root advanced settings.'
end end
def self.to_param
'teamcity'
end
def fields def fields
[ [
{ type: 'text', name: 'teamcity_url', { type: 'text', name: 'teamcity_url',
...@@ -74,26 +90,25 @@ class TeamcityService < CiService ...@@ -74,26 +90,25 @@ class TeamcityService < CiService
end end
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) case data[:object_kind]
when 'push'
execute_push(data)
when 'merge_request'
execute_merge_request(data)
end
end
auth = { private
username: username,
password: password
}
def execute_push(data)
branch = Gitlab::Git.ref_name(data[:ref]) branch = Gitlab::Git.ref_name(data[:ref])
post_to_build_queue(data, branch) if push_valid?(data)
Gitlab::HTTP.post(
build_url('httpAuth/app/rest/buildQueue'),
body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
)
end end
private def execute_merge_request(data)
branch = data[:object_attributes][:source_branch]
post_to_build_queue(data, branch) if merge_request_valid?(data)
end
def read_build_page(response) def read_build_page(response)
if response.code != 200 if response.code != 200
...@@ -134,10 +149,21 @@ class TeamcityService < CiService ...@@ -134,10 +149,21 @@ class TeamcityService < CiService
end end
def get_path(path) def get_path(path)
Gitlab::HTTP.get(build_url(path), verify: false, Gitlab::HTTP.get(build_url(path), verify: false, basic_auth: basic_auth)
basic_auth: { end
username: username,
password: password def post_to_build_queue(data, branch)
}) Gitlab::HTTP.post(
build_url('httpAuth/app/rest/buildQueue'),
body: "<build branchName=#{branch.encode(xml: :attr)}>"\
"<buildType id=#{build_type.encode(xml: :attr)}/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: basic_auth
)
end
def basic_auth
{ username: username, password: password }
end end
end end
---
title: Protect TeamCity builds from triggering when a branch has been deleted. And a MR-option
merge_request: 29836
author: Nikolay Novikov, Raphael Tweitmann
type: fixed
...@@ -15,6 +15,8 @@ describe 'User activates JetBrains TeamCity CI' do ...@@ -15,6 +15,8 @@ describe 'User activates JetBrains TeamCity CI' do
it 'activates service' do it 'activates service' do
check('Active') check('Active')
check('Push')
check('Merge request')
fill_in('Teamcity url', with: 'http://teamcity.example.com') fill_in('Teamcity url', with: 'http://teamcity.example.com')
fill_in('Build type', with: 'GitlabTest_Build') fill_in('Build type', with: 'GitlabTest_Build')
fill_in('Username', with: 'user') fill_in('Username', with: 'user')
......
...@@ -7,10 +7,11 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do ...@@ -7,10 +7,11 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do
include StubRequests include StubRequests
let(:teamcity_url) { 'http://gitlab.com/teamcity' } let(:teamcity_url) { 'http://gitlab.com/teamcity' }
let(:project) { create(:project) }
subject(:service) do subject(:service) do
described_class.create( described_class.create(
project: create(:project), project: project,
properties: { properties: {
teamcity_url: teamcity_url, teamcity_url: teamcity_url,
username: 'mic', username: 'mic',
...@@ -207,6 +208,97 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do ...@@ -207,6 +208,97 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do
end end
end end
describe '#execute' do
context 'when push' do
let(:data) do
{
object_kind: 'push',
ref: 'refs/heads/dev-123_branch',
after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
total_commits_count: 1
}
end
it 'handles push request correctly' do
stub_post_to_build_queue(branch: 'dev-123_branch')
expect(service.execute(data)).to include('Ok')
end
it 'returns nil when ref is blank' do
data[:after] = Gitlab::Git::BLANK_SHA
expect(service.execute(data)).to be_nil
end
it 'returns nil when there is no content' do
data[:total_commits_count] = 0
expect(service.execute(data)).to be_nil
end
it 'returns nil when a merge request is opened for the same ref' do
create(:merge_request, source_project: project, source_branch: 'dev-123_branch')
expect(service.execute(data)).to be_nil
end
end
context 'when merge_request' do
let(:data) do
{
object_kind: 'merge_request',
ref: 'refs/heads/dev-123_branch',
after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
total_commits_count: 1,
object_attributes: {
state: 'opened',
source_branch: 'dev-123_branch',
merge_status: 'unchecked'
}
}
end
it 'handles merge request correctly' do
stub_post_to_build_queue(branch: 'dev-123_branch')
expect(service.execute(data)).to include('Ok')
end
it 'returns nil when merge request is not opened' do
data[:object_attributes][:state] = 'closed'
expect(service.execute(data)).to be_nil
end
it 'returns nil unless merge request is marked as unchecked' do
data[:object_attributes][:merge_status] = 'can_be_merged'
expect(service.execute(data)).to be_nil
end
end
it 'returns nil when event is not supported' do
data = { object_kind: 'foo' }
expect(service.execute(data)).to be_nil
end
end
def stub_post_to_build_queue(branch:)
teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/buildQueue'
body ||= %Q(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>)
auth = %w(mic password)
stub_full_request(teamcity_full_url, method: :post).with(
basic_auth: auth,
body: body,
headers: {
'Content-Type' => 'application/xml'
}
).to_return(status: 200, body: 'Ok', headers: {})
end
def stub_request(status: 200, body: nil, build_status: 'success') def stub_request(status: 200, body: nil, build_status: 'success')
teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123'
auth = %w(mic password) auth = %w(mic password)
......
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