summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMayra Cabrera <mcabrera@gitlab.com>2018-10-16 15:03:59 -0500
committerMayra Cabrera <mcabrera@gitlab.com>2018-10-22 09:54:00 -0500
commite3ca493876ab71ed29817a0af436fc563f564bbe (patch)
treebca9826ae1cc1b8140249e570626f22eb48cb960
parenta310a638db037d3914cf12e580c7a41209d2d06e (diff)
downloadgitlab-ce-51716-add-kubernetes-namespace-model.tar.gz
Add Clusters::KubernetesNamespace model51716-add-kubernetes-namespace-model
This model will be used to persist into database Kubernetes properties, such as namespace, service account name and service account token.
-rw-r--r--app/models/clusters/cluster.rb3
-rw-r--r--app/models/clusters/kubernetes_namespace.rb49
-rw-r--r--app/models/clusters/platforms/kubernetes.rb16
-rw-r--r--app/models/clusters/project.rb3
-rw-r--r--changelogs/unreleased/51716-add-kubernetes-namespace-model.yml5
-rw-r--r--db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb24
-rw-r--r--db/schema.rb20
-rw-r--r--lib/gitlab/namespace_sanitizer.rb9
-rw-r--r--spec/factories/clusters/kubernetes_namespaces.rb9
-rw-r--r--spec/factories/clusters/projects.rb8
-rw-r--r--spec/models/clusters/cluster_spec.rb4
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb84
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb75
-rw-r--r--spec/models/clusters/project_spec.rb2
14 files changed, 287 insertions, 24 deletions
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 20d53b8e620..95efecfc41d 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -31,6 +31,9 @@ module Clusters
has_one :application_runner, class_name: 'Clusters::Applications::Runner'
has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter'
+ has_many :kubernetes_namespaces
+ has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace'
+
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
new file mode 100644
index 00000000000..fb5f6b65d9d
--- /dev/null
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Clusters
+ class KubernetesNamespace < ActiveRecord::Base
+ self.table_name = 'clusters_kubernetes_namespaces'
+
+ belongs_to :cluster_project, class_name: 'Clusters::Project'
+ belongs_to :cluster, class_name: 'Clusters::Cluster'
+ belongs_to :project, class_name: '::Project'
+ has_one :platform_kubernetes, through: :cluster
+
+ validates :namespace, presence: true
+ validates :namespace, uniqueness: { scope: :cluster_id }
+
+ before_validation :set_namespace_and_service_account_to_default, on: :create
+
+ attr_encrypted :service_account_token,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc'
+
+ def token_name
+ "#{namespace}-token"
+ end
+
+ private
+
+ def set_namespace_and_service_account_to_default
+ self.namespace ||= default_namespace
+ self.service_account_name ||= default_service_account_name
+ end
+
+ def default_namespace
+ platform_kubernetes&.namespace.presence || project_namespace
+ end
+
+ def default_service_account_name
+ "#{namespace}-service-account"
+ end
+
+ def project_namespace
+ Gitlab::NamespaceSanitizer.sanitize(project_slug)
+ end
+
+ def project_slug
+ "#{project.path}-#{project.id}".downcase
+ end
+ end
+end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 3a335909101..e8e943872de 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -7,6 +7,8 @@ module Clusters
include ReactiveCaching
include EnumWithNil
+ RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
+
self.table_name = 'cluster_platforms_kubernetes'
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] }
@@ -32,6 +34,8 @@ module Clusters
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
+ validates :namespace, exclusion: { in: RESERVED_NAMESPACES }
+
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
validates :api_url, url: true, presence: true
validates :token, presence: true
@@ -45,6 +49,7 @@ module Clusters
delegate :project, to: :cluster, allow_nil: true
delegate :enabled?, to: :cluster, allow_nil: true
delegate :managed?, to: :cluster, allow_nil: true
+ delegate :kubernetes_namespace, to: :cluster
alias_method :active?, :enabled?
@@ -116,10 +121,19 @@ module Clusters
end
def default_namespace
+ kubernetes_namespace&.namespace.presence || fallback_default_namespace
+ end
+
+ # DEPRECATED
+ #
+ # On 11.4 Clusters::KubernetesNamespace was introduced, this model will allow to
+ # have multiple namespaces per project. This method will be removed after migration
+ # has been completed.
+ def fallback_default_namespace
return unless project
slug = "#{project.path}-#{project.id}".downcase
- slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
+ Gitlab::NamespaceSanitizer.sanitize(slug)
end
def build_kube_client!(api_groups: ['api'], api_version: 'v1')
diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb
index 839ce796081..15092b1c9d2 100644
--- a/app/models/clusters/project.rb
+++ b/app/models/clusters/project.rb
@@ -6,5 +6,8 @@ module Clusters
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :project, class_name: '::Project'
+
+ has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
+ has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
end
end
diff --git a/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml b/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml
new file mode 100644
index 00000000000..ad43c512ba3
--- /dev/null
+++ b/changelogs/unreleased/51716-add-kubernetes-namespace-model.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce new model to persist specific cluster information
+merge_request: 22404
+author:
+type: added
diff --git a/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
new file mode 100644
index 00000000000..a58c190e1d6
--- /dev/null
+++ b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class CreateClustersKubernetesNamespaces < ActiveRecord::Migration
+ DOWNTIME = false
+ INDEX_NAME = 'kubernetes_namespaces_cluster_and_namespace'
+
+ def change
+ create_table :clusters_kubernetes_namespaces, id: :bigserial do |t|
+ t.references :cluster, null: false, index: true, foreign_key: { on_delete: :cascade }
+ t.references :project, index: true, foreign_key: { on_delete: :nullify }
+ t.references :cluster_project, index: true, foreign_key: { on_delete: :nullify }
+
+ t.timestamps_with_timezone null: false
+
+ t.string :encrypted_service_account_token_iv
+ t.string :namespace, null: false
+ t.string :service_account_name
+
+ t.text :encrypted_service_account_token
+
+ t.index [:cluster_id, :namespace], name: INDEX_NAME, unique: true
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3f3bec0ce04..50989960aa9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -691,6 +691,23 @@ ActiveRecord::Schema.define(version: 20181013005024) do
add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree
add_index "clusters_applications_runners", ["runner_id"], name: "index_clusters_applications_runners_on_runner_id", using: :btree
+ create_table "clusters_kubernetes_namespaces", id: :bigserial, force: :cascade do |t|
+ t.integer "cluster_id", null: false
+ t.integer "project_id"
+ t.integer "cluster_project_id"
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.text "encrypted_service_account_token"
+ t.string "encrypted_service_account_token_iv"
+ t.string "namespace", null: false
+ t.string "service_account_name"
+ end
+
+ add_index "clusters_kubernetes_namespaces", ["cluster_id", "namespace"], name: "kubernetes_namespaces_cluster_and_namespace", unique: true, using: :btree
+ add_index "clusters_kubernetes_namespaces", ["cluster_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_id", using: :btree
+ add_index "clusters_kubernetes_namespaces", ["cluster_project_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_project_id", using: :btree
+ add_index "clusters_kubernetes_namespaces", ["project_id"], name: "index_clusters_kubernetes_namespaces_on_project_id", using: :btree
+
create_table "container_repositories", force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
@@ -2325,6 +2342,9 @@ ActiveRecord::Schema.define(version: 20181013005024) do
add_foreign_key "clusters_applications_prometheus", "clusters", name: "fk_557e773639", on_delete: :cascade
add_foreign_key "clusters_applications_runners", "ci_runners", column: "runner_id", name: "fk_02de2ded36", on_delete: :nullify
add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_kubernetes_namespaces", "cluster_projects", on_delete: :nullify
+ add_foreign_key "clusters_kubernetes_namespaces", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_kubernetes_namespaces", "projects", on_delete: :nullify
add_foreign_key "container_repositories", "projects"
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
diff --git a/lib/gitlab/namespace_sanitizer.rb b/lib/gitlab/namespace_sanitizer.rb
new file mode 100644
index 00000000000..d755bbbcaf9
--- /dev/null
+++ b/lib/gitlab/namespace_sanitizer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class NamespaceSanitizer
+ def self.sanitize(namespace)
+ namespace.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
+ end
+ end
+end
diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb
new file mode 100644
index 00000000000..6fdada75a3d
--- /dev/null
+++ b/spec/factories/clusters/kubernetes_namespaces.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do
+ cluster
+ project
+ cluster_project
+ end
+end
diff --git a/spec/factories/clusters/projects.rb b/spec/factories/clusters/projects.rb
new file mode 100644
index 00000000000..6cda77c6f85
--- /dev/null
+++ b/spec/factories/clusters/projects.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :cluster_project, class: Clusters::Project do
+ cluster
+ project
+ end
+end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 34d321ec604..f5c4b0b66ae 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -11,6 +11,9 @@ describe Clusters::Cluster do
it { is_expected.to have_one(:application_ingress) }
it { is_expected.to have_one(:application_prometheus) }
it { is_expected.to have_one(:application_runner) }
+ it { is_expected.to have_many(:kubernetes_namespaces) }
+ it { is_expected.to have_one(:kubernetes_namespace) }
+
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
it { is_expected.to delegate_method(:status_name).to(:provider) }
@@ -20,6 +23,7 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
+
it { is_expected.to respond_to :project }
describe '.enabled' do
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
new file mode 100644
index 00000000000..dea58fa26c7
--- /dev/null
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Clusters::KubernetesNamespace, type: :model do
+ it { is_expected.to belong_to(:cluster_project) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to have_one(:platform_kubernetes) }
+
+ describe 'namespace uniqueness validation' do
+ let(:cluster_project) { create(:cluster_project) }
+
+ let(:kubernetes_namespace) do
+ build(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project)
+ end
+
+ subject { kubernetes_namespace }
+
+ context 'when cluster is using the namespace' do
+ before do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project,
+ namespace: kubernetes_namespace.namespace)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when cluster is not using the namespace' do
+ it { is_expected.to be_valid }
+ end
+ end
+
+ describe '#set_namespace_and_service_account_to_default' do
+ let(:cluster) { platform.cluster }
+ let(:cluster_project) { create(:cluster_project, cluster: cluster) }
+ let(:kubernetes_namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project)
+ end
+
+ describe 'namespace' do
+ let(:platform) { create(:cluster_platform_kubernetes, namespace: namespace) }
+
+ subject { kubernetes_namespace.namespace }
+
+ context 'when platform has a namespace assigned' do
+ let(:namespace) { 'platform-namespace' }
+
+ it 'should copy the namespace' do
+ is_expected.to eq('platform-namespace')
+ end
+ end
+
+ context 'when platform does not have namespace assigned' do
+ let(:namespace) { nil }
+
+ it 'should set default namespace' do
+ project_slug = "#{cluster_project.project.path}-#{cluster_project.project_id}"
+
+ is_expected.to eq(project_slug)
+ end
+ end
+ end
+
+ describe 'service_account_name' do
+ let(:platform) { create(:cluster_platform_kubernetes) }
+
+ subject { kubernetes_namespace.service_account_name }
+
+ it 'should set a service account name based on namespace' do
+ is_expected.to eq("#{kubernetes_namespace.namespace}-service-account")
+ end
+ end
+ end
+end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 66198d5ee2b..e13eb554add 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -9,6 +9,15 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to be_kind_of(ReactiveCaching) }
it { is_expected.to respond_to :ca_pem }
+ it { is_expected.to validate_exclusion_of(:namespace).in_array(%w(gitlab-managed-apps)) }
+ it { is_expected.to validate_presence_of(:api_url) }
+ it { is_expected.to validate_presence_of(:token) }
+
+ it { is_expected.to delegate_method(:project).to(:cluster) }
+ it { is_expected.to delegate_method(:enabled?).to(:cluster) }
+ it { is_expected.to delegate_method(:managed?).to(:cluster) }
+ it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) }
+
describe 'before_validation' do
context 'when namespace includes upper case' do
let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
@@ -90,6 +99,28 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { expect(kubernetes.save).to be_falsey }
end
end
+
+ describe 'when using reserved namespaces' do
+ subject { build(:cluster_platform_kubernetes, namespace: namespace) }
+
+ context 'when no namespace is manually assigned' do
+ let(:namespace) { nil }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when no reserved namespace is assigned' do
+ let(:namespace) { 'my-namespace' }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when reserved namespace is assigned' do
+ let(:namespace) { 'gitlab-managed-apps' }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
end
describe '#kubeclient' do
@@ -117,41 +148,39 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
describe '#actual_namespace' do
- subject { kubernetes.actual_namespace }
-
- let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
+ let(:cluster) { create(:cluster, :project) }
let(:project) { cluster.project }
- let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
- context 'when namespace is present' do
+ let(:platform) do
+ create(:cluster_platform_kubernetes,
+ cluster: cluster,
+ namespace: namespace)
+ end
+
+ subject { platform.actual_namespace }
+
+ context 'with a namespace assigned' do
let(:namespace) { 'namespace-123' }
it { is_expected.to eq(namespace) }
end
- context 'when namespace is not present' do
+ context 'with no namespace assigned' do
let(:namespace) { nil }
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
- end
-
- describe '#default_namespace' do
- subject { kubernetes.send(:default_namespace) }
+ context 'when kubernetes namespace is present' do
+ let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
- let(:kubernetes) { create(:cluster_platform_kubernetes, :configured) }
+ before do
+ kubernetes_namespace
+ end
- context 'when cluster belongs to a project' do
- let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
- let(:project) { cluster.project }
-
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
-
- context 'when cluster belongs to nothing' do
- let!(:cluster) { create(:cluster, platform_kubernetes: kubernetes) }
+ it { is_expected.to eq(kubernetes_namespace.namespace) }
+ end
- it { is_expected.to be_nil }
+ context 'when kubernetes namespace is not present' do
+ it { is_expected.to eq("#{project.path}-#{project.id}") }
+ end
end
end
diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb
index 7d75d6ab345..82ef5a23c18 100644
--- a/spec/models/clusters/project_spec.rb
+++ b/spec/models/clusters/project_spec.rb
@@ -3,4 +3,6 @@ require 'spec_helper'
describe Clusters::Project do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:kubernetes_namespaces) }
+ it { is_expected.to have_one(:kubernetes_namespace) }
end