Commit 46790d6c authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch 'ee-instance_level_clusters' into 'master'

EE port of instance level kubernetes cluster admin interface

See merge request gitlab-org/gitlab-ee!10761
parents cbc37a4c de4e5a55
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
import PersistentUserCallout from '~/persistent_user_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
function initGcpSignupCallout() {
const callout = document.querySelector('.gcp-signup-offer');
PersistentUserCallout.factory(callout);
}
document.addEventListener('DOMContentLoaded', () => {
const { page } = document.body.dataset;
const newClusterViews = [
'admin:clusters:new',
'admin:clusters:create_gcp',
'admin:clusters:create_user',
];
if (newClusterViews.indexOf(page) > -1) {
initGcpSignupCallout();
initGkeDropdowns();
}
});
import PersistentUserCallout from '~/persistent_user_callout';
document.addEventListener('DOMContentLoaded', () => {
const callout = document.querySelector('.gcp-signup-offer');
PersistentUserCallout.factory(callout);
});
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
......@@ -4,12 +4,9 @@
#
# Automatically sets the layout and ensures an administrator is logged in
class Admin::ApplicationController < ApplicationController
before_action :authenticate_admin!
layout 'admin'
include EnforcesAdminAuthentication
def authenticate_admin!
render_404 unless current_user.admin?
end
layout 'admin'
end
Admin::ApplicationController.prepend(EE::Admin::ApplicationController)
# frozen_string_literal: true
class Admin::Clusters::ApplicationsController < Clusters::ApplicationsController
include EnforcesAdminAuthentication
private
def clusterable
@clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
end
end
# frozen_string_literal: true
class Admin::ClustersController < Clusters::ClustersController
include EnforcesAdminAuthentication
layout 'admin'
private
def clusterable
@clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
end
end
# frozen_string_literal: true
# == EnforcesAdminAuthentication
#
# Controller concern to enforce that users are authenticated as admins
#
# Upon inclusion, adds `authenticate_admin!` as a before_action
#
module EnforcesAdminAuthentication
extend ActiveSupport::Concern
included do
before_action :authenticate_admin!
end
def authenticate_admin!
render_404 unless current_user.admin?
end
end
......@@ -286,6 +286,10 @@ module ApplicationSettingsHelper
def expanded_by_default?
Rails.env.test?
end
def instance_clusters_enabled?
can?(current_user, :read_cluster, Clusters::Instance.new)
end
end
ApplicationSettingsHelper.prepend(EE::ApplicationSettingsHelper) # rubocop: disable Cop/InjectEnterpriseEditionModule
......
......@@ -69,10 +69,12 @@ module Clusters
}
if cluster.group_type?
attributes.merge(groups: [group])
attributes[:groups] = [group]
elsif cluster.project_type?
attributes.merge(projects: [project])
attributes[:projects] = [project]
end
attributes
end
def gitlab_url
......
......@@ -115,10 +115,12 @@ module Clusters
}
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
return [] if clusterable.is_a?(Instance)
hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order).eager_load(:clusters)
hierarchy_groups = hierarchy_groups.merge(current_scope) if current_scope
hierarchy_groups.flat_map(&:clusters)
hierarchy_groups.flat_map(&:clusters) + Instance.new.clusters
end
def status_name
......@@ -177,6 +179,10 @@ module Clusters
end
alias_method :group, :first_group
def instance
Instance.new if instance_type?
end
def kubeclient
platform_kubernetes.kubeclient if kubernetes?
end
......
# frozen_string_literal: true
module Clusters
class Instance
def clusters
Clusters::Cluster.instance_type
end
def feature_available?(feature)
::Feature.enabled?(feature, default_enabled: true)
end
def self.enabled?
::Feature.enabled?(:instance_clusters, default_enabled: true)
end
end
end
......@@ -14,6 +14,7 @@ module DeploymentPlatform
def find_deployment_platform(environment)
find_cluster_platform_kubernetes(environment: environment) ||
find_group_cluster_platform_kubernetes_with_feature_guard(environment: environment) ||
find_instance_cluster_platform_kubernetes_with_feature_guard(environment: environment) ||
find_kubernetes_service_integration ||
build_cluster_and_deployment_platform
end
......@@ -36,6 +37,18 @@ module DeploymentPlatform
.first&.platform_kubernetes
end
def find_instance_cluster_platform_kubernetes_with_feature_guard(environment: nil)
return unless Clusters::Instance.enabled?
find_instance_cluster_platform_kubernetes(environment: environment)
end
# EE would override this and utilize environment argument
def find_instance_cluster_platform_kubernetes(environment: nil)
Clusters::Instance.new.clusters.enabled.default_environment
.first&.platform_kubernetes
end
def find_kubernetes_service_integration
services.deployment.reorder(nil).find_by(active: true)
end
......
......@@ -6,5 +6,6 @@ module Clusters
delegate { cluster.group }
delegate { cluster.project }
delegate { cluster.instance }
end
end
# frozen_string_literal: true
module Clusters
class InstancePolicy < BasePolicy
include ClusterableActions
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
condition(:can_have_multiple_clusters) { multiple_clusters_available? }
condition(:instance_clusters_enabled) { Instance.enabled? }
rule { admin & instance_clusters_enabled }.policy do
enable :read_cluster
enable :add_cluster
enable :create_cluster
enable :update_cluster
enable :admin_cluster
end
rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
end
end
......@@ -52,10 +52,6 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
raise NotImplementedError
end
def clusters_path(params = {})
raise NotImplementedError
end
def empty_state_help_text
nil
end
......
......@@ -35,6 +35,8 @@ module Clusters
s_("ClusterIntegration|Project cluster")
elsif cluster.group_type?
s_("ClusterIntegration|Group cluster")
elsif cluster.instance_type?
s_("ClusterIntegration|Instance cluster")
end
end
......@@ -43,6 +45,8 @@ module Clusters
project_cluster_path(project, cluster)
elsif cluster.group_type?
group_cluster_path(group, cluster)
elsif cluster.instance_type?
admin_cluster_path(cluster)
else
raise NotImplementedError
end
......
......@@ -24,11 +24,6 @@ class GroupClusterablePresenter < ClusterablePresenter
group_cluster_path(clusterable, cluster, params)
end
override :clusters_path
def clusters_path(params = {})
group_clusters_path(clusterable, params)
end
override :empty_state_help_text
def empty_state_help_text
s_('ClusterIntegration|Adding an integration to your group will share the cluster across all your projects.')
......
# frozen_string_literal: true
class InstanceClusterablePresenter < ClusterablePresenter
extend ::Gitlab::Utils::Override
include ActionView::Helpers::UrlHelper
def self.fabricate(clusterable, **attributes)
attributes_with_presenter_class = attributes.merge(presenter_class: InstanceClusterablePresenter)
Gitlab::View::Presenter::Factory
.new(clusterable, attributes_with_presenter_class)
.fabricate!
end
override :index_path
def index_path
admin_clusters_path
end
override :new_path
def new_path
new_admin_cluster_path
end
override :cluster_status_cluster_path
def cluster_status_cluster_path(cluster, params = {})
cluster_status_admin_cluster_path(cluster, params)
end
override :install_applications_cluster_path
def install_applications_cluster_path(cluster, application)
install_applications_admin_cluster_path(cluster, application)
end
override :update_applications_cluster_path
def update_applications_cluster_path(cluster, application)
update_applications_admin_cluster_path(cluster, application)
end
override :cluster_path
def cluster_path(cluster, params = {})
admin_cluster_path(cluster, params)
end
override :create_user_clusters_path
def create_user_clusters_path
create_user_admin_clusters_path
end
override :create_gcp_clusters_path
def create_gcp_clusters_path
create_gcp_admin_clusters_path
end
override :empty_state_help_text
def empty_state_help_text
s_('ClusterIntegration|Adding an integration will share the cluster across all projects.')
end
override :sidebar_text
def sidebar_text
s_('ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster.')
end
override :learn_more_link
def learn_more_link
link_to(s_('ClusterIntegration|Learn more about instance Kubernetes clusters'), help_page_path('user/instance/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
end
InstanceClusterablePresenter.prepend EE::InstanceClusterablePresenter
......@@ -24,11 +24,6 @@ class ProjectClusterablePresenter < ClusterablePresenter
project_cluster_path(clusterable, cluster, params)
end
override :clusters_path
def clusters_path(params = {})
project_clusters_path(clusterable, params)
end
override :sidebar_text
def sidebar_text
s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.')
......
......@@ -12,6 +12,8 @@ module Clusters
cluster.cluster_type = :project_type
when ::Group
cluster.cluster_type = :group_type
when Instance
cluster.cluster_type = :instance_type
else
raise NotImplementedError
end
......
......@@ -38,6 +38,8 @@ module Clusters
{ cluster_type: :project_type, projects: [clusterable] }
when ::Group
{ cluster_type: :group_type, groups: [clusterable] }
when Instance
{ cluster_type: :instance_type }
else
raise NotImplementedError
end
......
......@@ -145,6 +145,19 @@
%strong.fly-out-top-item-name
= _('License')
- if instance_clusters_enabled?
= nav_link(controller: :clusters) do
= link_to admin_clusters_path do
.nav-icon-container
= sprite_icon('cloud-gear')
%span.nav-item-name
= _('Kubernetes')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :clusters, html_options: { class: "fly-out-top-item" } ) do
= link_to admin_clusters_path do
%strong.fly-out-top-item-name
= _('Kubernetes')
- if akismet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path do
......
---
title: Instance level kubernetes clusters
merge_request: 27196
author:
type: added
......@@ -132,5 +132,7 @@ namespace :admin do
end
end
concerns :clusterable
root to: 'dashboard#index'
end
......@@ -15,7 +15,7 @@ module EE
def cluster_health_data(cluster)
{
'clusters-path': clusterable.clusters_path,
'clusters-path': clusterable.index_path,
'metrics-endpoint': clusterable.metrics_cluster_path(cluster, format: :json),
'documentation-path': help_page_path('administration/monitoring/prometheus/index.md'),
'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'),
......
......@@ -25,5 +25,13 @@ module EE
.ancestor_clusters_for_clusterable(self, hierarchy_order: :desc)
.last&.platform_kubernetes
end
override :find_instance_cluster_platform_kubernetes
def find_instance_cluster_platform_kubernetes(environment: nil)
return super unless environment && feature_available?(:multiple_clusters)
Clusters::Instance.new.clusters.enabled.on_environment(environment)
.first&.platform_kubernetes
end
end
end
# frozen_string_literal: true
module EE
module InstanceClusterablePresenter
extend ::Gitlab::Utils::Override
override :metrics_cluster_path
def metrics_cluster_path(cluster, params = {})
metrics_admin_cluster_path(cluster, params)
end
end
end
......@@ -76,7 +76,7 @@ describe ClustersHelper do
it do
is_expected.to match(
'clusters-path': clusterable_presenter.clusters_path,
'clusters-path': clusterable_presenter.index_path,
'metrics-endpoint': clusterable_presenter.metrics_cluster_path(cluster, format: :json),
'documentation-path': help_page_path('administration/monitoring/prometheus/index.md'),
'empty-getting-started-svg-path': match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
......
......@@ -122,6 +122,44 @@ describe EE::DeploymentPlatform do
end
end
context 'with instance clusters' do
let!(:default_cluster) do
create(:cluster, :provided_by_user, :instance, environment_scope: '*')
end
let!(:cluster) do
create(:cluster, :provided_by_user, :instance, environment_scope: 'review/*')
end
let(:environment) { 'review/name' }
subject { project.deployment_platform(environment: environment) }
context 'when environment scope is exactly matched' do
before do
cluster.update!(environment_scope: 'review/name')
end
it_behaves_like 'matching environment scope'
end
context 'when environment scope is matched by wildcard' do
before do
cluster.update!(environment_scope: 'review/*')
end
it_behaves_like 'matching environment scope'
end
context 'when environment scope does not match' do
before do
cluster.update!(environment_scope: 'review/*/special')
end
it_behaves_like 'not matching environment scope'
end
end
context 'when environment is specified' do
let!(:default_cluster) { create(:cluster, :provided_by_user, projects: [project], environment_scope: '*') }
let!(:cluster) { create(:cluster, :provided_by_user, environment_scope: 'review/*', projects: [project]) }
......
# frozen_string_literal: true
require 'spec_helper'
describe InstanceClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(instance) }
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
let(:instance) { cluster.instance }
describe '#metrics_cluster_path' do
subject { presenter.metrics_cluster_path(cluster) }
it { is_expected.to eq(metrics_admin_cluster_path(cluster)) }
end
end
......@@ -2554,9 +2554,15 @@ msgstr ""
msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
msgstr ""
msgid "ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster."
msgstr ""
msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
msgstr ""
msgid "ClusterIntegration|Adding an integration will share the cluster across all projects."
msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
......@@ -2746,6 +2752,9 @@ msgstr ""
msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr ""
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr ""
......@@ -2812,6 +2821,9 @@ msgstr ""
msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
msgid "ClusterIntegration|Let's Encrypt"
msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
describe Admin::Clusters::ApplicationsController do
include AccessMatchersForController
def current_application
Clusters::Cluster::APPLICATIONS[application]
end
shared_examples 'a secure endpoint' do
it { expect { subject }.to be_allowed_for(:admin) }
it { expect { subject }.to be_denied_for(:user) }
it { expect { subject }.to be_denied_for(:external) }
context 'when instance clusters are disabled' do
before do
stub_feature_flags(instance_clusters: false)
end
it 'returns 404' do
is_expected.to have_http_status(:not_found)
end
end
end
let(:cluster) { create(:cluster, :instance, :provided_by_gcp) }
describe 'POST create' do
subject do
post :create, params: params
end
let(:application) { 'helm' }
let(:params) { { application: application, id: cluster.id } }
describe 'functionality' do
let(:admin) { create(:admin) }
before do
sign_in(admin)
end
it 'schedule an application installation' do
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once
expect { subject }.to change { current_application.count }
expect(response).to have_http_status(:no_content)
expect(cluster.application_helm).to be_scheduled
end
context 'when cluster do not exists' do
before do
cluster.destroy!
end
it 'return 404' do
expect { subject }.not_to change { current_application.count }
expect(response).to have_http_status(:not_found)
end
end
context 'when application is unknown' do
let(:application) { 'unkwnown-app' }
it 'return 404' do
is_expected.to have_http_status(:not_found)
end
end
context 'when application is already installing' do
before do
create(:clusters_applications_helm, :installing, cluster: cluster)
end
it 'returns 400' do
is_expected.to have_http_status(:bad_request)
end
end
end
describe 'security' do
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
end
it_behaves_like 'a secure endpoint'
end
end
describe 'PATCH update' do
subject do
patch :update, params: params
end
let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) }
let(:application_name) { application.name }
let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } }
describe 'functionality' do
let(:admin) { create(:admin) }
before do
sign_in(admin)
end
context "when cluster and app exists" do
it "schedules an application update" do
expect(ClusterPatchAppWorker).to receive(:perform_async).with(application.name, anything).once
is_expected.to have_http_status(:no_content)
expect(cluster.application_cert_manager).to be_scheduled
end
end
context 'when cluster do not exists' do
before do
cluster.destroy!
end
it { is_expected.to have_http_status(:not_found) }
end
context 'when application is unknown' do
let(:application_name) { 'unkwnown-app' }
it { is_expected.to have_http_status(:not_found) }
end
context 'when application is already scheduled' do
before do
application.make_scheduled!
end
it { is_expected.to have_http_status(:bad_request) }
end
end
describe 'security' do
before do
allow(ClusterPatchAppWorker).to receive(:perform_async)
end
it_behaves_like 'a secure endpoint'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Admin::ClustersController do
include AccessMatchersForController
include GoogleApi::CloudPlatformHelpers
let(:admin) { create(:admin) }
before do
sign_in(admin)
end
describe 'GET #index' do
def get_index(params = {})
get :index, params: params
end
context 'when feature flag is not enabled' do
before do
stub_feature_flags(instance_clusters: false)
end
it 'responds with not found' do
get_index
expect(response).to have_gitlab_http_status(404)
end
end
context 'when feature flag is enabled' do
before do
stub_feature_flags(instance_clusters: true)
end
describe 'functionality' do
context 'when instance has one or more clusters' do
let!(:enabled_cluster) do
create(:cluster, :provided_by_gcp, :instance)
end
let!(:disabled_cluster) do
create(:cluster, :disabled, :provided_by_gcp, :production_environment, :instance)
end
it 'lists available clusters' do
get_index
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index)
expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
end
context 'when page is specified' do
let(:last_page) { Clusters::Cluster.instance_type.page.total_pages }
before do
allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
create_list(:cluster, 2, :provided_by_gcp, :production_environment, :instance)
end
it 'redirects to the page' do
get_index(page: last_page)
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:clusters).current_page).to eq(last_page)
end
end
end
context 'when instance does not have a cluster' do
it 'returns an empty state page' do
get_index
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index, partial: :empty_state)
expect(assigns(:clusters)).to eq([])
end
end
end
end
describe 'security' do
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
it { expect { get_index }.to be_allowed_for(:admin) }
it { expect { get_index }.to be_denied_for(:user) }
it { expect { get_index }.to be_denied_for(:external) }
end
end
describe 'GET #new' do
def get_new
get :new
end
describe 'functionality for new cluster' do
context 'when omniauth has been configured' do
let(:key) { 'secret-key' }
let(:session_key_for_redirect_uri) do
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key)
end
before do
allow(SecureRandom).to receive(:hex).and_return(key)
end
it 'has authorize_url' do
get_new
expect(assigns(:authorize_url)).to include(key)
expect(session[session_key_for_redirect_uri]).to eq(new_admin_cluster_path)
end
end
context 'when omniauth has not configured' do
before do
stub_omniauth_setting(providers: [])
end
it 'does not have authorize_url' do
get_new
expect(assigns(:authorize_url)).to be_nil
end
end
context 'when access token is valid' do
before do
stub_google_api_validate_token
end
it 'has new object' do
get_new
expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
end
end
context 'when access token is expired' do
before do
stub_google_api_expired_token
end
it { expect(@valid_gcp_token).to be_falsey }
end
context 'when access token is not stored in session' do
it { expect(@valid_gcp_token).to be_falsey }
end
end
describe 'functionality for existing cluster' do
it 'has new object' do
get_new
expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
end
end
describe 'security' do
it { expect { get_new }.to be_allowed_for(:admin) }
it { expect { get_new }.to be_denied_for(:user) }
it { expect { get_new }.to be_denied_for(:external) }
end
end
describe 'POST #create_gcp' do
let(:legacy_abac_param) { 'true' }
let(:params) do
{
cluster: {
name: 'new-cluster',
provider_gcp_attributes: {
gcp_project_id: 'gcp-project-12345',
legacy_abac: legacy_abac_param
}
}
}
end
def post_create_gcp
post :create_gcp, params: params
end
describe 'functionality' do
context 'when access token is valid' do
before do
stub_google_api_validate_token
end
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_gcp }.to change { Clusters::Cluster.count }
.and change { Clusters::Providers::Gcp.count }
cluster = Clusters::Cluster.instance_type.first
expect(response).to redirect_to(admin_cluster_path(cluster))
expect(cluster).to be_gcp
expect(cluster).to be_kubernetes
expect(cluster.provider_gcp).to be_legacy_abac
end
context 'when legacy_abac param is false' do
let(:legacy_abac_param) { 'false' }
it 'creates a new cluster with legacy_abac_disabled' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_gcp }.to change { Clusters::Cluster.count }
.and change { Clusters::Providers::Gcp.count }
expect(Clusters::Cluster.instance_type.first.provider_gcp).not_to be_legacy_abac
end
end
end
context 'when access token is expired' do
before do
stub_google_api_expired_token
end
it { expect(@valid_gcp_token).to be_falsey }
end
context 'when access token is not stored in session' do
it { expect(@valid_gcp_token).to be_falsey }
end
end
describe 'security' do
before do
allow_any_instance_of(described_class)
.to receive(:token_in_session).and_return('token')
allow_any_instance_of(described_class)
.to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
.to receive(:projects_zones_clusters_create) do
OpenStruct.new(
self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
status: 'RUNNING'
)
end
allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
end
it { expect { post_create_gcp }.to be_allowed_for(:admin) }
it { expect { post_create_gcp }.to be_denied_for(:user) }
it { expect { post_create_gcp }.to be_denied_for(:external) }
end
end
describe 'POST #create_user' do
let(:params) do
{
cluster: {
name: 'new-cluster',
platform_kubernetes_attributes: {
api_url: 'http://my-url',
token: 'test'
}
}
}
end
def post_create_user
post :create_user, params: params
end
describe 'functionality' do
context 'when creates a cluster' do
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_user }.to change { Clusters::Cluster.count }
.and change { Clusters::Platforms::Kubernetes.count }
cluster = Clusters::Cluster.instance_type.first
expect(response).to redirect_to(admin_cluster_path(cluster))
expect(cluster).to be_user
expect(cluster).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',
authorization_type: 'rbac'
}
}
}
end
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_user }.to change { Clusters::Cluster.count }
.and change { Clusters::Platforms::Kubernetes.count }
cluster = Clusters::Cluster.instance_type.first
expect(response).to redirect_to(admin_cluster_path(cluster))
expect(cluster).to be_user
expect(cluster).to be_kubernetes
expect(cluster).to be_platform_kubernetes_rbac
end
end
end
describe 'security' do
it { expect { post_create_user }.to be_allowed_for(:admin) }
it { expect { post_create_user }.to be_denied_for(:user) }
it { expect { post_create_user }.to be_denied_for(:external) }
end
end
describe 'GET #cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, :instance) }
def get_cluster_status
get :cluster_status,
params: {
id: cluster
},
format: :json
end
describe 'functionality' do
it 'responds with matching schema' do
get_cluster_status
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('cluster_status')
end
it 'invokes schedule_status_update on each application' do
expect_any_instance_of(Clusters::Applications::Ingress).to receive(:schedule_status_update)
get_cluster_status
end
end
describe 'security' do
it { expect { get_cluster_status }.to be_allowed_for(:admin) }
it { expect { get_cluster_status }.to be_denied_for(:user) }
it { expect { get_cluster_status }.to be_denied_for(:external) }
end
end
describe 'GET #show' do
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
def get_show
get :show,
params: {
id: cluster
}
end
describe 'functionality' do
it 'responds successfully' do
get_show
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster)
end
end
describe 'security' do
it { expect { get_show }.to be_allowed_for(:admin) }
it { expect { get_show }.to be_denied_for(:user) }
it { expect { get_show }.to be_denied_for(:external) }
end
end
describe 'PUT #update' do
def put_update(format: :html)
put :update, params: params.merge(
id: cluster,
format: format
)
end
let(:cluster) { create(:cluster, :provided_by_user, :instance) }
let(:domain) { 'test-domain.com' }
let(:params) do
{
cluster: {
enabled: false,
name: 'my-new-cluster-name',
base_domain: domain
}
}
end
it 'updates and redirects back to show page' do
put_update
cluster.reload
expect(response).to redirect_to(admin_cluster_path(cluster))
expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster.domain).to eq('test-domain.com')
end
context 'when domain is invalid' do
let(:domain) { 'http://not-a-valid-domain' }
it 'does not update cluster attributes' do
put_update
cluster.reload
expect(response).to render_template(:show)
expect(cluster.name).not_to eq('my-new-cluster-name')
expect(cluster.domain).not_to eq('test-domain.com')
end
end
context 'when format is json' do
context 'when changing parameters' do
context 'when valid parameters are used' do
let(:params) do
{
cluster: {
enabled: false,
name: 'my-new-cluster-name',
domain: domain
}
}
end
it 'updates and redirects back to show page' do
put_update(format: :json)
cluster.reload
expect(response).to have_http_status(:no_content)
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
end
end
context 'when invalid parameters are used' do
let(:params) do
{
cluster: {
enabled: false,
name: ''
}
}
end
it 'rejects changes' do
put_update(format: :json)
expect(response).to have_http_status(:bad_request)
end
end
end
end
describe 'security' do
set(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
it { expect { put_update }.to be_allowed_for(:admin) }
it { expect { put_update }.to be_denied_for(:user) }
it { expect { put_update }.to be_denied_for(:external) }
end
end
describe 'DELETE #destroy' do
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) }
def delete_destroy
delete :destroy,
params: {
id: cluster
}
end
describe 'functionality' do
context 'when cluster is provided by GCP' do
context 'when cluster is created' do
it 'destroys and redirects back to clusters list' do
expect { delete_destroy }
.to change { Clusters::Cluster.count }.by(-1)
.and change { Clusters::Platforms::Kubernetes.count }.by(-1)
.and change { Clusters::Providers::Gcp.count }.by(-1)
expect(response).to redirect_to(admin_clusters_path)
expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end
end
context 'when cluster is being created' do
let!(:cluster) { create(:cluster, :providing_by_gcp, :production_environment, :instance) }
it 'destroys and redirects back to clusters list' do
expect { delete_destroy }
.to change { Clusters::Cluster.count }.by(-1)
.and change { Clusters::Providers::Gcp.count }.by(-1)
expect(response).to redirect_to(admin_clusters_path)
expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end
end
end
context 'when cluster is provided by user' do
let!(:cluster) { create(:cluster, :provided_by_user, :production_environment, :instance) }
it 'destroys and redirects back to clusters list' do
expect { delete_destroy }
.to change { Clusters::Cluster.count }.by(-1)
.and change { Clusters::Platforms::Kubernetes.count }.by(-1)
.and change { Clusters::Providers::Gcp.count }.by(0)
expect(response).to redirect_to(admin_clusters_path)
expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
end
end
end
describe 'security' do
set(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, :instance) }
it { expect { delete_destroy }.to be_allowed_for(:admin) }
it { expect { delete_destroy }.to be_denied_for(:user) }
it { expect { delete_destroy }.to be_denied_for(:external) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe EnforcesAdminAuthentication do
let(:user) { create(:user) }
before do
sign_in(user)
end
controller(ApplicationController) do
# `described_class` is not available in this context
include EnforcesAdminAuthentication # rubocop:disable RSpec/DescribedClass
def index
head :ok
end
end
describe 'authenticate_admin!' do
context 'as an admin' do
let(:user) { create(:admin) }
it 'renders ok' do
get :index
expect(response).to have_gitlab_http_status(200)
end
end
context 'as a user' do
it 'renders a 404' do
get :index
expect(response).to have_gitlab_http_status(404)
end
end
end
end
......@@ -8,11 +8,15 @@ describe ClusterAncestorsFinder, '#execute' do
let(:user) { create(:user) }
let!(:project_cluster) do
create(:cluster, :provided_by_user, cluster_type: :project_type, projects: [project])
create(:cluster, :provided_by_user, :project, projects: [project])
end
let!(:group_cluster) do
create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group])
create(:cluster, :provided_by_user, :group, groups: [group])
end
let!(:instance_cluster) do
create(:cluster, :provided_by_user, :instance)
end
subject { described_class.new(clusterable, user).execute }
......@@ -25,7 +29,7 @@ describe ClusterAncestorsFinder, '#execute' do
end
it 'returns the project clusters followed by group clusters' do
is_expected.to eq([project_cluster, group_cluster])
is_expected.to eq([project_cluster, group_cluster, instance_cluster])
end
context 'nested groups', :nested_groups do
......@@ -33,11 +37,11 @@ describe ClusterAncestorsFinder, '#execute' do
let(:parent_group) { create(:group) }
let!(:parent_group_cluster) do
create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group])
create(:cluster, :provided_by_user, :group, groups: [parent_group])
end
it 'returns the project clusters followed by group clusters ordered ascending the hierarchy' do
is_expected.to eq([project_cluster, group_cluster, parent_group_cluster])
is_expected.to eq([project_cluster, group_cluster, parent_group_cluster, instance_cluster])
end
end
end
......@@ -58,7 +62,7 @@ describe ClusterAncestorsFinder, '#execute' do
end
it 'returns the list of group clusters' do
is_expected.to eq([group_cluster])
is_expected.to eq([group_cluster, instance_cluster])
end
context 'nested groups', :nested_groups do
......@@ -66,12 +70,21 @@ describe ClusterAncestorsFinder, '#execute' do
let(:parent_group) { create(:group) }
let!(:parent_group_cluster) do
create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group])
create(:cluster, :provided_by_user, :group, groups: [parent_group])
end
it 'returns the list of group clusters ordered ascending the hierarchy' do
is_expected.to eq([group_cluster, parent_group_cluster])
is_expected.to eq([group_cluster, parent_group_cluster, instance_cluster])
end
end
end
context 'for an instance' do
let(:clusterable) { Clusters::Instance.new }
let(:user) { create(:admin) }
it 'returns the list of instance clusters' do
is_expected.to eq([instance_cluster])
end
end
end
......@@ -69,8 +69,8 @@ describe Clusters::Applications::Runner do
expect(values).to include('privileged: true')
expect(values).to include('image: ubuntu:16.04')
expect(values).to include('resources')
expect(values).to match(/runnerToken: '?#{ci_runner.token}/)
expect(values).to match(/gitlabUrl: '?#{Gitlab::Routing.url_helpers.root_url}/)
expect(values).to match(/runnerToken: '?#{Regexp.escape(ci_runner.token)}/)
expect(values).to match(/gitlabUrl: '?#{Regexp.escape(Gitlab::Routing.url_helpers.root_url)}/)
end
context 'without a runner' do
......@@ -83,7 +83,7 @@ describe Clusters::Applications::Runner do
end
it 'uses the new runner token' do
expect(values).to match(/runnerToken: '?#{runner.token}/)
expect(values).to match(/runnerToken: '?#{Regexp.escape(runner.token)}/)
end
end
......@@ -114,6 +114,18 @@ describe Clusters::Applications::Runner do
expect(runner.groups).to eq [group]
end
end
context 'instance cluster' do
let(:cluster) { create(:cluster, :with_installed_helm, :instance) }
include_examples 'runner creation'
it 'creates an instance runner' do
subject
expect(runner).to be_instance_type
end
end
end
context 'with duplicated values on vendor/runner/values.yaml' do
......
......@@ -325,6 +325,15 @@ describe Clusters::Cluster do
end
end
context 'when group and instance have configured kubernetes clusters' do
let(:project) { create(:project, group: group) }
let!(:instance_cluster) { create(:cluster, :provided_by_gcp, :instance) }
it 'returns clusters in order, descending the hierachy' do
is_expected.to eq([group_cluster, instance_cluster])
end
end
context 'when sub-group has configured kubernetes cluster', :nested_groups do
let(:sub_group_cluster) { create(:cluster, :provided_by_gcp, :group) }
let(:sub_group) { sub_group_cluster.group }
......
......@@ -66,5 +66,21 @@ describe Clusters::ClusterPolicy, :models do
it { expect(policy).to be_disallowed :admin_cluster }
end
end
context 'instance cluster' do
let(:cluster) { create(:cluster, :instance) }
context 'when user' do
it { expect(policy).to be_disallowed :update_cluster }
it { expect(policy).to be_disallowed :admin_cluster }
end
context 'when admin' do
let(:user) { create(:admin) }
it { expect(policy).to be_allowed :update_cluster }
it { expect(policy).to be_allowed :admin_cluster }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::InstancePolicy do
let(:user) { create(:user) }
let(:policy) { described_class.new(user, Clusters::Instance.new) }
describe 'rules' do
context 'when user' do
it { expect(policy).to be_disallowed :read_cluster }
it { expect(policy).to be_disallowed :update_cluster }
it { expect(policy).to be_disallowed :admin_cluster }
end
context 'when admin' do
let(:user) { create(:admin) }
context 'with instance_level_clusters enabled' do
it { expect(policy).to be_allowed :read_cluster }
it { expect(policy).to be_allowed :update_cluster }
it { expect(policy).to be_allowed :admin_cluster }
end
context 'with instance_level_clusters disabled' do
before do
stub_feature_flags(instance_clusters: false)
end
it { expect(policy).to be_disallowed :read_cluster }
it { expect(policy).to be_disallowed :update_cluster }
it { expect(policy).to be_disallowed :admin_cluster }
end
end
end
end
......@@ -210,6 +210,12 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq('Group cluster') }
end
context 'instance_type cluster' do
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
it { is_expected.to eq('Instance cluster') }
end
end
describe '#show_path' do
......@@ -227,6 +233,12 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq(group_cluster_path(group, cluster)) }
end
context 'instance_type cluster' do
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
it { is_expected.to eq(admin_cluster_path(cluster)) }
end
end
describe '#read_only_kubernetes_platform_fields?' do
......
......@@ -82,10 +82,4 @@ describe GroupClusterablePresenter do
it { is_expected.to eq(group_cluster_path(group, cluster)) }
end
describe '#clusters_path' do
subject { presenter.clusters_path }
it { is_expected.to eq(group_clusters_path(group)) }
end
end
......@@ -82,10 +82,4 @@ describe ProjectClusterablePresenter do
it { is_expected.to eq(project_cluster_path(project, cluster)) }
end
describe '#clusters_path' do
subject { presenter.clusters_path }
it { is_expected.to eq(project_clusters_path(project)) }
end
end
......@@ -21,5 +21,13 @@ describe Clusters::BuildService do
is_expected.to be_group_type
end
end
describe 'when cluster subject is an instance' do
let(:cluster_subject) { Clusters::Instance.new }
it 'sets the cluster_type to instance_type' do
is_expected.to be_instance_type
end
end
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