Skip to content
Snippets Groups Projects
Commit d54791e0 authored by Thong Kuah's avatar Thong Kuah :speech_balloon:
Browse files

Create k8s namespace for project in group clusters

AFAIK the only relevant place is Projects::CreateService, this gets
called when user creates a new project, forks a new project and does
those things via the api.

Also create k8s namespace for new group hierarchy
when transferring project between groups

Uses new Refresh service to create k8s namespaces

- Ensure we use Cluster#cluster_project

If a project has multiple clusters (EE), using Project#cluster_project
is not guaranteed to return the cluster_project for this cluster. So
switch to using Cluster#cluster_project - at this stage a cluster can
only have 1 cluster_project.

Also, remove rescue so that sidekiq can retry
parent 8419b7dd
No related branches found
No related tags found
No related merge requests found
Showing
with 315 additions and 31 deletions
Loading
Loading
@@ -87,6 +87,12 @@ module Clusters
 
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
 
scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.cluster_id = clusters.id')
where('NOT EXISTS (?)', subquery)
end
# Returns an ordered list of group clusters order from clusters of closest
# group up to furthest ancestor group
def self.ordered_group_clusters_for_project(project_id)
Loading
Loading
@@ -161,11 +167,17 @@ module Clusters
platform_kubernetes.kubeclient if kubernetes?
end
 
def find_or_initialize_kubernetes_namespace(cluster_project)
kubernetes_namespaces.find_or_initialize_by(
project: cluster_project.project,
cluster_project: cluster_project
)
def find_or_initialize_kubernetes_namespace_for_project(project)
if project_type?
kubernetes_namespaces.find_or_initialize_by(
project: project,
cluster_project: cluster_project
)
else
kubernetes_namespaces.find_or_initialize_by(
project: project
)
end
end
 
def allow_user_defined_namespace?
Loading
Loading
Loading
Loading
@@ -383,6 +383,12 @@ class Project < ActiveRecord::Base
.where(project_ci_cd_settings: { group_runners_enabled: true })
end
 
scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.project_id = projects.id')
where('NOT EXISTS (?)', subquery)
end
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
 
chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
Loading
Loading
# frozen_string_literal: true
module Clusters
class RefreshService
def create_or_update_namespaces_for_cluster(cluster)
cluster_namespaces = cluster.kubernetes_namespaces
# Create all namespaces that are missing for each project
cluster.all_projects.missing_kubernetes_namespace(cluster_namespaces).each do |project|
kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project)
::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: cluster,
kubernetes_namespace: kubernetes_namespace
).execute
end
end
def create_or_update_namespaces_for_project(project)
project_namespaces = project.kubernetes_namespaces
# Create all namespaces that are missing for each cluster
project.all_clusters.missing_kubernetes_namespace(project_namespaces).each do |cluster|
kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project)
::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: cluster,
kubernetes_namespace: kubernetes_namespace
).execute
end
end
end
end
Loading
Loading
@@ -96,6 +96,8 @@ module Projects
current_user.invalidate_personal_projects_count
 
create_readme if @initialize_with_readme
configure_group_clusters_for_project
end
 
# Refresh the current user's authorizations inline (so they can access the
Loading
Loading
@@ -121,6 +123,10 @@ module Projects
Files::CreateService.new(@project, current_user, commit_attrs).execute
end
 
def configure_group_clusters_for_project
ClusterProjectConfigureWorker.perform_async(@project.id)
end
def skip_wiki?
!@project.feature_available?(:wiki, current_user) || @skip_wiki
end
Loading
Loading
Loading
Loading
@@ -54,6 +54,7 @@ module Projects
end
 
attempt_transfer_transaction
configure_group_clusters_for_project
end
# rubocop: enable CodeReuse/ActiveRecord
 
Loading
Loading
@@ -162,5 +163,9 @@ module Projects
@new_namespace.full_path
)
end
def configure_group_clusters_for_project
ClusterProjectConfigureWorker.perform_async(project.id)
end
end
end
Loading
Loading
@@ -29,6 +29,7 @@
- gcp_cluster:wait_for_cluster_creation
- gcp_cluster:cluster_wait_for_ingress_ip_address
- gcp_cluster:cluster_platform_configure
- gcp_cluster:cluster_project_configure
 
- github_import_advance_stage
- github_importer:github_import_import_diff_note
Loading
Loading
Loading
Loading
@@ -6,17 +6,7 @@ class ClusterPlatformConfigureWorker
 
def perform(cluster_id)
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
next unless cluster.cluster_project
kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace(cluster.cluster_project)
Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: cluster,
kubernetes_namespace: kubernetes_namespace
).execute
Clusters::RefreshService.new.create_or_update_namespaces_for_cluster(cluster)
end
rescue ::Kubeclient::HttpError => err
Rails.logger.error "Failed to create/update Kubernetes namespace for cluster_id: #{cluster_id} with error: #{err.message}"
end
end
# frozen_string_literal: true
class ClusterProjectConfigureWorker
include ApplicationWorker
include ClusterQueue
def perform(project_id)
project = Project.find(project_id)
::Clusters::RefreshService.new.create_or_update_namespaces_for_project(project)
end
end
Loading
Loading
@@ -92,6 +92,26 @@ describe Clusters::Cluster do
it { is_expected.to contain_exactly(cluster) }
end
 
describe '.missing_kubernetes_namespace' do
let!(:cluster) { create(:cluster, :provided_by_gcp, :project) }
let(:project) { cluster.project }
let(:kubernetes_namespaces) { project.kubernetes_namespaces }
subject do
described_class.joins(:projects).where(projects: { id: project.id }).missing_kubernetes_namespace(kubernetes_namespaces)
end
it { is_expected.to contain_exactly(cluster) }
context 'kubernetes namespace exists' do
before do
create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
end
it { is_expected.to be_empty }
end
end
describe 'validation' do
subject { cluster.valid? }
 
Loading
Loading
Loading
Loading
@@ -155,6 +155,24 @@ describe Project do
it { is_expected.to include_module(Sortable) }
end
 
describe '.missing_kubernetes_namespace' do
let!(:project) { create(:project) }
let!(:cluster) { create(:cluster, :provided_by_user, :group) }
let(:kubernetes_namespaces) { project.kubernetes_namespaces }
subject { described_class.missing_kubernetes_namespace(kubernetes_namespaces) }
it { is_expected.to contain_exactly(project) }
context 'kubernetes namespace exists' do
before do
create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
end
it { is_expected.to be_empty }
end
end
describe 'validation' do
let!(:project) { create(:project) }
 
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::RefreshService do
shared_examples 'creates a kubernetes namespace' do
let(:token) { 'aaaaaa' }
let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateServiceAccountService, execute: true) }
let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) }
it 'creates a kubernetes namespace' do
expect(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
expect { subject }.to change(project.kubernetes_namespaces, :count)
kubernetes_namespace = cluster.kubernetes_namespaces.first
expect(kubernetes_namespace).to be_present
expect(kubernetes_namespace.project).to eq(project)
end
end
shared_examples 'does not create a kubernetes namespace' do
it 'does not create a new kubernetes namespace' do
expect(Clusters::Gcp::Kubernetes::CreateServiceAccountService).not_to receive(:namespace_creator)
expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).not_to receive(:new)
expect { subject }.not_to change(Clusters::KubernetesNamespace, :count)
end
end
describe '#create_or_update_namespaces_for_cluster' do
let(:cluster) { create(:cluster, :provided_by_user, :project) }
let(:project) { cluster.project }
subject { described_class.new.create_or_update_namespaces_for_cluster(cluster) }
context 'cluster is project level' do
include_examples 'creates a kubernetes namespace'
context 'when project already has kubernetes namespace' do
before do
create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
end
include_examples 'does not create a kubernetes namespace'
end
end
context 'cluster is group level' do
let(:cluster) { create(:cluster, :provided_by_user, :group) }
let(:group) { cluster.group }
let(:project) { create(:project, group: group) }
include_examples 'creates a kubernetes namespace'
context 'when project already has kubernetes namespace' do
before do
create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
end
include_examples 'does not create a kubernetes namespace'
end
end
end
describe '#create_or_update_namespaces_for_project' do
let(:project) { create(:project) }
subject { described_class.new.create_or_update_namespaces_for_project(project) }
it 'creates no kubernetes namespaces' do
expect { subject }.not_to change(project.kubernetes_namespaces, :count)
end
context 'project has a project cluster' do
let!(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :project_type, projects: [project]) }
include_examples 'creates a kubernetes namespace'
context 'when project already has kubernetes namespace' do
before do
create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
end
include_examples 'does not create a kubernetes namespace'
end
end
context 'project belongs to a group cluster' do
let!(:cluster) { create(:cluster, :provided_by_gcp, :group) }
let(:group) { cluster.group }
let(:project) { create(:project, group: group) }
include_examples 'creates a kubernetes namespace'
context 'when project already has kubernetes namespace' do
before do
create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
end
include_examples 'does not create a kubernetes namespace'
end
end
end
end
Loading
Loading
@@ -261,6 +261,32 @@ describe Projects::CreateService, '#execute' do
end
end
 
context 'when group has kubernetes cluster' do
let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:group) { group_cluster.group }
let(:token) { 'aaaa' }
let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateServiceAccountService, execute: true) }
let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) }
before do
group.add_owner(user)
expect(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
end
it 'creates kubernetes namespace for the project' do
project = create_project(user, opts.merge!(namespace_id: group.id))
expect(project).to be_valid
kubernetes_namespace = group_cluster.kubernetes_namespaces.first
expect(kubernetes_namespace).to be_present
expect(kubernetes_namespace.project).to eq(project)
end
end
context 'when there is an active service template' do
before do
create(:service, project: nil, template: true, active: true)
Loading
Loading
Loading
Loading
@@ -62,6 +62,32 @@ describe Projects::TransferService do
 
expect(rugged_config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}"
end
context 'new group has a kubernetes cluster' do
let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:group) { group_cluster.group }
let(:token) { 'aaaa' }
let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateServiceAccountService, execute: true) }
let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) }
subject { transfer_project(project, user, group) }
before do
expect(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
end
it 'creates kubernetes namespace for the project' do
subject
expect(project.kubernetes_namespaces.count).to eq(1)
kubernetes_namespace = group_cluster.kubernetes_namespaces.first
expect(kubernetes_namespace).to be_present
expect(kubernetes_namespace.project).to eq(project)
end
end
end
 
context 'when transfer fails' do
Loading
Loading
Loading
Loading
@@ -2,7 +2,43 @@
 
require 'spec_helper'
 
describe ClusterPlatformConfigureWorker, '#execute' do
describe ClusterPlatformConfigureWorker, '#perform' do
let(:worker) { described_class.new }
context 'when group cluster' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:group) { cluster.group }
context 'when group has no projects' do
it 'does not create a namespace' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute)
worker.perform(cluster.id)
end
end
context 'when group has a project' do
let!(:project) { create(:project, group: group) }
it 'creates a namespace for the project' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute).once
worker.perform(cluster.id)
end
end
context 'when group has project in a sub-group' do
let!(:subgroup) { create(:group, parent: group) }
let!(:project) { create(:project, group: subgroup) }
it 'creates a namespace for the project' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute).once
worker.perform(cluster.id)
end
end
end
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
 
Loading
Loading
@@ -30,18 +66,4 @@ describe ClusterPlatformConfigureWorker, '#execute' do
described_class.new.perform(123)
end
end
context 'when kubeclient raises error' do
let(:cluster) { create(:cluster, :project) }
it 'rescues and logs the error' do
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute).and_raise(::Kubeclient::HttpError.new(500, 'something baaaad happened', ''))
expect(Rails.logger)
.to receive(:error)
.with("Failed to create/update Kubernetes namespace for cluster_id: #{cluster.id} with error: something baaaad happened")
described_class.new.perform(cluster.id)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment