Commit 6f532ed2 authored by Arturo Herrero's avatar Arturo Herrero Committed by Bob Van Landuyt

Validates only one service template per type

This enforces that there is only one service template per type.
- Delete all the duplicated service template by type keeping the
  template with the lowest `id` because that is the service template
  used: https://gitlab.com/gitlab-org/gitlab/-/blob/v12.8.1-ee/app/controllers/admin/services_controller.rb#L37
- Add Rails validation
- Add unique index having database validation
parent f96750dd
...@@ -34,6 +34,7 @@ class Service < ApplicationRecord ...@@ -34,6 +34,7 @@ class Service < ApplicationRecord
validates :project_id, presence: true, unless: -> { template? } validates :project_id, presence: true, unless: -> { template? }
validates :type, presence: true validates :type, presence: true
validates :template, uniqueness: { scope: :type }, if: -> { template? }
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
scope :issue_trackers, -> { where(category: 'issue_tracker') } scope :issue_trackers, -> { where(category: 'issue_tracker') }
......
---
title: Validates only one service template per type
merge_request: 26380
author:
type: other
# frozen_string_literal: true
class DeleteTemplateServicesDuplicatedByType < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
# Delete service templates with duplicated types. Keep the service
# template with the lowest `id` because that is the service template used:
# https://gitlab.com/gitlab-org/gitlab/-/blob/v12.8.1-ee/app/controllers/admin/services_controller.rb#L37
execute <<~SQL
DELETE
FROM services
WHERE TEMPLATE = TRUE
AND id NOT IN
(SELECT MIN(id)
FROM services
WHERE TEMPLATE = TRUE
GROUP BY TYPE);
SQL
end
def down
# This migration cannot be reversed.
end
end
# frozen_string_literal: true
class AddIndexToServiceUniqueTemplatePerType < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(:services, [:type, :template], unique: true, where: 'template IS TRUE')
end
def down
remove_concurrent_index(:services, [:type, :template])
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_03_04_090155) do ActiveRecord::Schema.define(version: 2020_03_04_160823) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -3898,6 +3898,7 @@ ActiveRecord::Schema.define(version: 2020_03_04_090155) do ...@@ -3898,6 +3898,7 @@ ActiveRecord::Schema.define(version: 2020_03_04_090155) do
t.boolean "template", default: false t.boolean "template", default: false
t.index ["project_id"], name: "index_services_on_project_id" t.index ["project_id"], name: "index_services_on_project_id"
t.index ["template"], name: "index_services_on_template" t.index ["template"], name: "index_services_on_template"
t.index ["type", "template"], name: "index_services_on_type_and_template", unique: true, where: "(template IS TRUE)"
t.index ["type"], name: "index_services_on_type" t.index ["type"], name: "index_services_on_type"
end end
......
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200304160801_delete_template_services_duplicated_by_type.rb')
describe DeleteTemplateServicesDuplicatedByType, :migration do
let(:services) { table(:services) }
before do
services.create!(template: true, type: 'JenkinsService')
services.create!(template: true, type: 'JenkinsService')
services.create!(template: true, type: 'JiraService')
services.create!(template: true, type: 'JenkinsService')
end
it 'deletes service templates duplicated by type except the one with the lowest ID' do
jenkins_service_id = services.where(type: 'JenkinsService').order(:id).pluck(:id).first
jira_service_id = services.where(type: 'JiraService').pluck(:id).first
migrate!
expect(services.pluck(:id)).to contain_exactly(jenkins_service_id, jira_service_id)
end
end
...@@ -17,6 +17,16 @@ describe Service do ...@@ -17,6 +17,16 @@ describe Service do
expect(build(:service, project_id: nil, template: true)).to be_valid expect(build(:service, project_id: nil, template: true)).to be_valid
expect(build(:service, project_id: nil, template: false)).to be_invalid expect(build(:service, project_id: nil, template: false)).to be_invalid
end end
context 'with an existing service template' do
before do
create(:service, type: 'Service', template: true)
end
it 'validates only one service template per type' do
expect(build(:service, type: 'Service', template: true)).to be_invalid
end
end
end end
describe 'Scopes' do describe 'Scopes' do
......
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