Read about our upcoming Code of Conduct on this issue

Commit 7cd8fa84 authored by Kamil Trzciński's avatar Kamil Trzciński
Browse files

Merge branch 'retryable_create_or_update_kubernetes_namespace' into 'master'

Update K8s project namespace and ServiceAccount if exist

See merge request gitlab-org/gitlab-ce!23525
......@@ -12,7 +12,8 @@ def execute(provider)
create_gitlab_service_account!
configure_kubernetes
cluster.save!
configure_project_service_account
ClusterPlatformConfigureWorker.perform_async(cluster.id)
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
......@@ -25,7 +26,7 @@ def execute(provider)
private
def create_gitlab_service_account!
Clusters::Gcp::Kubernetes::CreateServiceAccountService.gitlab_creator(
Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator(
kube_client,
rbac: create_rbac_cluster?
).execute
......@@ -55,15 +56,6 @@ def request_kubernetes_token
).execute
end
def configure_project_service_account
kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace(cluster.cluster_project)
Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: cluster,
kubernetes_namespace: kubernetes_namespace
).execute
end
def authorization_type
create_rbac_cluster? ? 'rbac' : 'abac'
end
......
......@@ -27,7 +27,7 @@ def configure_kubernetes_namespace
end
def create_project_service_account
Clusters::Gcp::Kubernetes::CreateServiceAccountService.namespace_creator(
Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.namespace_creator(
platform.kubeclient,
service_account_name: kubernetes_namespace.service_account_name,
service_account_namespace: kubernetes_namespace.namespace,
......
......@@ -3,7 +3,7 @@
module Clusters
module Gcp
module Kubernetes
class CreateServiceAccountService
class CreateOrUpdateServiceAccountService
def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil)
@kubeclient = kubeclient
@service_account_name = service_account_name
......@@ -38,8 +38,9 @@ def self.namespace_creator(kubeclient, service_account_name:, service_account_na
def execute
ensure_project_namespace_exists if namespace_creator
kubeclient.create_service_account(service_account_resource)
kubeclient.create_secret(service_account_token_resource)
kubeclient.create_or_update_service_account(service_account_resource)
kubeclient.create_or_update_secret(service_account_token_resource)
create_role_or_cluster_role_binding if rbac
end
......@@ -56,9 +57,9 @@ def ensure_project_namespace_exists
def create_role_or_cluster_role_binding
if namespace_creator
kubeclient.create_role_binding(role_binding_resource)
kubeclient.create_or_update_role_binding(role_binding_resource)
else
kubeclient.create_cluster_role_binding(cluster_role_binding_resource)
kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource)
end
end
......
---
title: Updates service to update Kubernetes project namespaces and restricted service
account if present
merge_request: 23525
author:
type: changed
......@@ -46,6 +46,7 @@ class KubeClient
:create_secret,
:create_service_account,
:update_config_map,
:update_secret,
:update_service_account,
to: :core_client
......@@ -80,8 +81,64 @@ def initialize(api_prefix, **kubeclient_options)
@kubeclient_options = kubeclient_options
end
def create_or_update_cluster_role_binding(resource)
if cluster_role_binding_exists?(resource)
update_cluster_role_binding(resource)
else
create_cluster_role_binding(resource)
end
end
def create_or_update_role_binding(resource)
if role_binding_exists?(resource)
update_role_binding(resource)
else
create_role_binding(resource)
end
end
def create_or_update_service_account(resource)
if service_account_exists?(resource)
update_service_account(resource)
else
create_service_account(resource)
end
end
def create_or_update_secret(resource)
if secret_exists?(resource)
update_secret(resource)
else
create_secret(resource)
end
end
private
def cluster_role_binding_exists?(resource)
get_cluster_role_binding(resource.metadata.name)
rescue ::Kubeclient::ResourceNotFoundError
false
end
def role_binding_exists?(resource)
get_role_binding(resource.metadata.name, resource.metadata.namespace)
rescue ::Kubeclient::ResourceNotFoundError
false
end
def service_account_exists?(resource)
get_service_account(resource.metadata.name, resource.metadata.namespace)
rescue ::Kubeclient::ResourceNotFoundError
false
end
def secret_exists?(resource)
get_secret(resource.metadata.name, resource.metadata.namespace)
rescue ::Kubeclient::ResourceNotFoundError
false
end
def build_kubeclient(api_group, api_version)
::Kubeclient::Client.new(
join_api_url(api_prefix, api_group),
......
......@@ -99,6 +99,7 @@
:create_secret,
:create_service_account,
:update_config_map,
:update_secret,
:update_service_account
].each do |method|
describe "##{method}" do
......@@ -174,6 +175,84 @@
end
end
shared_examples 'create_or_update method' do
let(:get_method) { "get_#{resource_type}" }
let(:update_method) { "update_#{resource_type}" }
let(:create_method) { "create_#{resource_type}" }
context 'resource exists' do
before do
expect(client).to receive(get_method).and_return(resource)
end
it 'calls the update method' do
expect(client).to receive(update_method).with(resource)
subject
end
end
context 'resource does not exist' do
before do
expect(client).to receive(get_method).and_raise(Kubeclient::ResourceNotFoundError.new(404, 'Not found', nil))
end
it 'calls the create method' do
expect(client).to receive(create_method).with(resource)
subject
end
end
end
describe '#create_or_update_cluster_role_binding' do
let(:resource_type) { 'cluster_role_binding' }
let(:resource) do
::Kubeclient::Resource.new(metadata: { name: 'name', namespace: 'namespace' })
end
subject { client.create_or_update_cluster_role_binding(resource) }
it_behaves_like 'create_or_update method'
end
describe '#create_or_update_role_binding' do
let(:resource_type) { 'role_binding' }
let(:resource) do
::Kubeclient::Resource.new(metadata: { name: 'name', namespace: 'namespace' })
end
subject { client.create_or_update_role_binding(resource) }
it_behaves_like 'create_or_update method'
end
describe '#create_or_update_service_account' do
let(:resource_type) { 'service_account' }
let(:resource) do
::Kubeclient::Resource.new(metadata: { name: 'name', namespace: 'namespace' })
end
subject { client.create_or_update_service_account(resource) }
it_behaves_like 'create_or_update method'
end
describe '#create_or_update_secret' do
let(:resource_type) { 'secret' }
let(:resource) do
::Kubeclient::Resource.new(metadata: { name: 'name', namespace: 'namespace' })
end
subject { client.create_or_update_secret(resource) }
it_behaves_like 'create_or_update method'
end
describe 'methods that do not exist on any client' do
it 'throws an error' do
expect { client.non_existent_method }.to raise_error(NoMethodError)
......
......@@ -19,6 +19,10 @@
subject { described_class.new.execute(provider) }
before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
end
shared_examples 'success' do
it 'configures provider and kubernetes' do
subject
......@@ -39,14 +43,10 @@
expect(platform.token).to eq(token)
end
it 'creates kubernetes namespace model' do
subject
it 'calls ClusterPlatformConfigureWorker in a ascync fashion' do
expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id)
kubernetes_namespace = cluster.reload.kubernetes_namespace
expect(kubernetes_namespace).to be_persisted
expect(kubernetes_namespace.namespace).to eq(namespace)
expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
expect(kubernetes_namespace.service_account_token).to be_present
subject
end
end
......@@ -104,8 +104,10 @@
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url)
stub_kubeclient_create_namespace(api_url)
stub_kubeclient_get_service_account_error(api_url, 'gitlab')
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_create_secret(api_url)
stub_kubeclient_put_secret(api_url, 'gitlab-token')
stub_kubeclient_get_secret(
api_url,
......@@ -115,19 +117,6 @@
namespace: 'default'
}
)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
stub_kubeclient_get_secret(
api_url,
{
metadata_name: "#{namespace}-token",
token: Base64.encode64(token),
namespace: namespace
}
)
end
end
......@@ -155,8 +144,8 @@
before do
provider.legacy_abac = false
stub_kubeclient_get_cluster_role_binding_error(api_url, 'gitlab-admin')
stub_kubeclient_create_cluster_role_binding(api_url)
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
end
include_context 'kubernetes information successfully fetched'
......
......@@ -10,6 +10,7 @@
let(:api_url) { 'https://kubernetes.example.com' }
let(:project) { cluster.project }
let(:cluster_project) { cluster.cluster_project }
let(:namespace) { "#{project.path}-#{project.id}" }
subject do
described_class.new(
......@@ -18,40 +19,31 @@
).execute
end
shared_context 'kubernetes requests' do
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url)
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_create_secret(api_url)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
stub_kubeclient_get_secret(
api_url,
{
metadata_name: "#{namespace}-token",
token: Base64.encode64('sample-token'),
namespace: namespace
}
)
end
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url)
stub_kubeclient_get_service_account_error(api_url, 'gitlab')
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_get_secret_error(api_url, 'gitlab-token')
stub_kubeclient_create_secret(api_url)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_get_service_account_error(api_url, "#{namespace}-service-account", namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
stub_kubeclient_get_secret(
api_url,
{
metadata_name: "#{namespace}-token",
token: Base64.encode64('sample-token'),
namespace: namespace
}
)
end
context 'when kubernetes namespace is not persisted' do
let(:namespace) { "#{project.path}-#{project.id}" }
let(:kubernetes_namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
include_context 'kubernetes requests'
shared_examples 'successful creation of kubernetes namespace' do
it 'creates a Clusters::KubernetesNamespace' do
expect do
subject
......@@ -59,7 +51,7 @@
end
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
subject
end
......@@ -74,42 +66,69 @@
end
end
context 'when there is a Kubernetes Namespace associated' do
let(:namespace) { 'new-namespace' }
context 'group clusters' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:group) { cluster.group }
let(:project) { create(:project, group: group) }
context 'when kubernetes namespace is not persisted' do
let(:kubernetes_namespace) do
build(:cluster_kubernetes_namespace,
cluster: cluster,
project: project)
end
let(:kubernetes_namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
cluster_project: cluster_project)
it_behaves_like 'successful creation of kubernetes namespace'
end
end
include_context 'kubernetes requests'
context 'project clusters' do
context 'when kubernetes namespace is not persisted' do
let(:kubernetes_namespace) do
build(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
before do
platform.update_column(:namespace, 'new-namespace')
it_behaves_like 'successful creation of kubernetes namespace'
end
it 'does not create any Clusters::KubernetesNamespace' do
subject
context 'when there is a Kubernetes Namespace associated' do
let(:namespace) { 'new-namespace' }
expect(cluster.kubernetes_namespace).to eq(kubernetes_namespace)
end
let(:kubernetes_namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once
before do
platform.update_column(:namespace, 'new-namespace')
end
subject
end
it 'does not create any Clusters::KubernetesNamespace' do
subject
it 'updates Clusters::KubernetesNamespace' do
subject
expect(cluster.kubernetes_namespace).to eq(kubernetes_namespace)
end
kubernetes_namespace.reload
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
expect(kubernetes_namespace.namespace).to eq(namespace)
expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
expect(kubernetes_namespace.encrypted_service_account_token).to be_present
subject
end
it 'updates Clusters::KubernetesNamespace' do
subject
kubernetes_namespace.reload
expect(kubernetes_namespace.namespace).to eq(namespace)
expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
expect(kubernetes_namespace.encrypted_service_account_token).to be_present
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
include KubernetesHelpers
let(:api_url) { 'http://111.111.111.111' }
......@@ -55,7 +55,11 @@
before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace )
stub_kubeclient_get_service_account_error(api_url, service_account_name, namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_get_secret_error(api_url, token_name, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
end
......@@ -74,10 +78,12 @@
context 'with RBAC cluster' do
let(:rbac) { true }
let(:cluster_role_binding_name) { 'gitlab-admin' }
before do
cluster.platform_kubernetes.rbac!
stub_kubeclient_get_cluster_role_binding_error(api_url, cluster_role_binding_name)
stub_kubeclient_create_cluster_role_binding(api_url)
end
......@@ -130,10 +136,12 @@
context 'With RBAC enabled cluster' do
let(:rbac) { true }
let(:role_binding_name) { "gitlab-#{namespace}"}
before do
cluster.platform_kubernetes.rbac!
stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace)
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
end
......
......@@ -47,6 +47,11 @@ def stub_kubeclient_get_secret_error(api_url, name, namespace: 'default', status
.to_return(status: [status, "Internal Server Error"])
end
def stub_kubeclient_get_service_account_error(api_url, name, namespace: 'default', status: 404)
WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts/#{name}")
.to_return(status: [status, "Internal Server Error"])
end
def stub_kubeclient_create_service_account(api_url, namespace: 'default')
WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts")
.to_return(kube_response({}))
......@@ -62,11 +67,26 @@ def stub_kubeclient_create_secret(api_url, namespace: 'default')
.to_return(kube_response({}))
end
def stub_kubeclient_put_secret(api_url, name, namespace: 'default')
WebMock.stub_request(:put, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{name}")
.to_return(kube_response({}))
end
def stub_kubeclient_get_cluster_role_binding_error(api_url, name, status: 404)
WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/#{name}")
.to_return(status: [status, "Internal Server Error"])
end
def stub_kubeclient_create_cluster_role_binding(api_url)
WebMock.stub_request(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings')
.to_return(kube_response({}))
end
def stub_kubeclient_get_role_binding_error(api_url, name, namespace: 'default', status: 404)
WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}")
.to_return(status: [status, "Internal Server Error"])
end
def stub_kubeclient_create_role_binding(api_url, namespace: 'default')
WebMock.stub_request(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings")
.to_return(kube_response({}))
......
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