Commit 733da6d6 authored by James Fargher's avatar James Fargher

Instance level kubernetes clusters admin

Instance level clusters were already mostly supported, this change adds
admin area controllers for cluster CRUD
parent 863f2bcf
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
});
# frozen_string_literal: true
class Admin::Clusters::ApplicationsController < Clusters::ApplicationsController
private
def clusterable
@clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
end
end
# frozen_string_literal: true
class Admin::ClustersController < Clusters::ClustersController
prepend_before_action :check_instance_clusters_feature_flag!
layout 'admin'
private
def clusterable
@clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
end
def check_instance_clusters_feature_flag!
render_404 unless Feature.enabled?(:instance_clusters, default_enabled: true)
end
end
...@@ -69,10 +69,12 @@ module Clusters ...@@ -69,10 +69,12 @@ module Clusters
} }
if cluster.group_type? if cluster.group_type?
attributes.merge(groups: [group]) attributes[:groups] = [group]
elsif cluster.project_type? elsif cluster.project_type?
attributes.merge(projects: [project]) attributes[:projects] = [project]
end end
attributes
end end
def gitlab_url def gitlab_url
......
...@@ -177,6 +177,10 @@ module Clusters ...@@ -177,6 +177,10 @@ module Clusters
end end
alias_method :group, :first_group alias_method :group, :first_group
def instance
Instance.new if instance_type?
end
def kubeclient def kubeclient
platform_kubernetes.kubeclient if kubernetes? platform_kubernetes.kubeclient if kubernetes?
end end
......
# frozen_string_literal: true
class Clusters::Instance
def clusters
Clusters::Cluster.instance_type
end
def feature_available?(feature)
::Feature.enabled?(feature, default_enabled: true)
end
end
...@@ -6,5 +6,6 @@ module Clusters ...@@ -6,5 +6,6 @@ module Clusters
delegate { cluster.group } delegate { cluster.group }
delegate { cluster.project } delegate { cluster.project }
delegate { cluster.instance }
end end
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? }
rule { admin }.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
...@@ -35,6 +35,8 @@ module Clusters ...@@ -35,6 +35,8 @@ module Clusters
s_("ClusterIntegration|Project cluster") s_("ClusterIntegration|Project cluster")
elsif cluster.group_type? elsif cluster.group_type?
s_("ClusterIntegration|Group cluster") s_("ClusterIntegration|Group cluster")
elsif cluster.instance_type?
s_("ClusterIntegration|Instance cluster")
end end
end end
...@@ -43,6 +45,8 @@ module Clusters ...@@ -43,6 +45,8 @@ module Clusters
project_cluster_path(project, cluster) project_cluster_path(project, cluster)
elsif cluster.group_type? elsif cluster.group_type?
group_cluster_path(group, cluster) group_cluster_path(group, cluster)
elsif cluster.instance_type?
admin_cluster_path(cluster)
else else
raise NotImplementedError raise NotImplementedError
end end
......
# 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
...@@ -12,6 +12,8 @@ module Clusters ...@@ -12,6 +12,8 @@ module Clusters
cluster.cluster_type = :project_type cluster.cluster_type = :project_type
when ::Group when ::Group
cluster.cluster_type = :group_type cluster.cluster_type = :group_type
when Instance
cluster.cluster_type = :instance_type
else else
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -38,6 +38,8 @@ module Clusters ...@@ -38,6 +38,8 @@ module Clusters
{ cluster_type: :project_type, projects: [clusterable] } { cluster_type: :project_type, projects: [clusterable] }
when ::Group when ::Group
{ cluster_type: :group_type, groups: [clusterable] } { cluster_type: :group_type, groups: [clusterable] }
when Instance
{ cluster_type: :instance_type }
else else
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -132,6 +132,18 @@ ...@@ -132,6 +132,18 @@
= _('Abuse Reports') = _('Abuse Reports')
%span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all)) %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all))
= 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? - if akismet_enabled?
= nav_link(controller: :spam_logs) do = nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path 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 ...@@ -132,5 +132,7 @@ namespace :admin do
end end
end end
concerns :clusterable
root to: 'dashboard#index' root to: 'dashboard#index'
end end
...@@ -2023,9 +2023,15 @@ msgstr "" ...@@ -2023,9 +2023,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." 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 "" 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." msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects."
msgstr "" msgstr ""
msgid "ClusterIntegration|Adding an integration will share the cluster across all projects."
msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "" msgstr ""
...@@ -2206,6 +2212,9 @@ msgstr "" ...@@ -2206,6 +2212,9 @@ msgstr ""
msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}." msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
msgstr "" msgstr ""
msgid "ClusterIntegration|Instance cluster"
msgstr ""
msgid "ClusterIntegration|Integrate Kubernetes cluster automation" msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "" msgstr ""
...@@ -2272,6 +2281,9 @@ msgstr "" ...@@ -2272,6 +2281,9 @@ msgstr ""
msgid "ClusterIntegration|Learn more about group Kubernetes clusters" msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr "" msgstr ""
msgid "ClusterIntegration|Learn more about instance Kubernetes clusters"
msgstr ""
msgid "ClusterIntegration|Let's Encrypt" msgid "ClusterIntegration|Let's Encrypt"
msgstr "" 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) }
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
This diff is collapsed.
...@@ -69,8 +69,8 @@ describe Clusters::Applications::Runner do ...@@ -69,8 +69,8 @@ describe Clusters::Applications::Runner do
expect(values).to include('privileged: true') expect(values).to include('privileged: true')
expect(values).to include('image: ubuntu:16.04') expect(values).to include('image: ubuntu:16.04')
expect(values).to include('resources') expect(values).to include('resources')
expect(values).to match(/runnerToken: '?#{ci_runner.token}/) expect(values).to match(/runnerToken: '?#{Regexp.escape(ci_runner.token)}/)
expect(values).to match(/gitlabUrl: '?#{Gitlab::Routing.url_helpers.root_url}/) expect(values).to match(/gitlabUrl: '?#{Regexp.escape(Gitlab::Routing.url_helpers.root_url)}/)
end end
context 'without a runner' do context 'without a runner' do
...@@ -83,7 +83,7 @@ describe Clusters::Applications::Runner do ...@@ -83,7 +83,7 @@ describe Clusters::Applications::Runner do
end end
it 'uses the new runner token' do 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
end end
...@@ -114,6 +114,18 @@ describe Clusters::Applications::Runner do ...@@ -114,6 +114,18 @@ describe Clusters::Applications::Runner do
expect(runner.groups).to eq [group] expect(runner.groups).to eq [group]
end end
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 end
context 'with duplicated values on vendor/runner/values.yaml' do context 'with duplicated values on vendor/runner/values.yaml' do
......
...@@ -66,5 +66,21 @@ describe Clusters::ClusterPolicy, :models do ...@@ -66,5 +66,21 @@ describe Clusters::ClusterPolicy, :models do
it { expect(policy).to be_disallowed :admin_cluster } it { expect(policy).to be_disallowed :admin_cluster }
end end
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
end end
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::InstancePolicy do
let(:cluster) { create(:cluster, :instance) }
let(:user) { create(:user) }
let(:policy) { described_class.new(user, cluster) }
describe 'rules' do
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
...@@ -210,6 +210,12 @@ describe Clusters::ClusterPresenter do ...@@ -210,6 +210,12 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq('Group cluster') } it { is_expected.to eq('Group cluster') }
end end
context 'instance_type cluster' do
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
it { is_expected.to eq('Instance cluster') }
end
end end
describe '#show_path' do describe '#show_path' do
...@@ -227,6 +233,12 @@ describe Clusters::ClusterPresenter do ...@@ -227,6 +233,12 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq(group_cluster_path(group, cluster)) } it { is_expected.to eq(group_cluster_path(group, cluster)) }
end 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 end
describe '#read_only_kubernetes_platform_fields?' do describe '#read_only_kubernetes_platform_fields?' do
......
...@@ -21,5 +21,13 @@ describe Clusters::BuildService do ...@@ -21,5 +21,13 @@ describe Clusters::BuildService do
is_expected.to be_group_type is_expected.to be_group_type
end end
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
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