Commit 45bc5093 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '51716-create-kube-namespace' into 'master'

Incorporates Kubernetes Namespace into Cluster's flow

See merge request gitlab-org/gitlab-ce!22011
parents 2a89f065 5ede567d
...@@ -19,6 +19,7 @@ module Clusters ...@@ -19,6 +19,7 @@ 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_one :cluster_project, -> { order(id: :desc) }, class_name: 'Clusters::Project'
has_many :cluster_groups, class_name: 'Clusters::Group' has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::Group' has_many :groups, through: :cluster_groups, class_name: '::Group'
...@@ -128,6 +129,13 @@ module Clusters ...@@ -128,6 +129,13 @@ module Clusters
platform_kubernetes.kubeclient if kubernetes? platform_kubernetes.kubeclient if kubernetes?
end end
def find_or_initialize_kubernetes_namespace(cluster_project)
kubernetes_namespaces.find_or_initialize_by(
project: cluster_project.project,
cluster_project: cluster_project
)
end
private private
def restrict_modification def restrict_modification
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module Clusters module Clusters
class KubernetesNamespace < ActiveRecord::Base class KubernetesNamespace < ActiveRecord::Base
include Gitlab::Kubernetes
self.table_name = 'clusters_kubernetes_namespaces' self.table_name = 'clusters_kubernetes_namespaces'
belongs_to :cluster_project, class_name: 'Clusters::Project' belongs_to :cluster_project, class_name: 'Clusters::Project'
...@@ -12,7 +14,8 @@ module Clusters ...@@ -12,7 +14,8 @@ module Clusters
validates :namespace, presence: true validates :namespace, presence: true
validates :namespace, uniqueness: { scope: :cluster_id } validates :namespace, uniqueness: { scope: :cluster_id }
before_validation :set_namespace_and_service_account_to_default, on: :create delegate :ca_pem, to: :platform_kubernetes, allow_nil: true
delegate :api_url, to: :platform_kubernetes, allow_nil: true
attr_encrypted :service_account_token, attr_encrypted :service_account_token,
mode: :per_attribute_iv, mode: :per_attribute_iv,
...@@ -23,14 +26,26 @@ module Clusters ...@@ -23,14 +26,26 @@ module Clusters
"#{namespace}-token" "#{namespace}-token"
end end
private def configure_predefined_credentials
self.namespace = kubernetes_or_project_namespace
self.service_account_name = default_service_account_name
end
def predefined_variables
config = YAML.dump(kubeconfig)
def set_namespace_and_service_account_to_default Gitlab::Ci::Variables::Collection.new.tap do |variables|
self.namespace ||= default_namespace variables
self.service_account_name ||= default_service_account_name .append(key: 'KUBE_SERVICE_ACCOUNT', value: service_account_name)
.append(key: 'KUBE_NAMESPACE', value: namespace)
.append(key: 'KUBE_TOKEN', value: service_account_token, public: false)
.append(key: 'KUBECONFIG', value: config, public: false, file: true)
end
end end
def default_namespace private
def kubernetes_or_project_namespace
platform_kubernetes&.namespace.presence || project_namespace platform_kubernetes&.namespace.presence || project_namespace
end end
...@@ -45,5 +60,13 @@ module Clusters ...@@ -45,5 +60,13 @@ module Clusters
def project_slug def project_slug
"#{project.path}-#{project.id}".downcase "#{project.path}-#{project.id}".downcase
end end
def kubeconfig
to_kubeconfig(
url: api_url,
namespace: namespace,
token: service_account_token,
ca_pem: ca_pem)
end
end end
end end
...@@ -6,6 +6,7 @@ module Clusters ...@@ -6,6 +6,7 @@ module Clusters
include Gitlab::Kubernetes include Gitlab::Kubernetes
include ReactiveCaching include ReactiveCaching
include EnumWithNil include EnumWithNil
include AfterCommitQueue
RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
...@@ -43,6 +44,7 @@ module Clusters ...@@ -43,6 +44,7 @@ module Clusters
validate :prevent_modification, on: :update validate :prevent_modification, on: :update
after_save :clear_reactive_cache! after_save :clear_reactive_cache!
after_update :update_kubernetes_namespace
alias_attribute :ca_pem, :ca_cert alias_attribute :ca_pem, :ca_cert
...@@ -67,21 +69,31 @@ module Clusters ...@@ -67,21 +69,31 @@ module Clusters
end end
end end
def predefined_variables def predefined_variables(project:)
config = YAML.dump(kubeconfig)
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables variables.append(key: 'KUBE_URL', value: api_url)
.append(key: 'KUBE_URL', value: api_url)
.append(key: 'KUBE_TOKEN', value: token, public: false)
.append(key: 'KUBE_NAMESPACE', value: actual_namespace)
.append(key: 'KUBECONFIG', value: config, public: false, file: true)
if ca_pem.present? if ca_pem.present?
variables variables
.append(key: 'KUBE_CA_PEM', value: ca_pem) .append(key: 'KUBE_CA_PEM', value: ca_pem)
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true) .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end end
if kubernetes_namespace = cluster.kubernetes_namespaces.find_by(project: project)
variables.concat(kubernetes_namespace.predefined_variables)
else
# From 11.5, every Clusters::Project should have at least one
# Clusters::KubernetesNamespace, so once migration has been completed,
# this 'else' branch will be removed. For more information, please see
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22433
config = YAML.dump(kubeconfig)
variables
.append(key: 'KUBE_URL', value: api_url)
.append(key: 'KUBE_TOKEN', value: token, public: false)
.append(key: 'KUBE_NAMESPACE', value: actual_namespace)
.append(key: 'KUBECONFIG', value: config, public: false, file: true)
end
end end
end end
...@@ -199,6 +211,14 @@ module Clusters ...@@ -199,6 +211,14 @@ module Clusters
true true
end end
def update_kubernetes_namespace
return unless namespace_changed?
run_after_commit do
ClusterPlatformConfigureWorker.perform_async(cluster_id)
end
end
end end
end end
end end
...@@ -1829,7 +1829,7 @@ class Project < ActiveRecord::Base ...@@ -1829,7 +1829,7 @@ class Project < ActiveRecord::Base
end end
def deployment_variables(environment: nil) def deployment_variables(environment: nil)
deployment_platform(environment: environment)&.predefined_variables || [] deployment_platform(environment: environment)&.predefined_variables(project: self) || []
end end
def auto_devops_variables def auto_devops_variables
......
...@@ -104,7 +104,12 @@ class KubernetesService < DeploymentService ...@@ -104,7 +104,12 @@ class KubernetesService < DeploymentService
{ success: false, result: err } { success: false, result: err }
end end
def predefined_variables # Project param was added on
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011,
# as a way to keep this service compatible with
# Clusters::Platforms::Kubernetes, it won't be used on this method
# as it's only needed for Clusters::Cluster.
def predefined_variables(project:)
config = YAML.dump(kubeconfig) config = YAML.dump(kubeconfig)
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
......
...@@ -11,8 +11,9 @@ module Clusters ...@@ -11,8 +11,9 @@ module Clusters
configure_provider configure_provider
create_gitlab_service_account! create_gitlab_service_account!
configure_kubernetes configure_kubernetes
cluster.save! cluster.save!
configure_project_service_account
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
rescue Kubeclient::HttpError => e rescue Kubeclient::HttpError => e
...@@ -24,7 +25,10 @@ module Clusters ...@@ -24,7 +25,10 @@ module Clusters
private private
def create_gitlab_service_account! def create_gitlab_service_account!
Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute Clusters::Gcp::Kubernetes::CreateServiceAccountService.gitlab_creator(
kube_client,
rbac: create_rbac_cluster?
).execute
end end
def configure_provider def configure_provider
...@@ -44,7 +48,20 @@ module Clusters ...@@ -44,7 +48,20 @@ module Clusters
end end
def request_kubernetes_token def request_kubernetes_token
Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(
kube_client,
Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
).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 end
def authorization_type def authorization_type
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
module Clusters module Clusters
module Gcp module Gcp
module Kubernetes module Kubernetes
SERVICE_ACCOUNT_NAME = 'gitlab' GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab'
SERVICE_ACCOUNT_NAMESPACE = 'default' GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default'
SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token' GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token'
CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
CLUSTER_ROLE_NAME = 'cluster-admin' GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
PROJECT_CLUSTER_ROLE_NAME = 'edit'
end end
end end
end end
# frozen_string_literal: true
module Clusters
module Gcp
module Kubernetes
class CreateOrUpdateNamespaceService
def initialize(cluster:, kubernetes_namespace:)
@cluster = cluster
@kubernetes_namespace = kubernetes_namespace
@platform = cluster.platform
end
def execute
configure_kubernetes_namespace
create_project_service_account
configure_kubernetes_token
kubernetes_namespace.save!
rescue ::Kubeclient::HttpError => err
raise err unless err.error_code = 404
end
private
attr_reader :cluster, :kubernetes_namespace, :platform
def configure_kubernetes_namespace
kubernetes_namespace.configure_predefined_credentials
end
def create_project_service_account
Clusters::Gcp::Kubernetes::CreateServiceAccountService.namespace_creator(
platform.kubeclient,
service_account_name: kubernetes_namespace.service_account_name,
service_account_namespace: kubernetes_namespace.namespace,
rbac: platform.rbac?
).execute
end
def configure_kubernetes_token
kubernetes_namespace.service_account_token = fetch_service_account_token
end
def fetch_service_account_token
Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(
platform.kubeclient,
kubernetes_namespace.token_name,
kubernetes_namespace.namespace
).execute
end
end
end
end
end
...@@ -4,46 +4,96 @@ module Clusters ...@@ -4,46 +4,96 @@ module Clusters
module Gcp module Gcp
module Kubernetes module Kubernetes
class CreateServiceAccountService class CreateServiceAccountService
attr_reader :kubeclient, :rbac def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil)
def initialize(kubeclient, rbac:)
@kubeclient = kubeclient @kubeclient = kubeclient
@service_account_name = service_account_name
@service_account_namespace = service_account_namespace
@token_name = token_name
@rbac = rbac @rbac = rbac
@namespace_creator = namespace_creator
@role_binding_name = role_binding_name
end
def self.gitlab_creator(kubeclient, rbac:)
self.new(
kubeclient,
service_account_name: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME,
service_account_namespace: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE,
token_name: Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
rbac: rbac
)
end
def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:)
self.new(
kubeclient,
service_account_name: service_account_name,
service_account_namespace: service_account_namespace,
token_name: "#{service_account_namespace}-token",
rbac: rbac,
namespace_creator: true,
role_binding_name: "gitlab-#{service_account_namespace}"
)
end end
def execute def execute
ensure_project_namespace_exists if namespace_creator
kubeclient.create_service_account(service_account_resource) kubeclient.create_service_account(service_account_resource)
kubeclient.create_secret(service_account_token_resource) kubeclient.create_secret(service_account_token_resource)
kubeclient.create_cluster_role_binding(cluster_role_binding_resource) if rbac create_role_or_cluster_role_binding if rbac
end end
private private
attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name
def ensure_project_namespace_exists
Gitlab::Kubernetes::Namespace.new(
service_account_namespace,
kubeclient
).ensure_exists!
end
def create_role_or_cluster_role_binding
if namespace_creator
kubeclient.create_role_binding(role_binding_resource)
else
kubeclient.create_cluster_role_binding(cluster_role_binding_resource)
end
end
def service_account_resource def service_account_resource
Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate Gitlab::Kubernetes::ServiceAccount.new(
service_account_name,
service_account_namespace
).generate
end end
def service_account_token_resource def service_account_token_resource
Gitlab::Kubernetes::ServiceAccountToken.new( Gitlab::Kubernetes::ServiceAccountToken.new(
SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_namespace).generate token_name,
service_account_name,
service_account_namespace
).generate
end end
def cluster_role_binding_resource def cluster_role_binding_resource
subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }] subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
Gitlab::Kubernetes::ClusterRoleBinding.new( Gitlab::Kubernetes::ClusterRoleBinding.new(
CLUSTER_ROLE_BINDING_NAME, Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME,
CLUSTER_ROLE_NAME, Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME,
subjects subjects
).generate ).generate
end end
def service_account_name def role_binding_resource
SERVICE_ACCOUNT_NAME Gitlab::Kubernetes::RoleBinding.new(
end name: role_binding_name,
role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME,
def service_account_namespace namespace: service_account_namespace,
SERVICE_ACCOUNT_NAMESPACE service_account_name: service_account_name
).generate
end end
end end
end end
......
...@@ -4,10 +4,12 @@ module Clusters ...@@ -4,10 +4,12 @@ module Clusters
module Gcp module Gcp
module Kubernetes module Kubernetes
class FetchKubernetesTokenService class FetchKubernetesTokenService
attr_reader :kubeclient attr_reader :kubeclient, :service_account_token_name, :namespace
def initialize(kubeclient) def initialize(kubeclient, service_account_token_name, namespace)
@kubeclient = kubeclient @kubeclient = kubeclient
@service_account_token_name = service_account_token_name
@namespace = namespace
end end
def execute def execute
...@@ -18,7 +20,7 @@ module Clusters ...@@ -18,7 +20,7 @@ module Clusters
private private
def get_secret def get_secret
kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json kubeclient.get_secret(service_account_token_name, namespace).as_json
rescue Kubeclient::HttpError => err rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404 raise err unless err.error_code == 404
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
- gcp_cluster:cluster_wait_for_app_installation - gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:wait_for_cluster_creation - gcp_cluster:wait_for_cluster_creation
- gcp_cluster:cluster_wait_for_ingress_ip_address - gcp_cluster:cluster_wait_for_ingress_ip_address
- gcp_cluster:cluster_platform_configure
- github_import_advance_stage - github_import_advance_stage
- github_importer:github_import_import_diff_note - github_importer:github_import_import_diff_note
......
# frozen_string_literal: true
class ClusterPlatformConfigureWorker
include ApplicationWorker
include ClusterQueue
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
end
rescue ::Kubeclient::HttpError => err
Rails.logger.error "Failed to create/update Kubernetes Namespace. id: #{kubernetes_namespace.id} message: #{err.message}"
end
end
...@@ -9,6 +9,8 @@ class ClusterProvisionWorker ...@@ -9,6 +9,8 @@ class ClusterProvisionWorker
cluster.provider.try do |provider| cluster.provider.try do |provider|
Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp? Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp?
end end
ClusterPlatformConfigureWorker.perform_async(cluster.id) if cluster.user?
end end
end end
end end
---
title: Extend RBAC by having a service account restricted to project's namespace
merge_request: 22011
author:
type: other
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
module Gitlab module Gitlab
module Kubernetes module Kubernetes
class RoleBinding class RoleBinding
attr_reader :role_name, :namespace, :service_account_name def initialize(name:, role_name:, namespace:, service_account_name:)
@name = name
def initialize(role_name:, namespace:, service_account_name:)
@role_name = role_name @role_name = role_name
@namespace = namespace @namespace = namespace
@service_account_name = service_account_name @service_account_name = service_account_name
...@@ -21,14 +20,16 @@ module Gitlab ...@@ -21,14 +20,16 @@ module Gitlab
private private
attr_reader :name, :role_name, :namespace, :service_account_name
def metadata def metadata
{ name: "gitlab-#{namespace}", namespace: namespace } { name: name, namespace: namespace }
end end
def role_ref def role_ref
{ {
apiGroup: 'rbac.authorization.k8s.io', apiGroup: 'rbac.authorization.k8s.io',
kind: 'Role', kind: 'ClusterRole',
name: role_name name: role_name
} }
end end
......
...@@ -5,6 +5,7 @@ require 'spec_helper' ...@@ -5,6 +5,7 @@ require 'spec_helper'
describe Projects::ClustersController do describe Projects::ClustersController do
include AccessMatchersForController include AccessMatchersForController
include GoogleApi::CloudPlatformHelpers include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers
set(:project) { create(:project) } set(:project) { create(:project) }
...@@ -309,6 +310,11 @@ describe Projects::ClustersController do ...@@ -309,6 +310,11 @@ describe Projects::ClustersController do
end end
describe 'security' do describe 'security' do
before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(project) } it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:maintainer).of(project) } it { expect { go }.to be_allowed_for(:maintainer).of(project) }
...@@ -412,6 +418,11 @@ describe Projects::ClustersController do ...@@ -412,6 +418,11 @@ describe Projects::ClustersController do
) )
end end
before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
context 'when cluster is provided by GCP' do context 'when cluster is provided by GCP' do
it "updates and redirects back to show page" do it "updates and redirects back to show page" do
go go
......
...@@ -2,8 +2,18 @@ ...@@ -2,8 +2,18 @@
FactoryBot.define do FactoryBot.define do
factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do
cluster association :cluster, :project, :provided_by_gcp
project namespace { |n| "environment#{n}" }
cluster_project
after(:build) do |kubernetes_namespace|
cluster_project = kubernetes_namespace.cluster.cluster_project
kubernetes_namespace.project = cluster_project.project
kubernetes_namespace.cluster_project = cluster_project
end
trait :with_token do
service_account_token { Faker::Lorem.characters(10) }
end
end end
end end
...@@ -130,6 +130,7 @@ describe 'Gcp Cluster', :js do ...@@ -130,6 +130,7 @@ describe 'Gcp Cluster', :js do
context 'when user changes cluster parameters' do context 'when user changes cluster parameters' do
before do before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
page.within('#js-cluster-details') { click_button 'Save changes' } page.within('#js-cluster-details') { click_button 'Save changes' }
end end
......
...@@ -9,7 +9,9 @@ describe 'User Cluster', :js do ...@@ -9,7 +9,9 @@ describe 'User Cluster', :js do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
gitlab_sign_in(user) gitlab_sign_in(user)
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
end end
context 'when user does not have a cluster and visits cluster index page' do context 'when user does not have a cluster and visits cluster index page' do
......
...@@ -20,7 +20,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do ...@@ -20,7 +20,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_ref) do let(:role_ref) do
{ {
apiGroup: 'rbac.authorization.k8s.io', apiGroup: 'rbac.authorization.k8s.io',
kind: 'Role', kind: 'ClusterRole',
name: role_name name: role_name
} }
end end
...@@ -35,6 +35,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do ...@@ -35,6 +35,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
subject do subject do
described_class.new( described_class.new(
name: "gitlab-#{namespace}",
role_name: role_name, role_name: role_name,
namespace: namespace, namespace: namespace,
service_account_name: service_account_name service_account_name: service_account_name
......
...@@ -109,14 +109,20 @@ describe Clusters::Applications::Prometheus do ...@@ -109,14 +109,20 @@ describe Clusters::Applications::Prometheus do
end end
context 'cluster has kubeclient' do context 'cluster has kubeclient' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url } let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url }
let(:kube_client) { subject.cluster.kubeclient.core_client } let(:kube_client) { subject.cluster.kubeclient.core_client }
subject { create(:clusters_applications_prometheus) } subject { create(:clusters_applications_prometheus, cluster: cluster) }
before do before do
subject.cluster.platform_kubernetes.namespace = 'a-namespace' subject.cluster.platform_kubernetes.namespace = 'a-namespace'
stub_kubeclient_discover(subject.cluster.platform_kubernetes.api_url) stub_kubeclient_discover(cluster.platform_kubernetes.api_url)
create(:cluster_kubernetes_namespace,
cluster: cluster,
cluster_project: cluster.cluster_project,
project: cluster.cluster_project.project)
end end
it 'creates proxy prometheus rest client' do it 'creates proxy prometheus rest client' do
......
...@@ -16,6 +16,7 @@ describe Clusters::Cluster do ...@@ -16,6 +16,7 @@ describe Clusters::Cluster do
it { is_expected.to have_one(:application_runner) } it { is_expected.to have_one(:application_runner) }
it { is_expected.to have_many(:kubernetes_namespaces) } it { is_expected.to have_many(:kubernetes_namespaces) }
it { is_expected.to have_one(:kubernetes_namespace) } it { is_expected.to have_one(:kubernetes_namespace) }
it { is_expected.to have_one(:cluster_project) }
it { is_expected.to delegate_method(:status).to(:provider) } 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_reason).to(:provider) }
......
...@@ -10,23 +10,15 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do ...@@ -10,23 +10,15 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
describe 'namespace uniqueness validation' do describe 'namespace uniqueness validation' do
let(:cluster_project) { create(:cluster_project) } let(:cluster_project) { create(:cluster_project) }
let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
let(:kubernetes_namespace) do
build(:cluster_kubernetes_namespace,
cluster: cluster_project.cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
subject { kubernetes_namespace } subject { kubernetes_namespace }
context 'when cluster is using the namespace' do context 'when cluster is using the namespace' do
before do before do
create(:cluster_kubernetes_namespace, create(:cluster_kubernetes_namespace,
cluster: cluster_project.cluster, cluster: kubernetes_namespace.cluster,
project: cluster_project.project, namespace: 'my-namespace')
cluster_project: cluster_project,
namespace: kubernetes_namespace.namespace)
end end
it { is_expected.not_to be_valid } it { is_expected.not_to be_valid }
...@@ -37,48 +29,79 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do ...@@ -37,48 +29,79 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
end end
end end
describe '#set_namespace_and_service_account_to_default' do describe '#configure_predefined_variables' do
let(:cluster) { platform.cluster } let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace) }
let(:cluster_project) { create(:cluster_project, cluster: cluster) } let(:cluster) { kubernetes_namespace.cluster }
let(:kubernetes_namespace) do let(:platform) { kubernetes_namespace.platform_kubernetes }
create(:cluster_kubernetes_namespace,
cluster: cluster_project.cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
describe 'namespace' do subject { kubernetes_namespace.configure_predefined_credentials }
let(:platform) { create(:cluster_platform_kubernetes, namespace: namespace) }
subject { kubernetes_namespace.namespace } describe 'namespace' do
before do
platform.update_column(:namespace, namespace)
end
context 'when platform has a namespace assigned' do context 'when platform has a namespace assigned' do
let(:namespace) { 'platform-namespace' } let(:namespace) { 'platform-namespace' }
it 'should copy the namespace' do it 'should copy the namespace' do
is_expected.to eq('platform-namespace') subject
expect(kubernetes_namespace.namespace).to eq('platform-namespace')
end end
end end
context 'when platform does not have namespace assigned' do context 'when platform does not have namespace assigned' do
let(:project) { kubernetes_namespace.project }
let(:namespace) { nil } let(:namespace) { nil }
let(:project_slug) { "#{project.path}-#{project.id}" }
it 'should set default namespace' do it 'should fallback to project namespace' do
project_slug = "#{cluster_project.project.path}-#{cluster_project.project_id}" subject
is_expected.to eq(project_slug) expect(kubernetes_namespace.namespace).to eq(project_slug)
end end
end end
end end
describe 'service_account_name' do describe 'service_account_name' do
let(:platform) { create(:cluster_platform_kubernetes) } let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" }
subject { kubernetes_namespace.service_account_name }
it 'should set a service account name based on namespace' do it 'should set a service account name based on namespace' do
is_expected.to eq("#{kubernetes_namespace.namespace}-service-account") subject
expect(kubernetes_namespace.service_account_name).to eq(service_account_name)
end end
end end
end end
describe '#predefined_variables' do
let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, service_account_token: token) }
let(:cluster) { create(:cluster, :project, platform_kubernetes: platform) }
let(:platform) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) }
let(:api_url) { 'https://kube.domain.com' }
let(:ca_pem) { 'CA PEM DATA' }
let(:token) { 'token' }
let(:kubeconfig) do
config_file = expand_fixture_path('config/kubeconfig.yml')
config = YAML.safe_load(File.read(config_file))
config.dig('users', 0, 'user')['token'] = token
config.dig('contexts', 0, 'context')['namespace'] = kubernetes_namespace.namespace
config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
Base64.strict_encode64(ca_pem)
YAML.dump(config)
end
it 'sets the variables' do
expect(kubernetes_namespace.predefined_variables).to include(
{ key: 'KUBE_SERVICE_ACCOUNT', value: kubernetes_namespace.service_account_name, public: true },
{ key: 'KUBE_NAMESPACE', value: kubernetes_namespace.namespace, public: true },
{ key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false },
{ key: 'KUBECONFIG', value: kubeconfig, public: false, file: true }
)
end
end
end end
...@@ -124,9 +124,17 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching ...@@ -124,9 +124,17 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end end
describe '#kubeclient' do describe '#kubeclient' do
let(:cluster) { create(:cluster, :project) }
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace', cluster: cluster) }
subject { kubernetes.kubeclient } subject { kubernetes.kubeclient }
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace') } before do
create(:cluster_kubernetes_namespace,
cluster: kubernetes.cluster,
cluster_project: kubernetes.cluster.cluster_project,
project: kubernetes.cluster.cluster_project.project)
end
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) } it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
end end
...@@ -186,29 +194,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching ...@@ -186,29 +194,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
describe '#predefined_variables' do describe '#predefined_variables' do
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) } let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) } let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) }
let(:api_url) { 'https://kube.domain.com' } let(:api_url) { 'https://kube.domain.com' }
let(:ca_pem) { 'CA PEM DATA' } let(:ca_pem) { 'CA PEM DATA' }
let(:token) { 'token' }
let(:kubeconfig) do
config_file = expand_fixture_path('config/kubeconfig.yml')
config = YAML.load(File.read(config_file))
config.dig('users', 0, 'user')['token'] = token
config.dig('contexts', 0, 'context')['namespace'] = namespace
config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
Base64.strict_encode64(ca_pem)
YAML.dump(config)
end
shared_examples 'setting variables' do shared_examples 'setting variables' do
it 'sets the variables' do it 'sets the variables' do
expect(kubernetes.predefined_variables).to include( expect(kubernetes.predefined_variables(project: cluster.project)).to include(
{ key: 'KUBE_URL', value: api_url, public: true }, { key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
{ key: 'KUBE_NAMESPACE', value: namespace, public: true },
{ key: 'KUBECONFIG', value: kubeconfig, public: false, file: true },
{ key: 'KUBE_CA_PEM', value: ca_pem, public: true }, { key: 'KUBE_CA_PEM', value: ca_pem, public: true },
{ key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true } { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
) )
...@@ -229,13 +222,6 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching ...@@ -229,13 +222,6 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
let(:namespace) { kubernetes.actual_namespace } let(:namespace) { kubernetes.actual_namespace }
it_behaves_like 'setting variables' it_behaves_like 'setting variables'
it 'sets the KUBE_NAMESPACE' do
kube_namespace = kubernetes.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
expect(kube_namespace).not_to be_nil
expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
end
end end
end end
...@@ -319,4 +305,27 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching ...@@ -319,4 +305,27 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to include(pods: []) } it { is_expected.to include(pods: []) }
end end
end end
describe '#update_kubernetes_namespace' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
let(:platform) { cluster.platform }
context 'when namespace is updated' do
it 'should call ConfigureWorker' do
expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id).once
platform.namespace = 'new-namespace'
platform.save
end
end
context 'when namespace is not updated' do
it 'should not call ConfigureWorker' do
expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async)
platform.username = "new-username"
platform.save
end
end
end
end end
...@@ -253,7 +253,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do ...@@ -253,7 +253,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end end
end end
describe '#predefined_variables' do describe '#predefined_variable' do
let(:kubeconfig) do let(:kubeconfig) do
config_file = expand_fixture_path('config/kubeconfig.yml') config_file = expand_fixture_path('config/kubeconfig.yml')
config = YAML.load(File.read(config_file)) config = YAML.load(File.read(config_file))
...@@ -274,7 +274,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do ...@@ -274,7 +274,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
shared_examples 'setting variables' do shared_examples 'setting variables' do
it 'sets the variables' do it 'sets the variables' do
expect(subject.predefined_variables).to include( expect(subject.predefined_variables(project: project)).to include(
{ key: 'KUBE_URL', value: 'https://kube.domain.com', public: true }, { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
{ key: 'KUBE_TOKEN', value: 'token', public: false }, { key: 'KUBE_TOKEN', value: 'token', public: false },
{ key: 'KUBE_NAMESPACE', value: namespace, public: true }, { key: 'KUBE_NAMESPACE', value: namespace, public: true },
...@@ -301,7 +301,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do ...@@ -301,7 +301,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
it_behaves_like 'setting variables' it_behaves_like 'setting variables'
it 'sets the KUBE_NAMESPACE' do it 'sets the KUBE_NAMESPACE' do
kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' } kube_namespace = subject.predefined_variables(project: project).find { |h| h[:key] == 'KUBE_NAMESPACE' }
expect(kube_namespace).not_to be_nil expect(kube_namespace).not_to be_nil
expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
......
...@@ -2405,12 +2405,24 @@ describe Project do ...@@ -2405,12 +2405,24 @@ describe Project do
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end end
context 'when user configured kubernetes from CI/CD > Clusters' do context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project } let(:project) { cluster.project }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end end
context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do
let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace) }
let!(:cluster) { kubernetes_namespace.cluster }
let(:project) { kubernetes_namespace.project }
it 'should return token from kubernetes namespace' do
expect(project.deployment_variables).to include(
{ key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
)
end
end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Clusters::Gcp::FinalizeCreationService do describe Clusters::Gcp::FinalizeCreationService, '#execute' do
include GoogleApi::CloudPlatformHelpers include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers include KubernetesHelpers
describe '#execute' do let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
let(:cluster) { create(:cluster, :project, :providing_by_gcp) } let(:provider) { cluster.provider }
let(:provider) { cluster.provider } let(:platform) { cluster.platform }
let(:platform) { cluster.platform } let(:endpoint) { '111.111.111.111' }
let(:gcp_project_id) { provider.gcp_project_id } let(:api_url) { 'https://' + endpoint }
let(:zone) { provider.zone } let(:username) { 'sample-username' }
let(:cluster_name) { cluster.name } let(:password) { 'sample-password' }
let(:secret_name) { 'gitlab-token' }
let(:token) { 'sample-token' }
let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" }
subject { described_class.new.execute(provider) } subject { described_class.new.execute(provider) }
shared_examples 'success' do shared_examples 'success' do
it 'configures provider and kubernetes' do it 'configures provider and kubernetes' do
subject subject
expect(provider).to be_created expect(provider).to be_created
end
end end
shared_examples 'error' do it 'properly configures database models' do
it 'sets an error to provider object' do subject
subject
expect(provider.reload).to be_errored cluster.reload
end
expect(provider.endpoint).to eq(endpoint)
expect(platform.api_url).to eq(api_url)
expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert))
expect(platform.username).to eq(username)
expect(platform.password).to eq(password)
expect(platform.token).to eq(token)
end
it 'creates kubernetes namespace model' do
subject
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
end end
end
shared_examples 'error' do
it 'sets an error to provider object' do
subject
context 'when succeeded to fetch gke cluster info' do expect(provider.reload).to be_errored
let(:endpoint) { '111.111.111.111' } end
let(:api_url) { 'https://' + endpoint } end
let(:username) { 'sample-username' }
let(:password) { 'sample-password' }
let(:secret_name) { 'gitlab-token' }
shared_examples 'kubernetes information not successfully fetched' do
context 'when failed to fetch gke cluster info' do
before do before do
stub_cloud_platform_get_zone_cluster( stub_cloud_platform_get_zone_cluster_error(provider.gcp_project_id, provider.zone, cluster.name)
gcp_project_id, zone, cluster_name,
{
endpoint: endpoint,
username: username,
password: password
}
)
end end
context 'service account and token created' do it_behaves_like 'error'
before do end
stub_kubeclient_discover(api_url)
stub_kubeclient_create_service_account(api_url) context 'when token is empty' do
stub_kubeclient_create_secret(api_url) let(:token) { '' }
end
it_behaves_like 'error'
shared_context 'kubernetes token successfully fetched' do end
let(:token) { 'sample-token' }
context 'when failed to fetch kubernetes token' do
before do before do
stub_kubeclient_get_secret( stub_kubeclient_get_secret_error(api_url, secret_name, namespace: 'default')
api_url,
{
metadata_name: secret_name,
token: Base64.encode64(token)
} )
end
end
context 'provider legacy_abac is enabled' do
include_context 'kubernetes token successfully fetched'
it_behaves_like 'success'
it 'properly configures database models' do
subject
cluster.reload
expect(provider.endpoint).to eq(endpoint)
expect(platform.api_url).to eq(api_url)
expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert))
expect(platform.username).to eq(username)
expect(platform.password).to eq(password)
expect(platform).to be_abac
expect(platform.authorization_type).to eq('abac')
expect(platform.token).to eq(token)
end
end
context 'provider legacy_abac is disabled' do
before do
provider.legacy_abac = false
end
include_context 'kubernetes token successfully fetched'
context 'cluster role binding created' do
before do
stub_kubeclient_create_cluster_role_binding(api_url)
end
it_behaves_like 'success'
it 'properly configures database models' do
subject
cluster.reload
expect(provider.endpoint).to eq(endpoint)
expect(platform.api_url).to eq(api_url)
expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert))
expect(platform.username).to eq(username)
expect(platform.password).to eq(password)
expect(platform).to be_rbac
expect(platform.token).to eq(token)
end
end
end
context 'when token is empty' do
before do
stub_kubeclient_get_secret(api_url, token: '', metadata_name: secret_name)
end
it_behaves_like 'error'
end
context 'when failed to fetch kubernetes token' do
before do
stub_kubeclient_get_secret_error(api_url, secret_name)
end
it_behaves_like 'error'
end
context 'when service account fails to create' do
before do
stub_kubeclient_create_service_account_error(api_url)
end
it_behaves_like 'error'
end
end end
it_behaves_like 'error'
end end
context 'when failed to fetch gke cluster info' do context 'when service account fails to create' do
before do before do
stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name) stub_kubeclient_create_service_account_error(api_url, namespace: 'default')
end end
it_behaves_like 'error' it_behaves_like 'error'
end end
end end
shared_context 'kubernetes information successfully fetched' do
before do
stub_cloud_platform_get_zone_cluster(
provider.gcp_project_id, provider.zone, cluster.name,
{
endpoint: endpoint,
username: username,
password: password
}
)
stub_kubeclient_discover(api_url)
stub_kubeclient_get_namespace(api_url)
stub_kubeclient_create_namespace(api_url)
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_create_secret(api_url)
stub_kubeclient_get_secret(
api_url,
{
metadata_name: secret_name,
token: Base64.encode64(token),
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
context 'With a legacy ABAC cluster' do
before do
provider.legacy_abac = true
end
include_context 'kubernetes information successfully fetched'
it_behaves_like 'success'
it 'uses ABAC authorization type' do
subject
cluster.reload
expect(platform).to be_abac
expect(platform.authorization_type).to eq('abac')
end
it_behaves_like 'kubernetes information not successfully fetched'
end
context 'With an RBAC cluster' do
before do
provider.legacy_abac = false
stub_kubeclient_create_cluster_role_binding(api_url)
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
end
include_context 'kubernetes information successfully fetched'
it_behaves_like 'success'
it 'uses RBAC authorization type' do
subject
cluster.reload
expect(platform).to be_rbac
expect(platform.authorization_type).to eq('rbac')
end
it_behaves_like 'kubernetes information not successfully fetched'
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
include KubernetesHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:platform) { cluster.platform }
let(:api_url) { 'https://kubernetes.example.com' }
let(:project) { cluster.project }
let(:cluster_project) { cluster.cluster_project }
subject do
described_class.new(
cluster: cluster,
kubernetes_namespace: kubernetes_namespace
).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
end
context 'when kubernetes namespace is not persisted' do
let(:namespace) { "#{project.path}-#{project.id}" }
let(:kubernetes_namespace) do
build(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
include_context 'kubernetes requests'
it 'creates a Clusters::KubernetesNamespace' do
expect do
subject
end.to change(Clusters::KubernetesNamespace, :count).by(1)
end
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once
subject
end
it 'configures kubernetes token' 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
context 'when there is a Kubernetes Namespace associated' do
let(:namespace) { 'new-namespace' }
let(:kubernetes_namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
cluster_project: cluster_project)
end
include_context 'kubernetes requests'
before do
platform.update_column(:namespace, 'new-namespace')
end
it 'does not create any Clusters::KubernetesNamespace' do
subject
expect(cluster.kubernetes_namespace).to eq(kubernetes_namespace)
end
it 'creates project service account' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once
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
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
include KubernetesHelpers include KubernetesHelpers
let(:service) { described_class.new(kubeclient, rbac: rbac) } let(:api_url) { 'http://111.111.111.111' }
let(:platform_kubernetes) { cluster.platform_kubernetes }
let(:cluster_project) { cluster.cluster_project }
let(:project) { cluster_project.project }
let(:cluster) do
create(:cluster,
:project, :provided_by_gcp,
platform_kubernetes: create(:cluster_platform_kubernetes, :configured))
end
let(:kubeclient) do
Gitlab::Kubernetes::KubeClient.new(
api_url,
auth_options: { username: 'admin', password: 'xxx' }
)
end
describe '#execute' do shared_examples 'creates service account and token' do
let(:rbac) { false } it 'creates a kubernetes service account' do
let(:api_url) { 'http://111.111.111.111' } subject
let(:username) { 'admin' }
let(:password) { 'xxx' } expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts").with(
body: hash_including(
kind: 'ServiceAccount',
metadata: { name: service_account_name, namespace: namespace }
)
)
end
let(:kubeclient) do it 'creates a kubernetes secret' do
Gitlab::Kubernetes::KubeClient.new( subject
api_url,
auth_options: { username: username, password: password } expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with(
body: hash_including(
kind: 'Secret',
metadata: {
name: token_name,
namespace: namespace,
annotations: {
'kubernetes.io/service-account.name': service_account_name
}
},
type: 'kubernetes.io/service-account-token'
)
) )
end end
end
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_create_secret(api_url, namespace: namespace)
end
describe '.gitlab_creator' do
let(:namespace) { 'default' }
let(:service_account_name) { 'gitlab' }
let(:token_name) { 'gitlab-token' }
subject { described_class.gitlab_creator(kubeclient, rbac: rbac).execute }
context 'with ABAC cluster' do
let(:rbac) { false }
it_behaves_like 'creates service account and token'
end
subject { service.execute } context 'with RBAC cluster' do
let(:rbac) { true }
context 'when params are correct' do
before do before do
stub_kubeclient_discover(api_url) cluster.platform_kubernetes.rbac!
stub_kubeclient_create_service_account(api_url)
stub_kubeclient_create_secret(api_url)
end
shared_examples 'creates service account and token' do stub_kubeclient_create_cluster_role_binding(api_url)
it 'creates a kubernetes service account' do end
subject
expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/serviceaccounts').with( it_behaves_like 'creates service account and token'
body: hash_including(
kind: 'ServiceAccount', it 'should create a cluster role binding with cluster-admin access' do
metadata: { name: 'gitlab', namespace: 'default' } subject
)
) expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings").with(
end body: hash_including(
kind: 'ClusterRoleBinding',
it 'creates a kubernetes secret of type ServiceAccountToken' do metadata: { name: 'gitlab-admin' },
subject roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/secrets').with( kind: 'ClusterRole',
body: hash_including( name: 'cluster-admin'
kind: 'Secret', },
metadata: { subjects: [
name: 'gitlab-token', {
namespace: 'default', kind: 'ServiceAccount',
annotations: { name: service_account_name,
'kubernetes.io/service-account.name': 'gitlab' namespace: namespace
} }
}, ]
type: 'kubernetes.io/service-account-token'
)
) )
end )
end end
end
end
describe '.namespace_creator' do
let(:namespace) { "#{project.path}-#{project.id}" }
let(:service_account_name) { "#{namespace}-service-account" }
let(:token_name) { "#{namespace}-token" }
subject do
described_class.namespace_creator(
kubeclient,
service_account_name: service_account_name,
service_account_namespace: namespace,
rbac: rbac
).execute
end
context 'with ABAC cluster' do
let(:rbac) { false }
it_behaves_like 'creates service account and token'
end
context 'With RBAC enabled cluster' do
let(:rbac) { true }
before do
cluster.platform_kubernetes.rbac!
context 'abac enabled cluster' do stub_kubeclient_create_role_binding(api_url, namespace: namespace)
it_behaves_like 'creates service account and token'
end end
context 'rbac enabled cluster' do it_behaves_like 'creates service account and token'
let(:rbac) { true }
it 'creates a namespaced role binding with edit access' do
before do subject
stub_kubeclient_create_cluster_role_binding(api_url)
end expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings").with(
body: hash_including(
it_behaves_like 'creates service account and token' kind: 'RoleBinding',
metadata: { name: "gitlab-#{namespace}", namespace: "#{namespace}" },
it 'creates a kubernetes cluster role binding' do roleRef: {
subject apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
expect(WebMock).to have_requested(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings').with( name: 'edit'
body: hash_including( },
kind: 'ClusterRoleBinding', subjects: [
metadata: { name: 'gitlab-admin' }, {
roleRef: { kind: 'ServiceAccount',
apiGroup: 'rbac.authorization.k8s.io', name: service_account_name,
kind: 'ClusterRole', namespace: namespace
name: 'cluster-admin' }
}, ]
subjects: [{ kind: 'ServiceAccount', namespace: 'default', name: 'gitlab' }]
)
) )
end )
end end
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
require 'fast_spec_helper' require 'spec_helper'
describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do
include KubernetesHelpers
describe '#execute' do describe '#execute' do
let(:api_url) { 'http://111.111.111.111' } let(:api_url) { 'http://111.111.111.111' }
let(:username) { 'admin' } let(:namespace) { 'my-namespace' }
let(:password) { 'xxx' } let(:service_account_token_name) { 'gitlab-token' }
let(:kubeclient) do let(:kubeclient) do
Gitlab::Kubernetes::KubeClient.new( Gitlab::Kubernetes::KubeClient.new(
api_url, api_url,
auth_options: { username: username, password: password } auth_options: { username: 'admin', password: 'xxx' }
) )
end end
subject { described_class.new(kubeclient).execute } subject { described_class.new(kubeclient, service_account_token_name, namespace).execute }
context 'when params correct' do context 'when params correct' do
let(:decoded_token) { 'xxx.token.xxx' } let(:decoded_token) { 'xxx.token.xxx' }
let(:token) { Base64.encode64(decoded_token) } let(:token) { Base64.encode64(decoded_token) }
let(:secret_json) do
{
'metadata': {
name: 'gitlab-token'
},
'data': {
'token': token
}
}
end
before do
allow_any_instance_of(Kubeclient::Client)
.to receive(:get_secret).and_return(secret_json)
end
context 'when gitlab-token exists' do context 'when gitlab-token exists' do
let(:metadata_name) { 'gitlab-token' } before do
stub_kubeclient_discover(api_url)
stub_kubeclient_get_secret(
api_url,
{
metadata_name: service_account_token_name,
namespace: namespace,
token: token
}
)
end
it { is_expected.to eq(decoded_token) } it { is_expected.to eq(decoded_token) }
end end
context 'when gitlab-token does not exist' do context 'when gitlab-token does not exist' do
let(:secret_json) { {} } before do
allow(kubeclient).to receive(:get_secret).and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
it { is_expected.to be_nil } end
end
context 'when token is nil' do
let(:token) { nil }
it { is_expected.to be_nil } it { is_expected.to be_nil }
end end
......
require 'spec_helper' require 'spec_helper'
describe Clusters::UpdateService do describe Clusters::UpdateService do
include KubernetesHelpers
describe '#execute' do describe '#execute' do
subject { described_class.new(cluster.user, params).execute(cluster) } subject { described_class.new(cluster.user, params).execute(cluster) }
...@@ -34,6 +36,11 @@ describe Clusters::UpdateService do ...@@ -34,6 +36,11 @@ describe Clusters::UpdateService do
} }
end end
before do
allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
it 'updates namespace' do it 'updates namespace' do
is_expected.to eq(true) is_expected.to eq(true)
expect(cluster.platform.namespace).to eq('custom-namespace') expect(cluster.platform.namespace).to eq('custom-namespace')
......
# frozen_string_literal: true
require 'spec_helper'
describe ClusterPlatformConfigureWorker, '#execute' do
context 'when provider type is gcp' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
it 'configures kubernetes platform' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
described_class.new.perform(cluster.id)
end
end
context 'when provider type is user' do
let(:cluster) { create(:cluster, :project, :provided_by_user) }
it 'configures kubernetes platform' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
described_class.new.perform(cluster.id)
end
end
context 'when cluster does not exist' do
it 'does not provision a cluster' do
expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute)
described_class.new.perform(123)
end
end
end
...@@ -14,18 +14,25 @@ describe ClusterProvisionWorker do ...@@ -14,18 +14,25 @@ describe ClusterProvisionWorker do
end end
context 'when provider type is user' do context 'when provider type is user' do
let(:cluster) { create(:cluster, provider_type: :user) } let(:cluster) { create(:cluster, :provided_by_user) }
it 'does not provision a cluster' do it 'does not provision a cluster' do
expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute) expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute)
described_class.new.perform(cluster.id) described_class.new.perform(cluster.id)
end end
it 'configures kubernetes platform' do
expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id)
described_class.new.perform(cluster.id)
end
end end
context 'when cluster does not exist' do context 'when cluster does not exist' do
it 'does not provision a cluster' do it 'does not provision a cluster' do
expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute) expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute)
expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async)
described_class.new.perform(123) described_class.new.perform(123)
end end
......
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