Commit 014c5c85 authored by Markus Koller's avatar Markus Koller

Merge branch '335218-integration-service-hooks' into 'master'

Automatically create integration webhooks when missing [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!65577
parents c8c32481 c2259e5e
# frozen_string_literal: true # frozen_string_literal: true
class Projects::ServiceHookLogsController < Projects::HookLogsController class Projects::ServiceHookLogsController < Projects::HookLogsController
extend Gitlab::Utils::Override
before_action :integration, only: [:show, :retry] before_action :integration, only: [:show, :retry]
def retry def retry
...@@ -10,11 +12,12 @@ class Projects::ServiceHookLogsController < Projects::HookLogsController ...@@ -10,11 +12,12 @@ class Projects::ServiceHookLogsController < Projects::HookLogsController
private private
def hook
@hook ||= integration.service_hook
end
def integration def integration
@integration ||= @project.find_or_initialize_integration(params[:service_id]) @integration ||= @project.find_or_initialize_integration(params[:service_id])
end end
override :hook
def hook
@hook ||= integration.service_hook || not_found
end
end end
# frozen_string_literal: true
module Integrations
module HasWebHook
extend ActiveSupport::Concern
included do
after_save :update_web_hook!, if: :activated?
end
# Return the URL to be used for the webhook.
def hook_url
raise NotImplementedError
end
# Return whether the webhook should use SSL verification.
def hook_ssl_verification
true
end
# Create or update the webhook, raising an exception if it cannot be saved.
def update_web_hook!
hook = service_hook || build_service_hook
hook.url = hook_url if hook.url != hook_url # avoid reencryption
hook.enable_ssl_verification = hook_ssl_verification
hook.save! if hook.changed?
hook
end
# Execute the webhook, creating it if necessary.
def execute_web_hook!(*args)
update_web_hook!
service_hook.execute(*args)
end
end
end
...@@ -18,14 +18,8 @@ module Integrations ...@@ -18,14 +18,8 @@ module Integrations
attr_accessor :response attr_accessor :response
after_save :compose_service_hook, if: :activated?
before_update :reset_password before_update :reset_password
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def reset_password def reset_password
if bamboo_url_changed? && !password_touched? if bamboo_url_changed? && !password_touched?
self.password = nil self.password = nil
......
...@@ -4,7 +4,9 @@ require "addressable/uri" ...@@ -4,7 +4,9 @@ require "addressable/uri"
module Integrations module Integrations
class Buildkite < BaseCi class Buildkite < BaseCi
include HasWebHook
include ReactiveService include ReactiveService
extend Gitlab::Utils::Override
ENDPOINT = "https://buildkite.com" ENDPOINT = "https://buildkite.com"
...@@ -13,8 +15,6 @@ module Integrations ...@@ -13,8 +15,6 @@ module Integrations
validates :project_url, presence: true, public_url: true, if: :activated? validates :project_url, presence: true, public_url: true, if: :activated?
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
def self.supported_events def self.supported_events
%w(push merge_request tag_push) %w(push merge_request tag_push)
end end
...@@ -35,21 +35,15 @@ module Integrations ...@@ -35,21 +35,15 @@ module Integrations
self.properties.delete('enable_ssl_verification') # Remove unused key self.properties.delete('enable_ssl_verification') # Remove unused key
end end
def webhook_url override :hook_url
def hook_url
"#{buildkite_endpoint('webhook')}/deliver/#{webhook_token}" "#{buildkite_endpoint('webhook')}/deliver/#{webhook_token}"
end end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = webhook_url
hook.enable_ssl_verification = true
hook.save
end
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
service_hook.execute(data) execute_web_hook!(data)
end end
def commit_status(sha, ref) def commit_status(sha, ref)
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
module Integrations module Integrations
class Datadog < Integration class Datadog < Integration
include HasWebHook
extend Gitlab::Utils::Override
DEFAULT_DOMAIN = 'datadoghq.com' DEFAULT_DOMAIN = 'datadoghq.com'
URL_TEMPLATE = 'https://webhooks-http-intake.logs.%{datadog_domain}/api/v2/webhook' URL_TEMPLATE = 'https://webhooks-http-intake.logs.%{datadog_domain}/api/v2/webhook'
URL_TEMPLATE_API_KEYS = 'https://app.%{datadog_domain}/account/settings#api' URL_TEMPLATE_API_KEYS = 'https://app.%{datadog_domain}/account/settings#api'
...@@ -21,8 +24,6 @@ module Integrations ...@@ -21,8 +24,6 @@ module Integrations
validates :api_url, presence: true, unless: -> (obj) { obj.datadog_site.present? } validates :api_url, presence: true, unless: -> (obj) { obj.datadog_site.present? }
end end
after_save :compose_service_hook, if: :activated?
def initialize_properties def initialize_properties
super super
...@@ -98,12 +99,7 @@ module Integrations ...@@ -98,12 +99,7 @@ module Integrations
] ]
end end
def compose_service_hook override :hook_url
hook = service_hook || build_service_hook
hook.url = hook_url
hook.save
end
def hook_url def hook_url
url = api_url.presence || sprintf(URL_TEMPLATE, datadog_domain: datadog_domain) url = api_url.presence || sprintf(URL_TEMPLATE, datadog_domain: datadog_domain)
url = URI.parse(url) url = URI.parse(url)
...@@ -127,7 +123,7 @@ module Integrations ...@@ -127,7 +123,7 @@ module Integrations
object_kind = 'job' if object_kind == 'build' object_kind = 'job' if object_kind == 'build'
return unless supported_events.include?(object_kind) return unless supported_events.include?(object_kind)
service_hook.execute(data, "#{object_kind} hook") execute_web_hook!(data, "#{object_kind} hook")
end end
def test(data) def test(data)
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
module Integrations module Integrations
class DroneCi < BaseCi class DroneCi < BaseCi
include HasWebHook
include ReactiveService include ReactiveService
include ServicePushDataValidations include ServicePushDataValidations
extend Gitlab::Utils::Override
prop_accessor :drone_url, :token prop_accessor :drone_url, :token
boolean_accessor :enable_ssl_verification boolean_accessor :enable_ssl_verification
...@@ -11,24 +13,16 @@ module Integrations ...@@ -11,24 +13,16 @@ module Integrations
validates :drone_url, presence: true, public_url: true, if: :activated? validates :drone_url, presence: true, public_url: true, if: :activated?
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
# If using a service template, project may not be available
hook.url = [drone_url, "/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
hook.enable_ssl_verification = !!enable_ssl_verification
hook.save
end
def execute(data) def execute(data)
return unless project
case data[:object_kind] case data[:object_kind]
when 'push' when 'push'
service_hook.execute(data) if push_valid?(data) execute_web_hook!(data) if push_valid?(data)
when 'merge_request' when 'merge_request'
service_hook.execute(data) if merge_request_valid?(data) execute_web_hook!(data) if merge_request_valid?(data)
when 'tag_push' when 'tag_push'
service_hook.execute(data) if tag_push_valid?(data) execute_web_hook!(data) if tag_push_valid?(data)
end end
end end
...@@ -105,5 +99,21 @@ module Integrations ...@@ -105,5 +99,21 @@ module Integrations
{ type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
] ]
end end
override :hook_url
def hook_url
[drone_url, "/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join
end
override :hook_ssl_verification
def hook_ssl_verification
!!enable_ssl_verification
end
override :update_web_hook!
def update_web_hook!
# If using a service template, project may not be available
super if project
end
end end
end end
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
module Integrations module Integrations
class Jenkins < BaseCi class Jenkins < BaseCi
include HasWebHook
include ActionView::Helpers::UrlHelper include ActionView::Helpers::UrlHelper
extend Gitlab::Utils::Override
prop_accessor :jenkins_url, :project_name, :username, :password prop_accessor :jenkins_url, :project_name, :username, :password
...@@ -16,8 +18,6 @@ module Integrations ...@@ -16,8 +18,6 @@ module Integrations
default_value_for :merge_requests_events, false default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false default_value_for :tag_push_events, false
after_save :compose_service_hook, if: :activated?
def reset_password def reset_password
# don't reset the password if a new one is provided # don't reset the password if a new one is provided
if (jenkins_url_changed? || username.blank?) && !password_touched? if (jenkins_url_changed? || username.blank?) && !password_touched?
...@@ -25,16 +25,10 @@ module Integrations ...@@ -25,16 +25,10 @@ module Integrations
end end
end end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = hook_url
hook.save
end
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
service_hook.execute(data, "#{data[:object_kind]}_hook") execute_web_hook!(data, "#{data[:object_kind]}_hook")
end end
def test(data) def test(data)
...@@ -48,6 +42,7 @@ module Integrations ...@@ -48,6 +42,7 @@ module Integrations
{ success: true, result: result[:message] } { success: true, result: result[:message] }
end end
override :hook_url
def hook_url def hook_url
url = URI.parse(jenkins_url) url = URI.parse(jenkins_url)
url.path = File.join(url.path || '/', "project/#{project_name}") url.path = File.join(url.path || '/', "project/#{project_name}")
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
module Integrations module Integrations
class Packagist < Integration class Packagist < Integration
include HasWebHook
extend Gitlab::Utils::Override
prop_accessor :username, :token, :server prop_accessor :username, :token, :server
validates :username, presence: true, if: :activated? validates :username, presence: true, if: :activated?
...@@ -10,8 +13,6 @@ module Integrations ...@@ -10,8 +13,6 @@ module Integrations
default_value_for :push_events, true default_value_for :push_events, true
default_value_for :tag_push_events, true default_value_for :tag_push_events, true
after_save :compose_service_hook, if: :activated?
def title def title
'Packagist' 'Packagist'
end end
...@@ -39,7 +40,7 @@ module Integrations ...@@ -39,7 +40,7 @@ module Integrations
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
service_hook.execute(data) execute_web_hook!(data)
end end
def test(data) def test(data)
...@@ -53,12 +54,7 @@ module Integrations ...@@ -53,12 +54,7 @@ module Integrations
{ success: true, result: result[:message] } { success: true, result: result[:message] }
end end
def compose_service_hook override :hook_url
hook = service_hook || build_service_hook
hook.url = hook_url
hook.save
end
def hook_url def hook_url
base_url = server.presence || 'https://packagist.org' base_url = server.presence || 'https://packagist.org'
"#{base_url}/api/update-package?username=#{username}&apiToken=#{token}" "#{base_url}/api/update-package?username=#{username}&apiToken=#{token}"
......
...@@ -18,7 +18,6 @@ module Integrations ...@@ -18,7 +18,6 @@ module Integrations
attr_accessor :response attr_accessor :response
after_save :compose_service_hook, if: :activated?
before_update :reset_password before_update :reset_password
class << self class << self
...@@ -31,11 +30,6 @@ module Integrations ...@@ -31,11 +30,6 @@ module Integrations
end end
end end
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def reset_password def reset_password
if teamcity_url_changed? && !password_touched? if teamcity_url_changed? && !password_touched?
self.password = nil self.password = nil
......
...@@ -15,6 +15,6 @@ class ProjectServiceWorker # rubocop:disable Scalability/IdempotentWorker ...@@ -15,6 +15,6 @@ class ProjectServiceWorker # rubocop:disable Scalability/IdempotentWorker
integration.execute(data) integration.execute(data)
rescue StandardError => error rescue StandardError => error
integration_class = integration&.class&.name || "Not Found" integration_class = integration&.class&.name || "Not Found"
logger.error class: self.class.name, service_class: integration_class, message: error.message Gitlab::ErrorTracking.log_exception(error, integration_class: integration_class)
end end
end end
...@@ -27,6 +27,15 @@ RSpec.describe Projects::ServiceHookLogsController do ...@@ -27,6 +27,15 @@ RSpec.describe Projects::ServiceHookLogsController do
specify do specify do
expect(response).to be_successful expect(response).to be_successful
end end
it 'renders a 404 if the hook does not exist' do
log_params
integration.service_hook.destroy!
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end end
describe 'POST #retry' do describe 'POST #retry' do
...@@ -37,5 +46,14 @@ RSpec.describe Projects::ServiceHookLogsController do ...@@ -37,5 +46,14 @@ RSpec.describe Projects::ServiceHookLogsController do
expect_any_instance_of(described_class).to receive(:set_hook_execution_notice) expect_any_instance_of(described_class).to receive(:set_hook_execution_notice)
expect(subject).to redirect_to(edit_project_service_path(project, integration)) expect(subject).to redirect_to(edit_project_service_path(project, integration))
end end
it 'renders a 404 if the hook does not exist' do
log_params
integration.service_hook.destroy!
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end end
end end
...@@ -9,11 +9,12 @@ RSpec.describe Integration do ...@@ -9,11 +9,12 @@ RSpec.describe Integration do
let_it_be(:project) { create(:project, group: group) } let_it_be(:project) { create(:project, group: group) }
describe "Associations" do describe "Associations" do
it { is_expected.to belong_to :project } it { is_expected.to belong_to(:project).inverse_of(:integrations) }
it { is_expected.to belong_to :group } it { is_expected.to belong_to(:group).inverse_of(:integrations) }
it { is_expected.to have_one :service_hook } it { is_expected.to have_one(:service_hook).inverse_of(:integration).with_foreign_key(:service_id) }
it { is_expected.to have_one :jira_tracker_data } it { is_expected.to have_one(:issue_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::IssueTrackerData') }
it { is_expected.to have_one :issue_tracker_data } it { is_expected.to have_one(:jira_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::JiraTrackerData') }
it { is_expected.to have_one(:open_project_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::OpenProjectTrackerData') }
end end
describe 'validations' do describe 'validations' do
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::Asana do RSpec.describe Integrations::Asana do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'active' do context 'active' do
before do before do
...@@ -42,7 +37,6 @@ RSpec.describe Integrations::Asana do ...@@ -42,7 +37,6 @@ RSpec.describe Integrations::Asana do
allow(@asana).to receive_messages( allow(@asana).to receive_messages(
project: project, project: project,
project_id: project.id, project_id: project.id,
service_hook: true,
api_key: 'verySecret', api_key: 'verySecret',
restrict_to_branch: 'master' restrict_to_branch: 'master'
) )
......
...@@ -5,11 +5,6 @@ require 'spec_helper' ...@@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::Assembla do RSpec.describe Integrations::Assembla do
include StubRequests include StubRequests
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe "Execute" do describe "Execute" do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
...@@ -19,7 +14,6 @@ RSpec.describe Integrations::Assembla do ...@@ -19,7 +14,6 @@ RSpec.describe Integrations::Assembla do
allow(@assembla_integration).to receive_messages( allow(@assembla_integration).to receive_messages(
project_id: project.id, project_id: project.id,
project: project, project: project,
service_hook: true,
token: 'verySecret', token: 'verySecret',
subdomain: 'project_name' subdomain: 'project_name'
) )
......
...@@ -22,11 +22,6 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do ...@@ -22,11 +22,6 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do
) )
end end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when active' do context 'when active' do
before do before do
......
...@@ -28,7 +28,6 @@ RSpec.describe Integrations::BaseChatNotification do ...@@ -28,7 +28,6 @@ RSpec.describe Integrations::BaseChatNotification do
allow(chat_integration).to receive_messages( allow(chat_integration).to receive_messages(
project: project, project: project,
project_id: project.id, project_id: project.id,
service_hook: true,
webhook: webhook_url webhook: webhook_url
) )
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::Bugzilla do RSpec.describe Integrations::Bugzilla do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
......
...@@ -12,16 +12,14 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do ...@@ -12,16 +12,14 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do
described_class.create!( described_class.create!(
project: project, project: project,
properties: { properties: {
service_hook: true,
project_url: 'https://buildkite.com/organization-name/example-pipeline', project_url: 'https://buildkite.com/organization-name/example-pipeline',
token: 'secret-sauce-webhook-token:secret-sauce-status-token' token: 'secret-sauce-webhook-token:secret-sauce-status-token'
} }
) )
end end
describe 'Associations' do it_behaves_like Integrations::HasWebHook do
it { is_expected.to belong_to :project } let(:hook_url) { 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' }
it { is_expected.to have_one :service_hook }
end end
describe 'Validations' do describe 'Validations' do
...@@ -66,9 +64,9 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do ...@@ -66,9 +64,9 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do
.to change { integration.service_hook.enable_ssl_verification }.from(false).to(true) .to change { integration.service_hook.enable_ssl_verification }.from(false).to(true)
end end
describe '#webhook_url' do describe '#hook_url' do
it 'returns the webhook url' do it 'returns the webhook url' do
expect(integration.webhook_url).to eq( expect(integration.hook_url).to eq(
'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token'
) )
end end
......
...@@ -5,11 +5,6 @@ require 'spec_helper' ...@@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::Campfire do RSpec.describe Integrations::Campfire do
include StubRequests include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
...@@ -37,7 +32,6 @@ RSpec.describe Integrations::Campfire do ...@@ -37,7 +32,6 @@ RSpec.describe Integrations::Campfire do
allow(@campfire_integration).to receive_messages( allow(@campfire_integration).to receive_messages(
project_id: project.id, project_id: project.id,
project: project, project: project,
service_hook: true,
token: 'verySecret', token: 'verySecret',
subdomain: 'project-name', subdomain: 'project-name',
room: 'test-room' room: 'test-room'
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::Confluence do RSpec.describe Integrations::Confluence do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
before do before do
subject.active = active subject.active = active
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::CustomIssueTracker do RSpec.describe Integrations::CustomIssueTracker do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
......
...@@ -38,9 +38,9 @@ RSpec.describe Integrations::Datadog do ...@@ -38,9 +38,9 @@ RSpec.describe Integrations::Datadog do
let(:pipeline_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } let(:pipeline_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
let(:build_data) { Gitlab::DataBuilder::Build.build(build) } let(:build_data) { Gitlab::DataBuilder::Build.build(build) }
describe 'associations' do it_behaves_like Integrations::HasWebHook do
it { is_expected.to belong_to(:project) } let(:integration) { instance }
it { is_expected.to have_one(:service_hook) } let(:hook_url) { "#{described_class::URL_TEMPLATE % { datadog_domain: dd_site }}?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}" }
end end
describe 'validations' do describe 'validations' do
......
...@@ -35,7 +35,6 @@ RSpec.describe Integrations::Discord do ...@@ -35,7 +35,6 @@ RSpec.describe Integrations::Discord do
allow(subject).to receive_messages( allow(subject).to receive_messages(
project: project, project: project,
project_id: project.id, project_id: project.id,
service_hook: true,
webhook: webhook_url webhook: webhook_url
) )
......
...@@ -5,11 +5,6 @@ require 'spec_helper' ...@@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers include ReactiveCachingHelpers
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:service_hook) }
end
describe 'validations' do describe 'validations' do
context 'active' do context 'active' do
before do before do
...@@ -32,7 +27,15 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do ...@@ -32,7 +27,15 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
end end
shared_context :drone_ci_integration do shared_context :drone_ci_integration do
let(:drone) { described_class.new } subject(:drone) do
described_class.new(
project: project,
active: true,
drone_url: drone_url,
token: token
)
end
let(:project) { create(:project, :repository, name: 'project') } let(:project) { create(:project, :repository, name: 'project') }
let(:path) { project.full_path } let(:path) { project.full_path }
let(:drone_url) { 'http://drone.example.com' } let(:drone_url) { 'http://drone.example.com' }
...@@ -45,16 +48,6 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do ...@@ -45,16 +48,6 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" } let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" } let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
before do
allow(drone).to receive_messages(
project_id: project.id,
project: project,
active: true,
drone_url: drone_url,
token: token
)
end
def stub_request(status: 200, body: nil) def stub_request(status: 200, body: nil)
body ||= %q({"status":"success"}) body ||= %q({"status":"success"})
...@@ -66,6 +59,20 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do ...@@ -66,6 +59,20 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
end end
end end
it_behaves_like Integrations::HasWebHook do
include_context :drone_ci_integration
let(:integration) { drone }
let(:hook_url) { "#{drone_url}/hook?owner=#{project.namespace.full_path}&name=#{project.path}&access_token=#{token}" }
it 'does not create a hook if project is not present' do
integration.project = nil
integration.instance = true
expect { integration.save! }.not_to change(ServiceHook, :count)
end
end
describe "integration page/path methods" do describe "integration page/path methods" do
include_context :drone_ci_integration include_context :drone_ci_integration
...@@ -137,10 +144,17 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do ...@@ -137,10 +144,17 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
Gitlab::DataBuilder::Push.build_sample(project, user) Gitlab::DataBuilder::Push.build_sample(project, user)
end end
it do it 'executes the webhook' do
service_hook = double expect(drone).to receive(:execute_web_hook!).with(push_sample_data)
expect(service_hook).to receive(:execute)
expect(drone).to receive(:service_hook).and_return(service_hook) drone.execute(push_sample_data)
end
it 'does not try to execute the webhook if the integration is not in a project' do
drone.project = nil
drone.instance = true
expect(drone).not_to receive(:execute_web_hook!)
drone.execute(push_sample_data) drone.execute(push_sample_data)
end end
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::Ewm do RSpec.describe Integrations::Ewm do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::ExternalWiki do RSpec.describe Integrations::ExternalWiki do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::Flowdock do RSpec.describe Integrations::Flowdock do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
...@@ -38,7 +33,6 @@ RSpec.describe Integrations::Flowdock do ...@@ -38,7 +33,6 @@ RSpec.describe Integrations::Flowdock do
allow(flowdock_integration).to receive_messages( allow(flowdock_integration).to receive_messages(
project_id: project.id, project_id: project.id,
project: project, project: project,
service_hook: true,
token: 'verySecret' token: 'verySecret'
) )
WebMock.stub_request(:post, api_url) WebMock.stub_request(:post, api_url)
......
...@@ -5,11 +5,6 @@ require 'socket' ...@@ -5,11 +5,6 @@ require 'socket'
require 'json' require 'json'
RSpec.describe Integrations::Irker do RSpec.describe Integrations::Irker do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
...@@ -46,7 +41,6 @@ RSpec.describe Integrations::Irker do ...@@ -46,7 +41,6 @@ RSpec.describe Integrations::Irker do
active: true, active: true,
project: project, project: project,
project_id: project.id, project_id: project.id,
service_hook: true,
server_host: @irker_server.addr[2], server_host: @irker_server.addr[2],
server_port: @irker_server.addr[1], server_port: @irker_server.addr[1],
default_irc_uri: 'irc://chat.freenode.net/', default_irc_uri: 'irc://chat.freenode.net/',
......
...@@ -24,9 +24,9 @@ RSpec.describe Integrations::Jenkins do ...@@ -24,9 +24,9 @@ RSpec.describe Integrations::Jenkins do
let(:jenkins_authorization) { "Basic " + ::Base64.strict_encode64(jenkins_username + ':' + jenkins_password) } let(:jenkins_authorization) { "Basic " + ::Base64.strict_encode64(jenkins_username + ':' + jenkins_password) }
describe 'Associations' do it_behaves_like Integrations::HasWebHook do
it { is_expected.to belong_to :project } let(:integration) { described_class.new(jenkins_params) }
it { is_expected.to have_one :service_hook } let(:hook_url) { "http://#{ERB::Util.url_encode jenkins_username}:#{ERB::Util.url_encode jenkins_password}@jenkins.example.com/project/my_project" }
end end
describe 'username validation' do describe 'username validation' do
......
...@@ -109,11 +109,6 @@ RSpec.describe Integrations::Jira do ...@@ -109,11 +109,6 @@ RSpec.describe Integrations::Jira do
end end
end end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe '.reference_pattern' do describe '.reference_pattern' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
......
...@@ -6,11 +6,6 @@ RSpec.describe Integrations::MicrosoftTeams do ...@@ -6,11 +6,6 @@ RSpec.describe Integrations::MicrosoftTeams do
let(:chat_integration) { described_class.new } let(:chat_integration) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' } let(:webhook_url) { 'https://example.gitlab.com/' }
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
...@@ -45,7 +40,6 @@ RSpec.describe Integrations::MicrosoftTeams do ...@@ -45,7 +40,6 @@ RSpec.describe Integrations::MicrosoftTeams do
allow(chat_integration).to receive_messages( allow(chat_integration).to receive_messages(
project: project, project: project,
project_id: project.id, project_id: project.id,
service_hook: true,
webhook: webhook_url webhook: webhook_url
) )
...@@ -142,7 +136,6 @@ RSpec.describe Integrations::MicrosoftTeams do ...@@ -142,7 +136,6 @@ RSpec.describe Integrations::MicrosoftTeams do
allow(chat_integration).to receive_messages( allow(chat_integration).to receive_messages(
project: project, project: project,
project_id: project.id, project_id: project.id,
service_hook: true,
webhook: webhook_url webhook: webhook_url
) )
...@@ -224,7 +217,6 @@ RSpec.describe Integrations::MicrosoftTeams do ...@@ -224,7 +217,6 @@ RSpec.describe Integrations::MicrosoftTeams do
before do before do
allow(chat_integration).to receive_messages( allow(chat_integration).to receive_messages(
project: project, project: project,
service_hook: true,
webhook: webhook_url webhook: webhook_url
) )
end end
......
...@@ -27,9 +27,4 @@ RSpec.describe Integrations::OpenProject do ...@@ -27,9 +27,4 @@ RSpec.describe Integrations::OpenProject do
it { is_expected.not_to validate_presence_of(:project_identifier_code) } it { is_expected.not_to validate_presence_of(:project_identifier_code) }
end end
end end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
end end
...@@ -24,9 +24,9 @@ RSpec.describe Integrations::Packagist do ...@@ -24,9 +24,9 @@ RSpec.describe Integrations::Packagist do
let(:packagist_server) { 'https://packagist.example.com' } let(:packagist_server) { 'https://packagist.example.com' }
let(:project) { create(:project) } let(:project) { create(:project) }
describe "Associations" do it_behaves_like Integrations::HasWebHook do
it { is_expected.to belong_to :project } let(:integration) { described_class.new(packagist_params) }
it { is_expected.to have_one :service_hook } let(:hook_url) { "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" }
end end
describe '#execute' do describe '#execute' do
......
...@@ -5,11 +5,6 @@ require 'spec_helper' ...@@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::Pivotaltracker do RSpec.describe Integrations::Pivotaltracker do
include StubRequests include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
......
...@@ -12,10 +12,6 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching, ...@@ -12,10 +12,6 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
let(:integration) { project.prometheus_integration } let(:integration) { project.prometheus_integration }
describe "Associations" do
it { is_expected.to belong_to :project }
end
context 'redirects' do context 'redirects' do
it 'does not follow redirects' do it 'does not follow redirects' do
redirect_to = 'https://redirected.example.com' redirect_to = 'https://redirected.example.com'
......
...@@ -5,11 +5,6 @@ require 'spec_helper' ...@@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::Pushover do RSpec.describe Integrations::Pushover do
include StubRequests include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
...@@ -51,7 +46,6 @@ RSpec.describe Integrations::Pushover do ...@@ -51,7 +46,6 @@ RSpec.describe Integrations::Pushover do
allow(pushover).to receive_messages( allow(pushover).to receive_messages(
project: project, project: project,
project_id: project.id, project_id: project.id,
service_hook: true,
api_key: api_key, api_key: api_key,
user_key: user_key, user_key: user_key,
device: device, device: device,
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::Redmine do RSpec.describe Integrations::Redmine do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
# if redmine is set in setting the urls are set to defaults # if redmine is set in setting the urls are set to defaults
# therefore the validation passes as the values are not nil # therefore the validation passes as the values are not nil
......
...@@ -22,11 +22,6 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do ...@@ -22,11 +22,6 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
) )
end end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::Youtrack do RSpec.describe Integrations::Youtrack do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do describe 'Validations' do
context 'when integration is active' do context 'when integration is active' do
before do before do
......
...@@ -18,6 +18,8 @@ Integration.available_integration_names.each do |service| ...@@ -18,6 +18,8 @@ Integration.available_integration_names.each do |service|
hash.merge!(k => 'https://example.atlassian.net/wiki') hash.merge!(k => 'https://example.atlassian.net/wiki')
elsif service == 'datadog' && k == :datadog_site elsif service == 'datadog' && k == :datadog_site
hash.merge!(k => 'datadoghq.com') hash.merge!(k => 'datadoghq.com')
elsif service == 'packagist' && k == :server
hash.merge!(k => 'https://packagist.example.com')
elsif k =~ /^(.*_url|url|webhook)/ elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com") hash.merge!(k => "http://example.com")
elsif service_klass.method_defined?("#{k}?") elsif service_klass.method_defined?("#{k}?")
......
# frozen_string_literal: true
RSpec.shared_examples Integrations::HasWebHook do
include AfterNextHelpers
describe 'callbacks' do
it 'calls #update_web_hook! when enabled' do
expect(integration).to receive(:update_web_hook!)
integration.active = true
integration.save!
end
it 'does not call #update_web_hook! when disabled' do
expect(integration).not_to receive(:update_web_hook!)
integration.active = false
integration.save!
end
it 'does not call #update_web_hook! when validation fails' do
expect(integration).not_to receive(:update_web_hook!)
integration.active = true
integration.project = nil
expect(integration.save).to be(false)
end
end
describe '#hook_url' do
it 'returns a string' do
expect(integration.hook_url).to be_a(String)
end
end
describe '#hook_ssl_verification' do
it 'returns a boolean' do
expect(integration.hook_ssl_verification).to be_in([true, false])
end
end
describe '#update_web_hook!' do
def call
integration.update_web_hook!
end
it 'creates or updates a service hook' do
expect { call }.to change(ServiceHook, :count).by(1)
expect(integration.service_hook.url).to eq(hook_url)
integration.service_hook.update!(url: 'http://other.com')
expect { call }.to change { integration.service_hook.reload.url }.from('http://other.com').to(hook_url)
end
it 'raises an error if the service hook could not be saved' do
call
integration.service_hook.integration = nil
expect { call }.to raise_error(ActiveRecord::RecordInvalid)
end
it 'does not attempt to save the service hook if there are no changes' do
call
expect(integration.service_hook).not_to receive(:save!)
call
end
end
describe '#execute_web_hook!' do
let(:args) { ['foo', [1, 2, 3]] }
def call
integration.execute_web_hook!(*args)
end
it 'creates the webhook if necessary and executes it' do
expect_next(ServiceHook).to receive(:execute).with(*args)
expect { call }.to change(ServiceHook, :count).by(1)
expect(integration.service_hook).to receive(:execute).with(*args)
expect { call }.not_to change(ServiceHook, :count)
end
it 'raises an error if the service hook could not be saved' do
expect_next(ServiceHook).to receive(:execute).with(*args)
call
integration.service_hook.integration = nil
expect(integration.service_hook).not_to receive(:execute)
expect { call }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end
...@@ -3,22 +3,24 @@ require 'spec_helper' ...@@ -3,22 +3,24 @@ require 'spec_helper'
RSpec.describe ProjectServiceWorker, '#perform' do RSpec.describe ProjectServiceWorker, '#perform' do
let(:worker) { described_class.new } let(:worker) { described_class.new }
let(:service) { Integrations::Jira.new } let(:integration) { Integrations::Jira.new }
before do before do
allow(Integration).to receive(:find).and_return(service) allow(Integration).to receive(:find).and_return(integration)
end end
it 'executes service with given data' do it 'executes integration with given data' do
data = { test: 'test' } data = { test: 'test' }
expect(service).to receive(:execute).with(data) expect(integration).to receive(:execute).with(data)
worker.perform(1, data) worker.perform(1, data)
end end
it 'logs error messages' do it 'logs error messages' do
allow(service).to receive(:execute).and_raise(StandardError, 'invalid URL') error = StandardError.new('invalid URL')
expect(Sidekiq.logger).to receive(:error).with({ class: described_class.name, service_class: service.class.name, message: "invalid URL" }) allow(integration).to receive(:execute).and_raise(error)
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(error, integration_class: 'Integrations::Jira')
worker.perform(1, {}) worker.perform(1, {})
end end
......
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