Commit 54e8ff0f authored by Thong Kuah's avatar Thong Kuah

Extend clusters_controller for group type clusters

- Add pages javascripts to intialize clusters for group pages

- Move specs asserting gcp specific validations from controller into
UpdateService spec

- Also teach Clusters::ApplicationController about groups
parent 76991929
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
import ClustersIndex from '~/clusters/clusters_index';
document.addEventListener('DOMContentLoaded', () => {
new ClustersIndex(); // eslint-disable-line no-new
});
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 initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
document.addEventListener('DOMContentLoaded', () => {
const { page } = document.body.dataset;
const newClusterViews = [
'groups:clusters:new',
'groups:clusters:create_gcp',
'groups:clusters:create_user',
];
if (newClusterViews.indexOf(page) > -1) {
initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
}
});
# frozen_string_literal: true
class Groups::Clusters::ApplicationsController < Clusters::ApplicationsController
include ControllerWithCrossProjectAccessCheck
prepend_before_action :group
requires_cross_project_access
private
def clusterable
@clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user)
end
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id])
end
end
# frozen_string_literal: true
class Groups::ClustersController < Clusters::ClustersController
include ControllerWithCrossProjectAccessCheck
prepend_before_action :group
requires_cross_project_access
layout 'group'
private
def clusterable
@clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user)
end
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id])
end
end
...@@ -6,6 +6,14 @@ module ClustersHelper ...@@ -6,6 +6,14 @@ module ClustersHelper
false false
end end
def clusterable
@project || @group
end
def can_create_cluster?
can?(current_user, :create_cluster, clusterable)
end
def render_gcp_signup_offer def render_gcp_signup_offer
return if Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? return if Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers?
return unless show_gcp_signup_offer? return unless show_gcp_signup_offer?
......
...@@ -15,6 +15,8 @@ module Clusters ...@@ -15,6 +15,8 @@ module Clusters
def show_path def show_path
if cluster.project_type? if cluster.project_type?
project_cluster_path(project, cluster) project_cluster_path(project, cluster)
elsif cluster.group_type?
group_cluster_path(group, cluster)
else else
raise NotImplementedError raise NotImplementedError
end end
......
# frozen_string_literal: true
class GroupClusterablePresenter < ClusterablePresenter
def cluster_status_cluster_path(cluster, params = {})
cluster_status_group_cluster_path(clusterable, cluster, params)
end
def install_applications_cluster_path(cluster, application)
install_applications_group_cluster_path(clusterable, cluster, application)
end
def cluster_path(cluster, params = {})
group_cluster_path(clusterable, cluster, params)
end
end
...@@ -36,6 +36,8 @@ module Clusters ...@@ -36,6 +36,8 @@ module Clusters
case clusterable case clusterable
when ::Project when ::Project
{ cluster_type: :project_type, projects: [clusterable] } { cluster_type: :project_type, projects: [clusterable] }
when ::Group
{ cluster_type: :group_type, groups: [clusterable] }
end end
end end
......
---
title: Add ability to create group level clusters and install gitlab managed applications
merge_request: 22450
author:
type: added
...@@ -53,6 +53,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -53,6 +53,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
concerns :clusterable
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
post :resend_invite, on: :member post :resend_invite, on: :member
delete :leave, on: :collection delete :leave, on: :collection
......
# frozen_string_literal: true
require 'spec_helper'
describe Groups::Clusters::ApplicationsController do
include AccessMatchersForController
def current_application
Clusters::Cluster::APPLICATIONS[application]
end
describe 'POST create' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:group) { cluster.group }
let(:application) { 'helm' }
let(:params) { { application: application, id: cluster.id } }
describe 'functionality' do
let(:user) { create(:user) }
before do
group.add_maintainer(user)
sign_in(user)
end
it 'schedule an application installation' do
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once
expect { go }.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 { go }.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
go
expect(response).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
go
expect(response).to have_http_status(:bad_request)
end
end
end
describe 'security' do
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
end
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(group) }
it { expect { go }.to be_allowed_for(:maintainer).of(group) }
it { expect { go }.to be_denied_for(:developer).of(group) }
it { expect { go }.to be_denied_for(:reporter).of(group) }
it { expect { go }.to be_denied_for(:guest).of(group) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
def go
post :create, params.merge(group_id: group)
end
end
end
This diff is collapsed.
...@@ -396,20 +396,6 @@ describe Projects::ClustersController do ...@@ -396,20 +396,6 @@ describe Projects::ClustersController do
end end
describe 'PUT update' do describe 'PUT update' do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
let(:params) do
{
cluster: {
enabled: false,
name: 'my-new-cluster-name',
platform_kubernetes_attributes: {
namespace: 'my-namespace'
}
}
}
end
def go(format: :html) def go(format: :html)
put :update, params.merge(namespace_id: project.namespace.to_param, put :update, params.merge(namespace_id: project.namespace.to_param,
project_id: project.to_param, project_id: project.to_param,
...@@ -423,105 +409,73 @@ describe Projects::ClustersController do ...@@ -423,105 +409,73 @@ describe Projects::ClustersController do
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end end
context 'when cluster is provided by GCP' do let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
it "updates and redirects back to show page" do
go
cluster.reload
expect(response).to redirect_to(project_cluster_path(project, cluster))
expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey
end
it "does not change cluster name" do
go
cluster.reload
expect(cluster.name).to eq('test-cluster')
end
context 'when cluster is being created' do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
it "rejects changes" do let(:params) do
go {
cluster: {
expect(response).to have_gitlab_http_status(:ok) enabled: false,
expect(response).to render_template(:show) name: 'my-new-cluster-name',
expect(cluster.enabled).to be_truthy platform_kubernetes_attributes: {
end namespace: 'my-namespace'
end
end
context 'when cluster is provided by user' do
let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
let(:params) do
{
cluster: {
enabled: false,
name: 'my-new-cluster-name',
platform_kubernetes_attributes: {
namespace: 'my-namespace'
}
} }
} }
end }
end
it "updates and redirects back to show page" do it "updates and redirects back to show page" do
go go
cluster.reload cluster.reload
expect(response).to redirect_to(project_cluster_path(project, cluster)) expect(response).to redirect_to(project_cluster_path(project, cluster))
expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name') expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
end end
context 'when format is json' do context 'when format is json' do
context 'when changing parameters' do context 'when changing parameters' do
context 'when valid parameters are used' do context 'when valid parameters are used' do
let(:params) do let(:params) do
{ {
cluster: { cluster: {
enabled: false, enabled: false,
name: 'my-new-cluster-name', name: 'my-new-cluster-name',
platform_kubernetes_attributes: { platform_kubernetes_attributes: {
namespace: 'my-namespace' namespace: 'my-namespace'
}
} }
} }
end }
end
it "updates and redirects back to show page" do it "updates and redirects back to show page" do
go(format: :json) go(format: :json)
cluster.reload cluster.reload
expect(response).to have_http_status(:no_content) expect(response).to have_http_status(:no_content)
expect(cluster.enabled).to be_falsey expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name') expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
end
end end
end
context 'when invalid parameters are used' do context 'when invalid parameters are used' do
let(:params) do let(:params) do
{ {
cluster: { cluster: {
enabled: false, enabled: false,
platform_kubernetes_attributes: { platform_kubernetes_attributes: {
namespace: 'my invalid namespace #@' namespace: 'my invalid namespace #@'
}
} }
} }
end }
end
it "rejects changes" do it "rejects changes" do
go(format: :json) go(format: :json)
expect(response).to have_http_status(:bad_request) expect(response).to have_http_status(:bad_request)
end
end end
end end
end end
......
...@@ -82,5 +82,12 @@ describe Clusters::ClusterPresenter do ...@@ -82,5 +82,12 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq(project_cluster_path(project, cluster)) } it { is_expected.to eq(project_cluster_path(project, cluster)) }
end end
context 'group_type cluster' do
let(:group) { cluster.group }
let(:cluster) { create(:cluster, :provided_by_gcp, :group) }
it { is_expected.to eq(group_cluster_path(group, cluster)) }
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe GroupClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(group) }
let(:cluster) { create(:cluster, :provided_by_gcp, :group) }
let(:group) { cluster.group }
describe '#can_create_cluster?' do
let(:user) { create(:user) }
subject { presenter.can_create_cluster? }
before do
allow(presenter).to receive(:current_user).and_return(user)
end
context 'when user can create' do
before do
group.add_maintainer(user)
end
it { is_expected.to be_truthy }
end
context 'when user cannot create' do
it { is_expected.to be_falsey }
end
end
describe '#index_path' do
subject { presenter.index_path }
it { is_expected.to eq(group_clusters_path(group)) }
end
describe '#new_path' do
subject { presenter.new_path }
it { is_expected.to eq(new_group_cluster_path(group)) }
end
describe '#create_user_clusters_path' do
subject { presenter.create_user_clusters_path }
it { is_expected.to eq(create_user_group_clusters_path(group)) }
end
describe '#create_gcp_clusters_path' do
subject { presenter.create_gcp_clusters_path }
it { is_expected.to eq(create_gcp_group_clusters_path(group)) }
end
describe '#cluster_status_cluster_path' do
subject { presenter.cluster_status_cluster_path(cluster) }
it { is_expected.to eq(cluster_status_group_cluster_path(group, cluster)) }
end
describe '#install_applications_cluster_path' do
let(:application) { :helm }
subject { presenter.install_applications_cluster_path(cluster, application) }
it { is_expected.to eq(install_applications_group_cluster_path(group, cluster, application)) }
end
describe '#cluster_path' do
subject { presenter.cluster_path(cluster) }
it { is_expected.to eq(group_cluster_path(group, cluster)) }
end
end
...@@ -62,5 +62,32 @@ describe Clusters::UpdateService do ...@@ -62,5 +62,32 @@ describe Clusters::UpdateService do
expect(cluster.errors[:"platform_kubernetes.namespace"]).to be_present expect(cluster.errors[:"platform_kubernetes.namespace"]).to be_present
end end
end end
context 'when cluster is provided by GCP' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:params) do
{
name: 'my-new-name'
}
end
it "does not change cluster name" do
is_expected.to eq(false)
cluster.reload
expect(cluster.name).to eq('test-cluster')
end
context 'when cluster is being created' do
let(:cluster) { create(:cluster, :providing_by_gcp) }
it "rejects changes" do
is_expected.to eq(false)
expect(cluster.errors.full_messages).to include('cannot modify during creation')
end
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