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 ...@@ -24,6 +24,7 @@ module Clusters
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN' KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
belongs_to :user belongs_to :user
belongs_to :management_project, class_name: '::Project', optional: true
has_many :cluster_projects, class_name: 'Clusters::Project' has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project' has_many :projects, through: :cluster_projects, class_name: '::Project'
...@@ -63,6 +64,7 @@ module Clusters ...@@ -63,6 +64,7 @@ module Clusters
validate :restrict_modification, on: :update validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type? validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type? validate :no_projects, unless: :project_type?
validate :unique_management_project_environment_scope
after_save :clear_reactive_cache! after_save :clear_reactive_cache!
...@@ -200,6 +202,18 @@ module Clusters ...@@ -200,6 +202,18 @@ module Clusters
private 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 def instance_domain
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain @instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end end
......
...@@ -247,6 +247,7 @@ class Project < ApplicationRecord ...@@ -247,6 +247,7 @@ class Project < ApplicationRecord
has_one :cluster_project, class_name: 'Clusters::Project' has_one :cluster_project, class_name: 'Clusters::Project'
has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster' has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster'
has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace' 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 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 ...@@ -991,7 +991,9 @@ ActiveRecord::Schema.define(version: 2019_10_04_134055) do
t.string "domain" t.string "domain"
t.boolean "managed", default: true, null: false t.boolean "managed", default: true, null: false
t.boolean "namespace_per_environment", 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 ["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" t.index ["user_id"], name: "index_clusters_on_user_id"
end end
...@@ -3983,6 +3985,7 @@ ActiveRecord::Schema.define(version: 2019_10_04_134055) do ...@@ -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", "clusters", on_delete: :cascade
add_foreign_key "cluster_projects", "projects", 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 "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", "users", on_delete: :nullify
add_foreign_key "clusters_applications_cert_managers", "clusters", on_delete: :cascade add_foreign_key "clusters_applications_cert_managers", "clusters", on_delete: :cascade
add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade add_foreign_key "clusters_applications_helm", "clusters", on_delete: :cascade
......
...@@ -30,6 +30,10 @@ FactoryBot.define do ...@@ -30,6 +30,10 @@ FactoryBot.define do
end end
end end
trait :management_project do
management_project factory: :project
end
trait :namespace_per_environment_disabled do trait :namespace_per_environment_disabled do
namespace_per_environment { false } namespace_per_environment { false }
end end
......
...@@ -256,6 +256,7 @@ project: ...@@ -256,6 +256,7 @@ project:
- cycle_analytics_stages - cycle_analytics_stages
- group - group
- namespace - namespace
- management_clusters
- boards - boards
- last_event - last_event
- services - services
......
...@@ -11,6 +11,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -11,6 +11,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
subject { build(:cluster) } subject { build(:cluster) }
it { is_expected.to belong_to(:user) } 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(:cluster_projects) }
it { is_expected.to have_many(:projects) } it { is_expected.to have_many(:projects) }
it { is_expected.to have_many(:cluster_groups) } it { is_expected.to have_many(:cluster_groups) }
...@@ -289,6 +290,20 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -289,6 +290,20 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
it { is_expected.to be_valid } it { is_expected.to be_valid }
end end
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 end
describe '.ancestor_clusters_for_clusterable' do describe '.ancestor_clusters_for_clusterable' do
......
...@@ -92,6 +92,7 @@ describe Project do ...@@ -92,6 +92,7 @@ describe Project do
it { is_expected.to have_many(:pipeline_schedules) } it { is_expected.to have_many(:pipeline_schedules) }
it { is_expected.to have_many(:members_and_requesters) } it { is_expected.to have_many(:members_and_requesters) }
it { is_expected.to have_many(:clusters) } 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(:kubernetes_namespaces) }
it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') } it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') } 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