Commit d0fb5fac authored by Thong Kuah's avatar Thong Kuah

Add management_project_id to clusters

This association will be used for a cluster to indicate which project is
used to manage it.

Validate against duplicate scope for same project. If multiple clusters
with the same scope points to the same management_project, it will be
impossible to deterministically select a cluster
parent 2b607f09
......@@ -24,6 +24,7 @@ module Clusters
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
belongs_to :user
belongs_to :management_project, class_name: '::Project', optional: true
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
......@@ -63,6 +64,7 @@ module Clusters
validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
validate :unique_management_project_environment_scope
after_save :clear_reactive_cache!
......@@ -200,6 +202,18 @@ module Clusters
private
def unique_management_project_environment_scope
return unless management_project
duplicate_management_clusters = management_project.management_clusters
.where(environment_scope: environment_scope)
.where.not(id: id)
if duplicate_management_clusters.any?
errors.add(:environment_scope, "cannot add duplicated environment scope")
end
end
def instance_domain
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end
......
......@@ -247,6 +247,7 @@ class Project < ApplicationRecord
has_one :cluster_project, class_name: 'Clusters::Project'
has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster'
has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace'
has_many :management_clusters, class_name: 'Clusters::Cluster', foreign_key: :management_project_id, inverse_of: :management_project
has_many :prometheus_metrics
......
---
title: Adds management project for a cluster
merge_request: 17866
author:
type: changed
# frozen_string_literal: true
class AddManagementProjectIdToClusters < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :clusters, :management_project_id, :integer
end
end
# frozen_string_literal: true
class AddManagementProjectIdIndexFkToClusters < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :clusters, :projects, column: :management_project_id, on_delete: :nullify
add_concurrent_index :clusters, :management_project_id, where: 'management_project_id IS NOT NULL'
end
def down
remove_concurrent_index :clusters, :management_project_id
remove_foreign_key_if_exists :clusters, column: :management_project_id
end
end
......@@ -991,7 +991,9 @@ ActiveRecord::Schema.define(version: 2019_10_04_134055) do
t.string "domain"
t.boolean "managed", default: true, null: false
t.boolean "namespace_per_environment", default: true, null: false
t.integer "management_project_id"
t.index ["enabled"], name: "index_clusters_on_enabled"
t.index ["management_project_id"], name: "index_clusters_on_management_project_id", where: "(management_project_id IS NOT NULL)"
t.index ["user_id"], name: "index_clusters_on_user_id"
end
......@@ -3983,6 +3985,7 @@ ActiveRecord::Schema.define(version: 2019_10_04_134055) do
add_foreign_key "cluster_projects", "clusters", on_delete: :cascade
add_foreign_key "cluster_projects", "projects", on_delete: :cascade
add_foreign_key "cluster_providers_gcp", "clusters", on_delete: :cascade
add_foreign_key "clusters", "projects", column: "management_project_id", name: "fk_f05c5e5a42", on_delete: :nullify
add_foreign_key "clusters", "users", on_delete: :nullify
add_foreign_key "clusters_applications_cert_managers", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
......
......@@ -30,6 +30,10 @@ FactoryBot.define do
end
end
trait :management_project do
management_project factory: :project
end
trait :namespace_per_environment_disabled do
namespace_per_environment { false }
end
......
......@@ -256,6 +256,7 @@ project:
- cycle_analytics_stages
- group
- namespace
- management_clusters
- boards
- last_event
- services
......
......@@ -11,6 +11,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
subject { build(:cluster) }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:management_project).class_name('::Project') }
it { is_expected.to have_many(:cluster_projects) }
it { is_expected.to have_many(:projects) }
it { is_expected.to have_many(:cluster_groups) }
......@@ -289,6 +290,20 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
it { is_expected.to be_valid }
end
end
describe 'unique scope for management_project' do
let(:project) { create(:project) }
let!(:cluster_with_management_project) { create(:cluster, management_project: project) }
context 'duplicate scopes for the same management project' do
let(:cluster) { build(:cluster, management_project: project) }
it 'adds an error on environment_scope' do
expect(cluster).not_to be_valid
expect(cluster.errors[:environment_scope].first).to eq('cannot add duplicated environment scope')
end
end
end
end
describe '.ancestor_clusters_for_clusterable' do
......
......@@ -92,6 +92,7 @@ describe Project do
it { is_expected.to have_many(:pipeline_schedules) }
it { is_expected.to have_many(:members_and_requesters) }
it { is_expected.to have_many(:clusters) }
it { is_expected.to have_many(:management_clusters).class_name('Clusters::Cluster') }
it { is_expected.to have_many(:kubernetes_namespaces) }
it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
......
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