summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorThong Kuah <tkuah@gitlab.com>2018-09-06 10:03:38 +0000
committerKamil Trzciński <ayufan@ayufan.eu>2018-09-06 10:03:38 +0000
commit6f2ad2b6041b8a007df7eb8c4f477c24cc153ac3 (patch)
tree7b190f17b6da295cf3599174f48c0fbc060ddbb1 /spec
parenta2ea32dd44cc4a104e404325c73a77151913a946 (diff)
downloadgitlab-ce-6f2ad2b6041b8a007df7eb8c4f477c24cc153ac3.tar.gz
Enable Kubernetes RBAC for GitLab Managed Apps for existing clusters
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb32
-rw-r--r--spec/factories/clusters/platforms/kubernetes.rb4
-rw-r--r--spec/features/projects/clusters/user_spec.rb22
-rw-r--r--spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb35
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb109
-rw-r--r--spec/lib/gitlab/kubernetes/helm/base_command_spec.rb20
-rw-r--r--spec/lib/gitlab/kubernetes/helm/init_command_spec.rb130
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb155
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb17
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb247
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_spec.rb24
-rw-r--r--spec/models/clusters/applications/helm_spec.rb14
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb9
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb9
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb35
-rw-r--r--spec/models/clusters/applications/runner_spec.rb9
-rw-r--r--spec/models/clusters/cluster_spec.rb4
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb24
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb19
19 files changed, 880 insertions, 38 deletions
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 42917d0d505..26a532ee01d 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -274,11 +274,43 @@ describe Projects::ClustersController do
context 'when creates a cluster' do
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
+
expect { go }.to change { Clusters::Cluster.count }
.and change { Clusters::Platforms::Kubernetes.count }
+
expect(response).to redirect_to(project_cluster_path(project, project.clusters.first))
+
+ expect(project.clusters.first).to be_user
+ expect(project.clusters.first).to be_kubernetes
+ end
+ end
+
+ context 'when creates a RBAC-enabled cluster' do
+ let(:params) do
+ {
+ cluster: {
+ name: 'new-cluster',
+ platform_kubernetes_attributes: {
+ api_url: 'http://my-url',
+ token: 'test',
+ namespace: 'aaa',
+ authorization_type: 'rbac'
+ }
+ }
+ }
+ end
+
+ it 'creates a new cluster' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { go }.to change { Clusters::Cluster.count }
+ .and change { Clusters::Platforms::Kubernetes.count }
+
+ expect(response).to redirect_to(project_cluster_path(project, project.clusters.first))
+
expect(project.clusters.first).to be_user
expect(project.clusters.first).to be_kubernetes
+ expect(project.clusters.first).to be_platform_kubernetes_rbac
end
end
end
diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb
index 89f6ddebf6a..36ac2372204 100644
--- a/spec/factories/clusters/platforms/kubernetes.rb
+++ b/spec/factories/clusters/platforms/kubernetes.rb
@@ -16,5 +16,9 @@ FactoryBot.define do
platform_kubernetes.ca_cert = File.read(pem_file)
end
end
+
+ trait :rbac_enabled do
+ authorization_type :rbac
+ end
end
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index babf47cc341..ec968bfcf7d 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -38,6 +38,28 @@ describe 'User Cluster', :js do
end
end
+ context 'rbac_clusters feature flag is enabled' do
+ before do
+ stub_feature_flags(rbac_clusters: true)
+
+ fill_in 'cluster_name', with: 'dev-cluster'
+ fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com'
+ fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token'
+ check 'cluster_platform_kubernetes_attributes_authorization_type'
+ click_button 'Add Kubernetes cluster'
+ end
+
+ it 'user sees a cluster details page' do
+ expect(page).to have_content('Kubernetes cluster integration')
+ expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
+ .to have_content('http://example.com')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
+ .to have_content('my-token')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][authorization_type]', disabled: true)).to be_checked
+ end
+ end
+
context 'when user filled form with invalid parameters' do
before do
click_button 'Add Kubernetes cluster'
diff --git a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
new file mode 100644
index 00000000000..4a669408025
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::ClusterRoleBinding do
+ let(:cluster_role_binding) { described_class.new(name, cluster_role_name, subjects) }
+ let(:name) { 'cluster-role-binding-name' }
+ let(:cluster_role_name) { 'cluster-admin' }
+
+ let(:subjects) { [{ kind: 'ServiceAccount', name: 'sa', namespace: 'ns' }] }
+
+ describe '#generate' do
+ let(:role_ref) do
+ {
+ apiGroup: 'rbac.authorization.k8s.io',
+ kind: 'ClusterRole',
+ name: cluster_role_name
+ }
+ end
+
+ let(:resource) do
+ ::Kubeclient::Resource.new(
+ metadata: { name: name },
+ roleRef: role_ref,
+ subjects: subjects
+ )
+ end
+
+ subject { cluster_role_binding.generate }
+
+ it 'should build a Kubeclient Resource' do
+ is_expected.to eq(resource)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 341f71a3e49..25c3b37753d 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -5,9 +5,18 @@ describe Gitlab::Kubernetes::Helm::Api do
let(:helm) { described_class.new(client) }
let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
let(:namespace) { Gitlab::Kubernetes::Namespace.new(gitlab_namespace, client) }
- let(:application) { create(:clusters_applications_prometheus) }
-
- let(:command) { application.install_command }
+ let(:application_name) { 'app-name' }
+ let(:rbac) { false }
+ let(:files) { {} }
+
+ let(:command) do
+ Gitlab::Kubernetes::Helm::InstallCommand.new(
+ name: application_name,
+ chart: 'chart-name',
+ rbac: rbac,
+ files: files
+ )
+ end
subject { helm }
@@ -28,6 +37,8 @@ describe Gitlab::Kubernetes::Helm::Api do
before do
allow(client).to receive(:create_pod).and_return(nil)
allow(client).to receive(:create_config_map).and_return(nil)
+ allow(client).to receive(:create_service_account).and_return(nil)
+ allow(client).to receive(:create_cluster_role_binding).and_return(nil)
allow(namespace).to receive(:ensure_exists!).once
end
@@ -39,7 +50,7 @@ describe Gitlab::Kubernetes::Helm::Api do
end
context 'with a ConfigMap' do
- let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application.name, application.files).generate }
+ let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application_name, files).generate }
it 'creates a ConfigMap on kubeclient' do
expect(client).to receive(:create_config_map).with(resource).once
@@ -47,6 +58,96 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.install(command)
end
end
+
+ context 'without a service account' do
+ it 'does not create a service account on kubeclient' do
+ expect(client).not_to receive(:create_service_account)
+ expect(client).not_to receive(:create_cluster_role_binding)
+
+ subject.install(command)
+ end
+ end
+
+ context 'with a service account' do
+ let(:command) { Gitlab::Kubernetes::Helm::InitCommand.new(name: application_name, files: files, rbac: rbac) }
+
+ context 'rbac-enabled cluster' do
+ let(:rbac) { true }
+
+ let(:service_account_resource) do
+ Kubeclient::Resource.new(metadata: { name: 'tiller', namespace: 'gitlab-managed-apps' })
+ end
+
+ let(:cluster_role_binding_resource) do
+ Kubeclient::Resource.new(
+ metadata: { name: 'tiller-admin' },
+ roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'cluster-admin' },
+ subjects: [{ kind: 'ServiceAccount', name: 'tiller', namespace: 'gitlab-managed-apps' }]
+ )
+ end
+
+ context 'service account and cluster role binding does not exist' do
+ before do
+ expect(client).to receive('get_service_account').with('tiller', 'gitlab-managed-apps').and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
+ expect(client).to receive('get_cluster_role_binding').with('tiller-admin').and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
+ end
+
+ it 'creates a service account, followed the cluster role binding on kubeclient' do
+ expect(client).to receive(:create_service_account).with(service_account_resource).once.ordered
+ expect(client).to receive(:create_cluster_role_binding).with(cluster_role_binding_resource).once.ordered
+
+ subject.install(command)
+ end
+ end
+
+ context 'service account already exists' do
+ before do
+ expect(client).to receive('get_service_account').with('tiller', 'gitlab-managed-apps').and_return(service_account_resource)
+ expect(client).to receive('get_cluster_role_binding').with('tiller-admin').and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
+ end
+
+ it 'updates the service account, followed by creating the cluster role binding' do
+ expect(client).to receive(:update_service_account).with(service_account_resource).once.ordered
+ expect(client).to receive(:create_cluster_role_binding).with(cluster_role_binding_resource).once.ordered
+
+ subject.install(command)
+ end
+ end
+
+ context 'service account and cluster role binding already exists' do
+ before do
+ expect(client).to receive('get_service_account').with('tiller', 'gitlab-managed-apps').and_return(service_account_resource)
+ expect(client).to receive('get_cluster_role_binding').with('tiller-admin').and_return(cluster_role_binding_resource)
+ end
+
+ it 'updates the service account, followed by creating the cluster role binding' do
+ expect(client).to receive(:update_service_account).with(service_account_resource).once.ordered
+ expect(client).to receive(:update_cluster_role_binding).with(cluster_role_binding_resource).once.ordered
+
+ subject.install(command)
+ end
+ end
+
+ context 'a non-404 error is thrown' do
+ before do
+ expect(client).to receive('get_service_account').with('tiller', 'gitlab-managed-apps').and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ end
+
+ it 'raises an error' do
+ expect { subject.install(command) }.to raise_error(Kubeclient::HttpError)
+ end
+ end
+ end
+
+ context 'legacy abac cluster' do
+ it 'does not create a service account on kubeclient' do
+ expect(client).not_to receive(:create_service_account)
+ expect(client).not_to receive(:create_cluster_role_binding)
+
+ subject.install(command)
+ end
+ end
+ end
end
describe '#status' do
diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
index d50616e95e8..aacae78be43 100644
--- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb
@@ -2,14 +2,24 @@ require 'spec_helper'
describe Gitlab::Kubernetes::Helm::BaseCommand do
let(:application) { create(:clusters_applications_helm) }
+ let(:rbac) { false }
+
let(:test_class) do
Class.new do
include Gitlab::Kubernetes::Helm::BaseCommand
+ def initialize(rbac)
+ @rbac = rbac
+ end
+
def name
"test-class-name"
end
+ def rbac?
+ @rbac
+ end
+
def files
{
some: 'value'
@@ -19,7 +29,7 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do
end
let(:base_command) do
- test_class.new
+ test_class.new(rbac)
end
subject { base_command }
@@ -34,6 +44,14 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do
it 'should returns a kubeclient resoure with pod content for application' do
is_expected.to be_an_instance_of ::Kubeclient::Resource
end
+
+ context 'when rbac is true' do
+ let(:rbac) { true }
+
+ it 'also returns a kubeclient resource' do
+ is_expected.to be_an_instance_of ::Kubeclient::Resource
+ end
+ end
end
describe '#pod_name' do
diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
index dcbc046cf00..72dc1817936 100644
--- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb
@@ -2,9 +2,135 @@ require 'spec_helper'
describe Gitlab::Kubernetes::Helm::InitCommand do
let(:application) { create(:clusters_applications_helm) }
- let(:commands) { 'helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem >/dev/null' }
+ let(:rbac) { false }
+ let(:files) { {} }
+ let(:init_command) { described_class.new(name: application.name, files: files, rbac: rbac) }
- subject { described_class.new(name: application.name, files: {}) }
+ let(:commands) do
+ <<~EOS
+ helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem >/dev/null
+ EOS
+ end
+
+ subject { init_command }
it_behaves_like 'helm commands'
+
+ context 'on a rbac-enabled cluster' do
+ let(:rbac) { true }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem --service-account tiller >/dev/null
+ EOS
+ end
+ end
+ end
+
+ describe '#rbac?' do
+ subject { init_command.rbac? }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#config_map_resource' do
+ let(:metadata) do
+ {
+ name: 'values-content-configuration-helm',
+ namespace: 'gitlab-managed-apps',
+ labels: { name: 'values-content-configuration-helm' }
+ }
+ end
+
+ let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: files) }
+
+ subject { init_command.config_map_resource }
+
+ it 'returns a KubeClient resource with config map content for the application' do
+ is_expected.to eq(resource)
+ end
+ end
+
+ describe '#pod_resource' do
+ subject { init_command.pod_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a pod that uses the tiller serviceAccountName' do
+ expect(subject.spec.serviceAccountName).to eq('tiller')
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates a pod that uses the default serviceAccountName' do
+ expect(subject.spec.serviceAcccountName).to be_nil
+ end
+ end
+ end
+
+ describe '#service_account_resource' do
+ let(:resource) do
+ Kubeclient::Resource.new(metadata: { name: 'tiller', namespace: 'gitlab-managed-apps' })
+ end
+
+ subject { init_command.service_account_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a Kubeclient resource for the tiller ServiceAccount' do
+ is_expected.to eq(resource)
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#cluster_role_binding_resource' do
+ let(:resource) do
+ Kubeclient::Resource.new(
+ metadata: { name: 'tiller-admin' },
+ roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'cluster-admin' },
+ subjects: [{ kind: 'ServiceAccount', name: 'tiller', namespace: 'gitlab-managed-apps' }]
+ )
+ end
+
+ subject { init_command.cluster_role_binding_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a Kubeclient resource for the ClusterRoleBinding for tiller' do
+ is_expected.to eq(resource)
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 982e2f41043..f28941ce58f 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -3,14 +3,17 @@ require 'rails_helper'
describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:files) { { 'ca.pem': 'some file content' } }
let(:repository) { 'https://repository.example.com' }
+ let(:rbac) { false }
let(:version) { '1.2.3' }
let(:install_command) do
described_class.new(
name: 'app-name',
chart: 'chart-name',
+ rbac: rbac,
files: files,
- version: version, repository: repository
+ version: version,
+ repository: repository
)
end
@@ -21,19 +24,76 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
<<~EOS
helm init --client-only >/dev/null
helm repo add app-name https://repository.example.com
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null
+ #{helm_install_comand}
+ EOS
+ end
+
+ let(:helm_install_comand) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ --version 1.2.3
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
EOS
end
end
+ context 'when rbac is true' do
+ let(:rbac) { true }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm init --client-only >/dev/null
+ helm repo add app-name https://repository.example.com
+ #{helm_install_command}
+ EOS
+ end
+
+ let(:helm_install_command) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ --version 1.2.3
+ --set rbac.create\\=true,rbac.enabled\\=true
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
+ EOS
+ end
+ end
+ end
+
context 'when there is no repository' do
let(:repository) { nil }
it_behaves_like 'helm commands' do
let(:commands) do
<<~EOS
- helm init --client-only >/dev/null
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null
+ helm init --client-only >/dev/null
+ #{helm_install_command}
+ EOS
+ end
+
+ let(:helm_install_command) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ --version 1.2.3
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
EOS
end
end
@@ -45,9 +105,19 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
it_behaves_like 'helm commands' do
let(:commands) do
<<~EOS
- helm init --client-only >/dev/null
- helm repo add app-name https://repository.example.com
- helm install chart-name --name app-name --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null
+ helm init --client-only >/dev/null
+ helm repo add app-name https://repository.example.com
+ #{helm_install_command}
+ EOS
+ end
+
+ let(:helm_install_command) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --version 1.2.3
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
EOS
end
end
@@ -59,14 +129,63 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
it_behaves_like 'helm commands' do
let(:commands) do
<<~EOS
- helm init --client-only >/dev/null
- helm repo add app-name https://repository.example.com
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null
+ helm init --client-only >/dev/null
+ helm repo add app-name https://repository.example.com
+ #{helm_install_command}
+ EOS
+ end
+
+ let(:helm_install_command) do
+ <<~EOS.squish
+ helm install chart-name
+ --name app-name
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml >/dev/null
EOS
end
end
end
+ describe '#rbac?' do
+ subject { install_command.rbac? }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#pod_resource' do
+ subject { install_command.pod_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a pod that uses the tiller serviceAccountName' do
+ expect(subject.spec.serviceAccountName).to eq('tiller')
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates a pod that uses the default serviceAccountName' do
+ expect(subject.spec.serviceAcccountName).to be_nil
+ end
+ end
+ end
+
describe '#config_map_resource' do
let(:metadata) do
{
@@ -84,4 +203,20 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
is_expected.to eq(resource)
end
end
+
+ describe '#service_account_resource' do
+ subject { install_command.service_account_resource }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+
+ describe '#cluster_role_binding_resource' do
+ subject { install_command.cluster_role_binding_resource }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index ec64193c0b2..b333b334f36 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -5,8 +5,9 @@ describe Gitlab::Kubernetes::Helm::Pod do
let(:app) { create(:clusters_applications_prometheus) }
let(:command) { app.install_command }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
+ let(:service_account_name) { nil }
- subject { described_class.new(command, namespace) }
+ subject { described_class.new(command, namespace, service_account_name: service_account_name) }
context 'with a command' do
it 'should generate a Kubeclient::Resource' do
@@ -58,6 +59,20 @@ describe Gitlab::Kubernetes::Helm::Pod do
expect(volume.configMap['items'].first['key']).to eq(:'values.yaml')
expect(volume.configMap['items'].first['path']).to eq(:'values.yaml')
end
+
+ it 'should have no serviceAccountName' do
+ spec = subject.generate.spec
+ expect(spec.serviceAccountName).to be_nil
+ end
+
+ context 'with a service_account_name' do
+ let(:service_account_name) { 'sa' }
+
+ it 'should use the serviceAccountName provided' do
+ spec = subject.generate.spec
+ expect(spec.serviceAccountName).to eq(service_account_name)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
new file mode 100644
index 00000000000..9146729d139
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -0,0 +1,247 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::KubeClient do
+ include KubernetesHelpers
+
+ let(:api_url) { 'https://kubernetes.example.com/prefix' }
+ let(:api_groups) { ['api', 'apis/rbac.authorization.k8s.io'] }
+ let(:api_version) { 'v1' }
+ let(:kubeclient_options) { { auth_options: { bearer_token: 'xyz' } } }
+
+ let(:client) { described_class.new(api_url, api_groups, api_version, kubeclient_options) }
+
+ before do
+ stub_kubeclient_discover(api_url)
+ end
+
+ describe '#hashed_clients' do
+ subject { client.hashed_clients }
+
+ it 'has keys from api groups' do
+ expect(subject.keys).to match_array api_groups
+ end
+
+ it 'has values of Kubeclient::Client' do
+ expect(subject.values).to all(be_an_instance_of Kubeclient::Client)
+ end
+ end
+
+ describe '#clients' do
+ subject { client.clients }
+
+ it 'is not empty' do
+ is_expected.to be_present
+ end
+
+ it 'is an array of Kubeclient::Client objects' do
+ is_expected.to all(be_an_instance_of Kubeclient::Client)
+ end
+
+ it 'has each API group url' do
+ expected_urls = api_groups.map { |group| "#{api_url}/#{group}" }
+
+ expect(subject.map(&:api_endpoint).map(&:to_s)).to match_array(expected_urls)
+ end
+
+ it 'has the kubeclient options' do
+ subject.each do |client|
+ expect(client.auth_options).to eq({ bearer_token: 'xyz' })
+ end
+ end
+
+ it 'has the api_version' do
+ subject.each do |client|
+ expect(client.instance_variable_get(:@api_version)).to eq('v1')
+ end
+ end
+ end
+
+ describe '#core_client' do
+ subject { client.core_client }
+
+ it 'is a Kubeclient::Client' do
+ is_expected.to be_an_instance_of Kubeclient::Client
+ end
+
+ it 'has the core API endpoint' do
+ expect(subject.api_endpoint.to_s).to match(%r{\/api\Z})
+ end
+ end
+
+ describe '#rbac_client' do
+ subject { client.rbac_client }
+
+ it 'is a Kubeclient::Client' do
+ is_expected.to be_an_instance_of Kubeclient::Client
+ end
+
+ it 'has the RBAC API group endpoint' do
+ expect(subject.api_endpoint.to_s).to match(%r{\/apis\/rbac.authorization.k8s.io\Z})
+ end
+ end
+
+ describe '#extensions_client' do
+ subject { client.extensions_client }
+
+ let(:api_groups) { ['apis/extensions'] }
+
+ it 'is a Kubeclient::Client' do
+ is_expected.to be_an_instance_of Kubeclient::Client
+ end
+
+ it 'has the extensions API group endpoint' do
+ expect(subject.api_endpoint.to_s).to match(%r{\/apis\/extensions\Z})
+ end
+ end
+
+ describe '#discover!' do
+ it 'makes a discovery request for each API group' do
+ client.discover!
+
+ api_groups.each do |api_group|
+ discovery_url = api_url + '/' + api_group + '/v1'
+ expect(WebMock).to have_requested(:get, discovery_url).once
+ end
+ end
+ end
+
+ describe 'core API' do
+ let(:core_client) { client.core_client }
+
+ [
+ :get_pods,
+ :get_secrets,
+ :get_config_map,
+ :get_pod,
+ :get_namespace,
+ :get_service,
+ :get_service_account,
+ :delete_pod,
+ :create_config_map,
+ :create_namespace,
+ :create_pod,
+ :create_service_account,
+ :update_config_map,
+ :update_service_account
+ ].each do |method|
+ describe "##{method}" do
+ it 'delegates to the core client' do
+ expect(client).to delegate_method(method).to(:core_client)
+ end
+
+ it 'responds to the method' do
+ expect(client).to respond_to method
+ end
+ end
+ end
+ end
+
+ describe 'rbac API group' do
+ let(:rbac_client) { client.rbac_client }
+
+ [
+ :create_cluster_role_binding,
+ :get_cluster_role_binding,
+ :update_cluster_role_binding
+ ].each do |method|
+ describe "##{method}" do
+ it 'delegates to the rbac client' do
+ expect(client).to delegate_method(method).to(:rbac_client)
+ end
+
+ it 'responds to the method' do
+ expect(client).to respond_to method
+ end
+
+ context 'no rbac client' do
+ let(:api_groups) { ['api'] }
+
+ it 'throws an error' do
+ expect { client.public_send(method) }.to raise_error(Module::DelegationError)
+ end
+ end
+ end
+ end
+ end
+
+ describe 'extensions API group' do
+ let(:api_groups) { ['apis/extensions'] }
+ let(:api_version) { 'v1beta1' }
+ let(:extensions_client) { client.extensions_client }
+
+ describe '#get_deployments' do
+ it 'delegates to the extensions client' do
+ expect(client).to delegate_method(:get_deployments).to(:extensions_client)
+ end
+
+ it 'responds to the method' do
+ expect(client).to respond_to :get_deployments
+ end
+
+ context 'no extensions client' do
+ let(:api_groups) { ['api'] }
+ let(:api_version) { 'v1' }
+
+ it 'throws an error' do
+ expect { client.get_deployments }.to raise_error(Module::DelegationError)
+ end
+ end
+ end
+ end
+
+ describe 'non-entity methods' do
+ it 'does not proxy for non-entity methods' do
+ expect(client.clients.first).to respond_to :proxy_url
+
+ expect(client).not_to respond_to :proxy_url
+ end
+
+ it 'throws an error' do
+ expect { client.proxy_url }.to raise_error(NoMethodError)
+ end
+ end
+
+ describe '#get_pod_log' do
+ let(:core_client) { client.core_client }
+
+ it 'is delegated to the core client' do
+ expect(client).to delegate_method(:get_pod_log).to(:core_client)
+ end
+
+ context 'when no core client' do
+ let(:api_groups) { ['apis/extensions'] }
+
+ it 'throws an error' do
+ expect { client.get_pod_log('pod-name') }.to raise_error(Module::DelegationError)
+ end
+ end
+ end
+
+ describe '#watch_pod_log' do
+ let(:core_client) { client.core_client }
+
+ it 'is delegated to the core client' do
+ expect(client).to delegate_method(:watch_pod_log).to(:core_client)
+ end
+
+ context 'when no core client' do
+ let(:api_groups) { ['apis/extensions'] }
+
+ it 'throws an error' do
+ expect { client.watch_pod_log('pod-name') }.to raise_error(Module::DelegationError)
+ end
+ end
+ 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)
+ end
+
+ it 'returns false for respond_to' do
+ expect(client.respond_to?(:non_existent_method)).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/service_account_spec.rb b/spec/lib/gitlab/kubernetes/service_account_spec.rb
new file mode 100644
index 00000000000..8da9e932dc3
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/service_account_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::ServiceAccount do
+ let(:name) { 'a_service_account' }
+ let(:namespace_name) { 'a_namespace' }
+ let(:service_account) { described_class.new(name, namespace_name) }
+
+ it { expect(service_account.name).to eq(name) }
+ it { expect(service_account.namespace_name).to eq(namespace_name) }
+
+ describe '#generate' do
+ let(:resource) do
+ ::Kubeclient::Resource.new(metadata: { name: name, namespace: namespace_name })
+ end
+
+ subject { service_account.generate }
+
+ it 'should build a Kubeclient Resource' do
+ is_expected.to eq(resource)
+ end
+ end
+end
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index e5b2bdc8a4e..2c37cd20ecc 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -47,5 +47,19 @@ describe Clusters::Applications::Helm do
cert = OpenSSL::X509::Certificate.new(subject.files[:'cert.pem'])
expect(cert.not_after).to be > 999.years.from_now
end
+
+ describe 'rbac' do
+ context 'non rbac cluster' do
+ it { expect(subject).not_to be_rbac }
+ end
+
+ context 'rbac cluster' do
+ before do
+ helm.cluster.platform_kubernetes.rbac!
+ end
+
+ it { expect(subject).to be_rbac }
+ end
+ end
end
end
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 21f75ced8c3..c55953c8d22 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -88,9 +88,18 @@ describe Clusters::Applications::Ingress do
expect(subject.name).to eq('ingress')
expect(subject.chart).to eq('stable/nginx-ingress')
expect(subject.version).to eq('0.23.0')
+ expect(subject).not_to be_rbac
expect(subject.files).to eq(ingress.files)
end
+ context 'on a rbac enabled cluster' do
+ before do
+ ingress.cluster.platform_kubernetes.rbac!
+ end
+
+ it { is_expected.to be_rbac }
+ end
+
context 'application failed to install previously' do
let(:ingress) { create(:clusters_applications_ingress, :errored, version: 'nginx') }
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 027b732681b..591a01d78a9 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -51,10 +51,19 @@ describe Clusters::Applications::Jupyter do
expect(subject.name).to eq('jupyter')
expect(subject.chart).to eq('jupyter/jupyterhub')
expect(subject.version).to eq('v0.6')
+ expect(subject).not_to be_rbac
expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/')
expect(subject.files).to eq(jupyter.files)
end
+ context 'on a rbac enabled cluster' do
+ before do
+ jupyter.cluster.platform_kubernetes.rbac!
+ end
+
+ it { is_expected.to be_rbac }
+ end
+
context 'application failed to install previously' do
let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') }
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 26b75c75e1d..f34b4ece8db 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -1,6 +1,8 @@
require 'rails_helper'
describe Clusters::Applications::Prometheus do
+ include KubernetesHelpers
+
include_examples 'cluster application core specs', :clusters_applications_prometheus
include_examples 'cluster application status specs', :cluster_application_prometheus
@@ -107,26 +109,14 @@ describe Clusters::Applications::Prometheus do
end
context 'cluster has kubeclient' do
- let(:kubernetes_url) { 'http://example.com' }
- let(:k8s_discover_response) do
- {
- resources: [
- {
- name: 'service',
- kind: 'Service'
- }
- ]
- }
- end
-
- let(:kube_client) { Kubeclient::Client.new(kubernetes_url) }
+ let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url }
+ let(:kube_client) { subject.cluster.kubeclient.core_client }
- let(:cluster) { create(:cluster) }
- subject { create(:clusters_applications_prometheus, cluster: cluster) }
+ subject { create(:clusters_applications_prometheus) }
before do
- allow(kube_client.rest_client).to receive(:get).and_return(k8s_discover_response.to_json)
- allow(subject.cluster).to receive(:kubeclient).and_return(kube_client)
+ subject.cluster.platform_kubernetes.namespace = 'a-namespace'
+ stub_kubeclient_discover(subject.cluster.platform_kubernetes.api_url)
end
it 'creates proxy prometheus rest client' do
@@ -134,7 +124,7 @@ describe Clusters::Applications::Prometheus do
end
it 'creates proper url' do
- expect(subject.prometheus_client.url).to eq('http://example.com/api/v1/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80/proxy')
+ expect(subject.prometheus_client.url).to eq("#{kubernetes_url}/api/v1/namespaces/gitlab-managed-apps/services/prometheus-prometheus-server:80/proxy")
end
it 'copies options and headers from kube client to proxy client' do
@@ -164,9 +154,18 @@ describe Clusters::Applications::Prometheus do
expect(subject.name).to eq('prometheus')
expect(subject.chart).to eq('stable/prometheus')
expect(subject.version).to eq('6.7.3')
+ expect(subject).not_to be_rbac
expect(subject.files).to eq(prometheus.files)
end
+ context 'on a rbac enabled cluster' do
+ before do
+ prometheus.cluster.platform_kubernetes.rbac!
+ end
+
+ it { is_expected.to be_rbac }
+ end
+
context 'application failed to install previously' do
let(:prometheus) { create(:clusters_applications_prometheus, :errored, version: '2.0.0') }
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index d84f125e246..eda8d519f60 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -46,10 +46,19 @@ describe Clusters::Applications::Runner do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
expect(subject.version).to eq('0.1.31')
+ expect(subject).not_to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
end
+ context 'on a rbac enabled cluster' do
+ before do
+ gitlab_runner.cluster.platform_kubernetes.rbac!
+ end
+
+ it { is_expected.to be_rbac }
+ end
+
context 'application failed to install previously' do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 6f66515b45f..2727191eb9b 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -13,6 +13,10 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:status_reason).to(:provider) }
it { is_expected.to delegate_method(:status_name).to(:provider) }
it { is_expected.to delegate_method(:on_creation?).to(:provider) }
+ it { is_expected.to delegate_method(:active?).to(:platform_kubernetes).with_prefix }
+ it { is_expected.to delegate_method(:rbac?).to(:platform_kubernetes).with_prefix }
+ it { is_expected.to delegate_method(:installed?).to(:application_helm).with_prefix }
+ it { is_expected.to delegate_method(:installed?).to(:application_ingress).with_prefix }
it { is_expected.to respond_to :project }
describe '.enabled' do
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index ab7f89f9bf4..66198d5ee2b 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -92,6 +92,30 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
end
+ describe '#kubeclient' do
+ subject { kubernetes.kubeclient }
+
+ let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace') }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
+ end
+
+ describe '#rbac?' do
+ subject { kubernetes.rbac? }
+
+ let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
+
+ context 'when authorization type is rbac' do
+ let(:kubernetes) { build(:cluster_platform_kubernetes, :rbac_enabled, :configured) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when authorization type is nil' do
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#actual_namespace' do
subject { kubernetes.actual_namespace }
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 683a64504a1..994a2aaef90 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -16,6 +16,7 @@ module KubernetesHelpers
def stub_kubeclient_discover(api_url)
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
WebMock.stub_request(:get, api_url + '/apis/extensions/v1beta1').to_return(kube_response(kube_v1beta1_discovery_body))
+ WebMock.stub_request(:get, api_url + '/apis/rbac.authorization.k8s.io/v1').to_return(kube_response(kube_v1_rbac_authorization_discovery_body))
end
def stub_kubeclient_pods(response = nil)
@@ -66,7 +67,8 @@ module KubernetesHelpers
"resources" => [
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
- { "name" => "secrets", "namespaced" => true, "kind" => "Secret" }
+ { "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
+ { "name" => "services", "namespaced" => true, "kind" => "Service" }
]
}
end
@@ -77,7 +79,20 @@ module KubernetesHelpers
"resources" => [
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
- { "name" => "secrets", "namespaced" => true, "kind" => "Secret" }
+ { "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
+ { "name" => "services", "namespaced" => true, "kind" => "Service" }
+ ]
+ }
+ end
+
+ def kube_v1_rbac_authorization_discovery_body
+ {
+ "kind" => "APIResourceList",
+ "resources" => [
+ { "name" => "clusterrolebindings", "namespaced" => false, "kind" => "ClusterRoleBinding" },
+ { "name" => "clusterroles", "namespaced" => false, "kind" => "ClusterRole" },
+ { "name" => "rolebindings", "namespaced" => true, "kind" => "RoleBinding" },
+ { "name" => "roles", "namespaced" => true, "kind" => "Role" }
]
}
end