Commit 0e15eec8 authored by Thong Kuah's avatar Thong Kuah

Associate clusters model to groups

Even though we currently only should have one group for a cluster, we
allow the flexibility to associate to other groups in the future.

This also matches the runner <=> groups association.

- Adds Cluster#first_group, aliased to Cluster#group. For the
conceivable future, a cluster will have at most one group.

- Prevent mixing of group and project clusters. If project type
clusters, it should only have projects assigned.  Similarly with groups.

- Default cluster_type to :project_type. As it's very small table we can
set default and null: false in one release.
parent b868b02c
...@@ -20,6 +20,12 @@ module Clusters ...@@ -20,6 +20,12 @@ module Clusters
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'
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::Group'
has_one :cluster_group, -> { order(id: :desc) }, class_name: 'Clusters::Group'
has_one :group, through: :cluster_group, class_name: '::Group'
# we force autosave to happen when we save `Cluster` model # we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
...@@ -38,8 +44,12 @@ module Clusters ...@@ -38,8 +44,12 @@ module Clusters
accepts_nested_attributes_for :platform_kubernetes, update_only: true accepts_nested_attributes_for :platform_kubernetes, update_only: true
validates :name, cluster_name: true validates :name, cluster_name: true
validates :cluster_type, presence: true
validate :restrict_modification, on: :update validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
delegate :status, to: :provider, allow_nil: true delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true delegate :on_creation?, to: :provider, allow_nil: true
...@@ -50,6 +60,12 @@ module Clusters ...@@ -50,6 +60,12 @@ module Clusters
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
enum cluster_type: {
instance_type: 1,
group_type: 2,
project_type: 3
}
enum platform_type: { enum platform_type: {
kubernetes: 1 kubernetes: 1
} }
...@@ -122,5 +138,17 @@ module Clusters ...@@ -122,5 +138,17 @@ module Clusters
true true
end end
def no_groups
if groups.any?
errors.add(:cluster, 'cannot have groups assigned')
end
end
def no_projects
if projects.any?
errors.add(:cluster, 'cannot have projects assigned')
end
end
end end
end end
# frozen_string_literal: true
module Clusters
class Group < ActiveRecord::Base
self.table_name = 'cluster_groups'
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :group, class_name: '::Group'
end
end
...@@ -42,6 +42,7 @@ module DeploymentPlatform ...@@ -42,6 +42,7 @@ module DeploymentPlatform
{ {
name: 'kubernetes-template', name: 'kubernetes-template',
projects: [self], projects: [self],
cluster_type: :project_type,
provider_type: :user, provider_type: :user,
platform_type: :kubernetes, platform_type: :kubernetes,
platform_kubernetes_attributes: platform_kubernetes_attributes_from_service_template platform_kubernetes_attributes: platform_kubernetes_attributes_from_service_template
......
...@@ -41,6 +41,9 @@ class Group < Namespace ...@@ -41,6 +41,9 @@ class Group < Namespace
has_many :boards has_many :boards
has_many :badges, class_name: 'GroupBadge' has_many :badges, class_name: 'GroupBadge'
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :clusters, through: :cluster_groups, class_name: 'Clusters::Cluster'
has_many :todos has_many :todos
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
......
...@@ -9,9 +9,9 @@ module Clusters ...@@ -9,9 +9,9 @@ module Clusters
end end
def execute(project:, access_token: nil) def execute(project:, access_token: nil)
raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?(project) raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?(project)
cluster_params = params.merge(user: current_user, projects: [project]) cluster_params = params.merge(user: current_user, cluster_type: :project_type, projects: [project])
cluster_params[:provider_gcp_attributes].try do |provider| cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token provider[:access_token] = access_token
end end
......
---
title: Adds model and migrations to enable group level clusters
merge_request: 22307
author:
type: other
# frozen_string_literal: true
class CreateClusterGroups < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :cluster_groups do |t|
t.references :cluster, null: false, foreign_key: { on_delete: :cascade }
t.references :group, null: false, index: true
t.index [:cluster_id, :group_id], unique: true
t.foreign_key :namespaces, column: :group_id, on_delete: :cascade
end
end
end
# frozen_string_literal: true
class AddClusterTypeToClusters < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
PROJECT_CLUSTER_TYPE = 3
disable_ddl_transaction!
def up
add_column_with_default(:clusters, :cluster_type, :smallint, default: PROJECT_CLUSTER_TYPE)
end
def down
remove_column(:clusters, :cluster_type)
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,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: 20181016152238) do ActiveRecord::Schema.define(version: 20181017001059) 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 "plpgsql" enable_extension "plpgsql"
...@@ -599,6 +599,14 @@ ActiveRecord::Schema.define(version: 20181016152238) do ...@@ -599,6 +599,14 @@ ActiveRecord::Schema.define(version: 20181016152238) do
add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
create_table "cluster_groups", force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "group_id", null: false
end
add_index "cluster_groups", ["cluster_id", "group_id"], name: "index_cluster_groups_on_cluster_id_and_group_id", unique: true, using: :btree
add_index "cluster_groups", ["group_id"], name: "index_cluster_groups_on_group_id", using: :btree
create_table "cluster_platforms_kubernetes", force: :cascade do |t| create_table "cluster_platforms_kubernetes", force: :cascade do |t|
t.integer "cluster_id", null: false t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
...@@ -654,6 +662,7 @@ ActiveRecord::Schema.define(version: 20181016152238) do ...@@ -654,6 +662,7 @@ ActiveRecord::Schema.define(version: 20181016152238) do
t.boolean "enabled", default: true t.boolean "enabled", default: true
t.string "name", null: false t.string "name", null: false
t.string "environment_scope", default: "*", null: false t.string "environment_scope", default: "*", null: false
t.integer "cluster_type", limit: 2, default: 3, null: false
end end
add_index "clusters", ["enabled"], name: "index_clusters_on_enabled", using: :btree add_index "clusters", ["enabled"], name: "index_clusters_on_enabled", using: :btree
...@@ -2372,6 +2381,8 @@ ActiveRecord::Schema.define(version: 20181016152238) do ...@@ -2372,6 +2381,8 @@ ActiveRecord::Schema.define(version: 20181016152238) do
add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade
add_foreign_key "cluster_groups", "clusters", on_delete: :cascade
add_foreign_key "cluster_groups", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "cluster_platforms_kubernetes", "clusters", on_delete: :cascade add_foreign_key "cluster_platforms_kubernetes", "clusters", on_delete: :cascade
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
......
...@@ -2,13 +2,28 @@ FactoryBot.define do ...@@ -2,13 +2,28 @@ FactoryBot.define do
factory :cluster, class: Clusters::Cluster do factory :cluster, class: Clusters::Cluster do
user user
name 'test-cluster' name 'test-cluster'
cluster_type :project_type
trait :instance do
cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
end
trait :project do trait :project do
cluster_type { Clusters::Cluster.cluster_types[:project_type] }
before(:create) do |cluster, evaluator| before(:create) do |cluster, evaluator|
cluster.projects << create(:project, :repository) cluster.projects << create(:project, :repository)
end end
end end
trait :group do
cluster_type { Clusters::Cluster.cluster_types[:group_type] }
before(:create) do |cluster, evalutor|
cluster.groups << create(:group)
end
end
trait :provided_by_user do trait :provided_by_user do
provider_type :user provider_type :user
platform_type :kubernetes platform_type :kubernetes
......
...@@ -4,7 +4,10 @@ require 'spec_helper' ...@@ -4,7 +4,10 @@ require 'spec_helper'
describe Clusters::Cluster do describe Clusters::Cluster do
it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:user) }
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(:groups) }
it { is_expected.to have_one(:provider_gcp) } it { is_expected.to have_one(:provider_gcp) }
it { is_expected.to have_one(:platform_kubernetes) } it { is_expected.to have_one(:platform_kubernetes) }
it { is_expected.to have_one(:application_helm) } it { is_expected.to have_one(:application_helm) }
...@@ -178,6 +181,53 @@ describe Clusters::Cluster do ...@@ -178,6 +181,53 @@ describe Clusters::Cluster do
it { expect(cluster.update(enabled: false)).to be_truthy } it { expect(cluster.update(enabled: false)).to be_truthy }
end end
end end
describe 'cluster_type validations' do
let(:instance_cluster) { create(:cluster, :instance) }
let(:group_cluster) { create(:cluster, :group) }
let(:project_cluster) { create(:cluster, :project) }
it 'validates presence' do
cluster = build(:cluster, :project, cluster_type: nil)
expect(cluster).not_to be_valid
expect(cluster.errors.full_messages).to include("Cluster type can't be blank")
end
context 'project_type cluster' do
it 'does not allow setting group' do
project_cluster.groups << build(:group)
expect(project_cluster).not_to be_valid
expect(project_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
end
end
context 'group_type cluster' do
it 'does not allow setting project' do
group_cluster.projects << build(:project)
expect(group_cluster).not_to be_valid
expect(group_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
end
end
context 'instance_type cluster' do
it 'does not allow setting group' do
instance_cluster.groups << build(:group)
expect(instance_cluster).not_to be_valid
expect(instance_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
end
it 'does not allow setting project' do
instance_cluster.projects << build(:project)
expect(instance_cluster).not_to be_valid
expect(instance_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
end
end
end
end end
describe '#provider' do describe '#provider' do
...@@ -229,6 +279,23 @@ describe Clusters::Cluster do ...@@ -229,6 +279,23 @@ describe Clusters::Cluster do
end end
end end
describe '#group' do
subject { cluster.group }
context 'when cluster belongs to a group' do
let(:cluster) { create(:cluster, :group) }
let(:group) { cluster.groups.first }
it { is_expected.to eq(group) }
end
context 'when cluster does not belong to any group' do
let(:cluster) { create(:cluster) }
it { is_expected.to be_nil }
end
end
describe '#applications' do describe '#applications' do
set(:cluster) { create(:cluster) } set(:cluster) { create(:cluster) }
......
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Group do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:group) }
end
...@@ -19,6 +19,8 @@ describe Group do ...@@ -19,6 +19,8 @@ describe Group do
it { is_expected.to have_one(:chat_team) } it { is_expected.to have_one(:chat_team) }
it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') } it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') }
it { is_expected.to have_many(:badges).class_name('GroupBadge') } it { is_expected.to have_many(:badges).class_name('GroupBadge') }
it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') }
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
describe '#members & #requesters' do describe '#members & #requesters' do
let(:requester) { create(:user) } let(:requester) { create(:user) }
......
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