Merge branch '34758-group-cluster-controller' into 'master'
User can create a group level cluster and install applications See merge request gitlab-org/gitlab-ce!22450
This commit is contained in:
commit
8b1212ed6d
|
|
@ -9,7 +9,7 @@ import eventHub from './event_hub';
|
|||
import { APPLICATION_STATUS, REQUEST_LOADING, REQUEST_SUCCESS, REQUEST_FAILURE } from './constants';
|
||||
import ClustersService from './services/clusters_service';
|
||||
import ClustersStore from './stores/clusters_store';
|
||||
import applications from './components/applications.vue';
|
||||
import Applications from './components/applications.vue';
|
||||
import setupToggleButtons from '../toggle_buttons';
|
||||
|
||||
/**
|
||||
|
|
@ -31,6 +31,7 @@ export default class Clusters {
|
|||
installKnativePath,
|
||||
installPrometheusPath,
|
||||
managePrometheusPath,
|
||||
clusterType,
|
||||
clusterStatus,
|
||||
clusterStatusReason,
|
||||
helpPath,
|
||||
|
|
@ -67,7 +68,7 @@ export default class Clusters {
|
|||
initDismissableCallout('.js-cluster-security-warning');
|
||||
initSettingsPanels();
|
||||
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
|
||||
this.initApplications();
|
||||
this.initApplications(clusterType);
|
||||
|
||||
if (this.store.state.status !== 'created') {
|
||||
this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
|
||||
|
|
@ -79,23 +80,21 @@ export default class Clusters {
|
|||
}
|
||||
}
|
||||
|
||||
initApplications() {
|
||||
initApplications(type) {
|
||||
const { store } = this;
|
||||
const el = document.querySelector('#js-cluster-applications');
|
||||
|
||||
this.applications = new Vue({
|
||||
el,
|
||||
components: {
|
||||
applications,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
state: store.state,
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('applications', {
|
||||
return createElement(Applications, {
|
||||
props: {
|
||||
type,
|
||||
applications: this.state.applications,
|
||||
helpPath: this.state.helpPath,
|
||||
ingressHelpPath: this.state.ingressHelpPath,
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import setupToggleButtons from '~/toggle_buttons';
|
||||
import initDismissableCallout from '~/dismissable_callout';
|
||||
|
||||
import ClustersService from './services/clusters_service';
|
||||
|
||||
export default () => {
|
||||
const clusterList = document.querySelector('.js-clusters-list');
|
||||
|
||||
initDismissableCallout('.gcp-signup-offer');
|
||||
|
||||
// The empty state won't have a clusterList
|
||||
if (clusterList) {
|
||||
setupToggleButtons(document.querySelector('.js-clusters-list'), (value, toggle) =>
|
||||
ClustersService.updateCluster(toggle.dataset.endpoint, { cluster: { enabled: value } }).catch(
|
||||
err => {
|
||||
createFlash(__('Something went wrong on our end.'));
|
||||
throw err;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -13,7 +13,7 @@ import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
|
|||
import { s__, sprintf } from '../../locale';
|
||||
import applicationRow from './application_row.vue';
|
||||
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
|
||||
import { APPLICATION_STATUS, INGRESS } from '../constants';
|
||||
import { CLUSTER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -21,6 +21,11 @@ export default {
|
|||
clipboardButton,
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: CLUSTER_TYPE.PROJECT,
|
||||
},
|
||||
applications: {
|
||||
type: Object,
|
||||
required: false,
|
||||
|
|
@ -59,6 +64,9 @@ export default {
|
|||
prometheusLogo,
|
||||
}),
|
||||
computed: {
|
||||
isProjectCluster() {
|
||||
return this.type === CLUSTER_TYPE.PROJECT;
|
||||
},
|
||||
helmInstalled() {
|
||||
return (
|
||||
this.applications.helm.status === APPLICATION_STATUS.INSTALLED ||
|
||||
|
|
@ -281,6 +289,7 @@ export default {
|
|||
</div>
|
||||
</application-row>
|
||||
<application-row
|
||||
v-if="isProjectCluster"
|
||||
id="prometheus"
|
||||
:logo-url="prometheusLogo"
|
||||
:title="applications.prometheus.title"
|
||||
|
|
@ -299,6 +308,7 @@ export default {
|
|||
</div>
|
||||
</application-row>
|
||||
<application-row
|
||||
v-if="isProjectCluster"
|
||||
id="runner"
|
||||
:logo-url="gitlabLogo"
|
||||
:title="applications.runner.title"
|
||||
|
|
@ -317,6 +327,7 @@ export default {
|
|||
</div>
|
||||
</application-row>
|
||||
<application-row
|
||||
v-if="isProjectCluster"
|
||||
id="jupyter"
|
||||
:logo-url="jupyterhubLogo"
|
||||
:title="applications.jupyter.title"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
// These need to match the enum found in app/models/clusters/cluster.rb
|
||||
export const CLUSTER_TYPE = {
|
||||
INSTANCE: 'instance_type',
|
||||
GROUP: 'group_type',
|
||||
PROJECT: 'project_type',
|
||||
};
|
||||
|
||||
// These need to match what is returned from the server
|
||||
export const APPLICATION_STATUS = {
|
||||
NOT_INSTALLABLE: 'not_installable',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import ClustersBundle from '~/clusters/clusters_bundle';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import initDismissableCallout from '~/dismissable_callout';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initDismissableCallout('.gcp-signup-offer');
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import ClustersBundle from '~/clusters/clusters_bundle';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import ClustersBundle from '~/clusters/clusters_bundle';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ClustersBundle(); // eslint-disable-line no-new
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import ClustersIndex from '~/clusters/clusters_index';
|
||||
import initDismissableCallout from '~/dismissable_callout';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ClustersIndex(); // eslint-disable-line no-new
|
||||
initDismissableCallout('.gcp-signup-offer');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@
|
|||
.cluster-application-row {
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding: $gl-padding;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
border-bottom-left-radius: calc(#{$border-radius-default} - 1px);
|
||||
border-bottom-right-radius: calc(#{$border-radius-default} - 1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +79,10 @@
|
|||
padding: $gl-padding-top $gl-padding;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: $gl-vert-padding;
|
||||
}
|
||||
|
||||
.empty-state .svg-content img {
|
||||
width: 145px;
|
||||
}
|
||||
|
|
@ -80,6 +90,31 @@
|
|||
.top-area .nav-controls > .btn.btn-add-cluster {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.clusters-table {
|
||||
background-color: $gray-light;
|
||||
padding: $gl-padding-8;
|
||||
}
|
||||
|
||||
.badge-light {
|
||||
background-color: $white-normal;
|
||||
}
|
||||
|
||||
.gl-responsive-table-row {
|
||||
padding: $gl-padding;
|
||||
border: 0;
|
||||
|
||||
&.table-row-header {
|
||||
background-color: none;
|
||||
border: 0;
|
||||
font-weight: bold;
|
||||
color: $gl-gray-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cluster-warning {
|
||||
@include alert-variant(theme-color-level('warning', $alert-bg-level), theme-color-level('warning', $alert-border-level), theme-color-level('warning', $alert-color-level));
|
||||
}
|
||||
|
||||
.gcp-signup-offer {
|
||||
|
|
|
|||
|
|
@ -183,13 +183,13 @@ class Clusters::ClustersController < Clusters::BaseController
|
|||
def gcp_cluster
|
||||
@gcp_cluster = ::Clusters::Cluster.new.tap do |cluster|
|
||||
cluster.build_provider_gcp
|
||||
end
|
||||
end.present(current_user: current_user)
|
||||
end
|
||||
|
||||
def user_cluster
|
||||
@user_cluster = ::Clusters::Cluster.new.tap do |cluster|
|
||||
cluster.build_platform_kubernetes
|
||||
end
|
||||
end.present(current_user: current_user)
|
||||
end
|
||||
|
||||
def validate_gcp_token
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# 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
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Groups::ClustersController < Clusters::ClustersController
|
||||
include ControllerWithCrossProjectAccessCheck
|
||||
|
||||
prepend_before_action :check_group_clusters_feature_flag!
|
||||
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
|
||||
|
||||
def check_group_clusters_feature_flag!
|
||||
render_404 unless Feature.enabled?(:group_clusters)
|
||||
end
|
||||
end
|
||||
|
|
@ -140,6 +140,10 @@ module GroupsHelper
|
|||
can?(current_user, "read_group_#{resource}".to_sym, @group)
|
||||
end
|
||||
|
||||
if can?(current_user, :read_cluster, @group) && Feature.enabled?(:group_clusters)
|
||||
links << :kubernetes
|
||||
end
|
||||
|
||||
if can?(current_user, :admin_group, @group)
|
||||
links << :settings
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ module Clusters
|
|||
# we force autosave to happen when we save `Cluster` model
|
||||
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
|
||||
|
||||
has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', autosave: true
|
||||
has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', inverse_of: :cluster, autosave: true
|
||||
|
||||
has_one :application_helm, class_name: 'Clusters::Applications::Helm'
|
||||
has_one :application_ingress, class_name: 'Clusters::Applications::Ingress'
|
||||
|
|
@ -144,6 +144,10 @@ module Clusters
|
|||
)
|
||||
end
|
||||
|
||||
def allow_user_defined_namespace?
|
||||
project_type?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def restrict_modification
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ module Clusters
|
|||
|
||||
validates :namespace, exclusion: { in: RESERVED_NAMESPACES }
|
||||
|
||||
validate :no_namespace, unless: :allow_user_defined_namespace?
|
||||
|
||||
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
|
||||
validates :api_url, url: true, presence: true
|
||||
validates :token, presence: true
|
||||
|
|
@ -52,6 +54,7 @@ module Clusters
|
|||
delegate :project, to: :cluster, allow_nil: true
|
||||
delegate :enabled?, to: :cluster, allow_nil: true
|
||||
delegate :managed?, to: :cluster, allow_nil: true
|
||||
delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true
|
||||
delegate :kubernetes_namespace, to: :cluster
|
||||
|
||||
alias_method :active?, :enabled?
|
||||
|
|
@ -150,7 +153,8 @@ module Clusters
|
|||
end
|
||||
|
||||
def build_kube_client!
|
||||
raise "Incomplete settings" unless api_url && actual_namespace
|
||||
raise "Incomplete settings" unless api_url
|
||||
raise "No namespace" if cluster.project_type? && actual_namespace.empty? # can probably remove this line once we remove #actual_namespace
|
||||
|
||||
unless (username && password) || token
|
||||
raise "Either username/password or token is required to access API"
|
||||
|
|
@ -207,6 +211,12 @@ module Clusters
|
|||
self.token = self.token&.strip
|
||||
end
|
||||
|
||||
def no_namespace
|
||||
if namespace
|
||||
errors.add(:namespace, 'only allowed for project cluster')
|
||||
end
|
||||
end
|
||||
|
||||
def prevent_modification
|
||||
return unless managed?
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@ module Clusters
|
|||
class ClusterPolicy < BasePolicy
|
||||
alias_method :cluster, :subject
|
||||
|
||||
delegate { cluster.group }
|
||||
delegate { cluster.project }
|
||||
|
||||
rule { can?(:maintainer_access) }.policy do
|
||||
enable :update_cluster
|
||||
enable :admin_cluster
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ class GroupPolicy < BasePolicy
|
|||
enable :create_projects
|
||||
enable :admin_pipeline
|
||||
enable :admin_build
|
||||
enable :read_cluster
|
||||
enable :create_cluster
|
||||
enable :update_cluster
|
||||
enable :admin_cluster
|
||||
end
|
||||
|
||||
rule { owner }.policy do
|
||||
|
|
|
|||
|
|
@ -258,6 +258,8 @@ class ProjectPolicy < BasePolicy
|
|||
enable :update_pages
|
||||
enable :read_cluster
|
||||
enable :create_cluster
|
||||
enable :update_cluster
|
||||
enable :admin_cluster
|
||||
enable :create_environment_terminal
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -43,4 +43,16 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
|
|||
def cluster_path(cluster, params = {})
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def empty_state_help_text
|
||||
nil
|
||||
end
|
||||
|
||||
def sidebar_text
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def learn_more_link
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ module Clusters
|
|||
def show_path
|
||||
if cluster.project_type?
|
||||
project_cluster_path(project, cluster)
|
||||
elsif cluster.group_type?
|
||||
group_cluster_path(group, cluster)
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GroupClusterablePresenter < ClusterablePresenter
|
||||
extend ::Gitlab::Utils::Override
|
||||
include ActionView::Helpers::UrlHelper
|
||||
|
||||
override :cluster_status_cluster_path
|
||||
def cluster_status_cluster_path(cluster, params = {})
|
||||
cluster_status_group_cluster_path(clusterable, cluster, params)
|
||||
end
|
||||
|
||||
override :install_applications_cluster_path
|
||||
def install_applications_cluster_path(cluster, application)
|
||||
install_applications_group_cluster_path(clusterable, cluster, application)
|
||||
end
|
||||
|
||||
override :cluster_path
|
||||
def cluster_path(cluster, params = {})
|
||||
group_cluster_path(clusterable, cluster, 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.')
|
||||
end
|
||||
|
||||
override :sidebar_text
|
||||
def sidebar_text
|
||||
s_('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.')
|
||||
end
|
||||
|
||||
override :learn_more_link
|
||||
def learn_more_link
|
||||
link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
|
||||
end
|
||||
end
|
||||
|
|
@ -1,15 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProjectClusterablePresenter < ClusterablePresenter
|
||||
extend ::Gitlab::Utils::Override
|
||||
include ActionView::Helpers::UrlHelper
|
||||
|
||||
override :cluster_status_cluster_path
|
||||
def cluster_status_cluster_path(cluster, params = {})
|
||||
cluster_status_project_cluster_path(clusterable, cluster, params)
|
||||
end
|
||||
|
||||
override :install_applications_cluster_path
|
||||
def install_applications_cluster_path(cluster, application)
|
||||
install_applications_project_cluster_path(clusterable, cluster, application)
|
||||
end
|
||||
|
||||
override :cluster_path
|
||||
def cluster_path(cluster, params = {})
|
||||
project_cluster_path(clusterable, cluster, 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.')
|
||||
end
|
||||
|
||||
override :learn_more_link
|
||||
def learn_more_link
|
||||
link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,7 +42,16 @@ module Clusters
|
|||
def builders
|
||||
{
|
||||
"helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm },
|
||||
"ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress },
|
||||
"ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress }
|
||||
}.tap do |hash|
|
||||
hash.merge!(project_builders) if cluster.project_type?
|
||||
end
|
||||
end
|
||||
|
||||
# These applications will need extra configuration to enable them to work
|
||||
# with groups of projects
|
||||
def project_builders
|
||||
{
|
||||
"prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus },
|
||||
"runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner },
|
||||
"jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter },
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ module Clusters
|
|||
case clusterable
|
||||
when ::Project
|
||||
{ cluster_type: :project_type, projects: [clusterable] }
|
||||
when ::Group
|
||||
{ cluster_type: :group_type, groups: [clusterable] }
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
-# This partial is overridden in EE
|
||||
.nav-controls
|
||||
%span.btn.btn-add-cluster.disabled.js-add-cluster
|
||||
= s_("ClusterIntegration|Add Kubernetes cluster")
|
||||
|
|
@ -1,24 +1,16 @@
|
|||
.gl-responsive-table-row
|
||||
.table-section.section-30
|
||||
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster")
|
||||
.table-mobile-content
|
||||
= link_to cluster.name, cluster.show_path
|
||||
.table-section.section-30
|
||||
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope")
|
||||
.table-mobile-content= cluster.environment_scope
|
||||
.table-section.section-30
|
||||
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace")
|
||||
.table-mobile-content= cluster.platform_kubernetes&.actual_namespace
|
||||
.table-section.section-10
|
||||
.table-mobile-header{ role: "rowheader" }
|
||||
.table-mobile-content
|
||||
%button.js-project-feature-toggle.project-feature-toggle{ type: "button",
|
||||
class: "#{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}",
|
||||
"aria-label": s_("ClusterIntegration|Toggle Kubernetes Cluster"),
|
||||
disabled: !cluster.can_toggle_cluster?,
|
||||
data: { endpoint: clusterable.cluster_path(cluster, format: :json) } }
|
||||
%input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? }
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
%span.toggle-icon
|
||||
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
|
||||
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
|
||||
.card
|
||||
.card-body.gl-responsive-table-row
|
||||
.table-section.section-60
|
||||
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster")
|
||||
.table-mobile-content
|
||||
= link_to cluster.name, cluster.show_path
|
||||
- unless cluster.enabled?
|
||||
%span.badge.badge-danger Connection disabled
|
||||
.table-section.section-25
|
||||
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope")
|
||||
.table-mobile-content= cluster.environment_scope
|
||||
.table-section.section-15.text-right
|
||||
.table-mobile-header{ role: "rowheader" }
|
||||
.table-mobile-content
|
||||
%span.badge.badge-light
|
||||
= cluster.project_type? ? s_("ClusterIntegration|Project cluster") : s_("ClusterIntegration|Group cluster")
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
.col-12
|
||||
.text-content
|
||||
%h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation')
|
||||
- link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
|
||||
%p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
|
||||
%p
|
||||
= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way.')
|
||||
= clusterable.empty_state_help_text
|
||||
= clusterable.learn_more_link
|
||||
|
||||
- if clusterable.can_create_cluster?
|
||||
.text-center
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
- clusters_help_url = help_page_path('user/project/clusters/index.md')
|
||||
- help_link_start = "<a href=\"%{url}\" target=\"_blank\" rel=\"noopener noreferrer\">".html_safe
|
||||
- help_link_end = '</a>'.html_safe
|
||||
%h4.prepend-top-0
|
||||
= s_('ClusterIntegration|Kubernetes cluster integration')
|
||||
= s_('ClusterIntegration|Add a Kubernetes cluster integration')
|
||||
%p
|
||||
= 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.')
|
||||
= clusterable.sidebar_text
|
||||
%p
|
||||
= s_('ClusterIntegration|Learn more about %{help_link_start}Kubernetes%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: clusters_help_url }, help_link_end: help_link_end }
|
||||
= clusterable.learn_more_link
|
||||
|
|
|
|||
|
|
@ -33,9 +33,10 @@
|
|||
= s_('ClusterIntegration|Show')
|
||||
= clipboard_button(text: @cluster.platform_kubernetes.token, title: s_('ClusterIntegration|Copy Token'), class: 'btn-default')
|
||||
|
||||
.form-group
|
||||
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
|
||||
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
|
||||
- if @cluster.allow_user_defined_namespace?
|
||||
.form-group
|
||||
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
|
||||
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
|
||||
|
||||
.form-group
|
||||
.form-check
|
||||
|
|
|
|||
|
|
@ -10,14 +10,13 @@
|
|||
.top-area.adjust
|
||||
.nav-text
|
||||
= s_("ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project")
|
||||
.ci-table.js-clusters-list
|
||||
= render 'clusters/clusters/buttons'
|
||||
.clusters-table.js-clusters-list
|
||||
.gl-responsive-table-row.table-row-header{ role: "row" }
|
||||
.table-section.section-30{ role: "rowheader" }
|
||||
.table-section.section-60{ role: "rowheader" }
|
||||
= s_("ClusterIntegration|Kubernetes cluster")
|
||||
.table-section.section-30{ role: "rowheader" }
|
||||
= s_("ClusterIntegration|Environment scope")
|
||||
.table-section.section-30{ role: "rowheader" }
|
||||
= s_("ClusterIntegration|Project namespace")
|
||||
.table-section.section-10{ role: "rowheader" }
|
||||
- @clusters.each do |cluster|
|
||||
= render "cluster", cluster: cluster.present(current_user: current_user)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
|
||||
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
|
||||
toggle_status: @cluster.enabled? ? 'true': 'false',
|
||||
cluster_type: @cluster.cluster_type,
|
||||
cluster_status: @cluster.status_name,
|
||||
cluster_status_reason: @cluster.status_reason,
|
||||
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
|
||||
|
|
|
|||
|
|
@ -21,9 +21,10 @@
|
|||
= platform_kubernetes_field.label :token, s_('ClusterIntegration|Token'), class: 'label-bold'
|
||||
= platform_kubernetes_field.text_field :token, class: 'form-control', placeholder: s_('ClusterIntegration|Service token'), autocomplete: 'off'
|
||||
|
||||
.form-group
|
||||
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
|
||||
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
|
||||
- if @user_cluster.allow_user_defined_namespace?
|
||||
.form-group
|
||||
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
|
||||
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
|
||||
|
||||
.form-group
|
||||
.form-check
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@
|
|||
%button.js-show-cluster-token.btn-blank{ type: 'button' }
|
||||
= s_('ClusterIntegration|Show')
|
||||
|
||||
.form-group
|
||||
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
|
||||
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
|
||||
- if @cluster.allow_user_defined_namespace?
|
||||
.form-group
|
||||
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
|
||||
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
|
||||
|
||||
.form-group
|
||||
.form-check
|
||||
|
|
|
|||
|
|
@ -116,6 +116,19 @@
|
|||
%strong.fly-out-top-item-name
|
||||
= _('Members')
|
||||
|
||||
- if group_sidebar_link?(:kubernetes)
|
||||
= nav_link(controller: [:clusters]) do
|
||||
= link_to group_clusters_path(@group) 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 group_clusters_path(@group), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Kubernetes')
|
||||
|
||||
- if group_sidebar_link?(:settings)
|
||||
= nav_link(path: group_nav_link_paths) do
|
||||
= link_to edit_group_path(@group) do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
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
|
|||
|
||||
resource :avatar, only: [:destroy]
|
||||
|
||||
concerns :clusterable
|
||||
|
||||
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
|
||||
post :resend_invite, on: :member
|
||||
delete :leave, on: :collection
|
||||
|
|
|
|||
|
|
@ -1364,6 +1364,15 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Add Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Add a Kubernetes cluster integration"
|
||||
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 an integration to your group will share the cluster across all your projects."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -1466,6 +1475,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Google Kubernetes Engine project"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Group cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Helm Tiller"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -1520,9 +1532,6 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Kubernetes cluster details"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Kubernetes cluster integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -1532,7 +1541,7 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
|
||||
msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project"
|
||||
|
|
@ -1541,10 +1550,13 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Learn more about %{help_link_start}Kubernetes%{help_link_end}."
|
||||
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
|
||||
msgid "ClusterIntegration|Learn more about Kubernetes"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Machine type"
|
||||
|
|
@ -1589,6 +1601,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Project cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Project namespace"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -1679,9 +1694,6 @@ msgstr ""
|
|||
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Toggle Kubernetes Cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Toggle Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
# 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
|
||||
|
|
@ -0,0 +1,574 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::ClustersController do
|
||||
include AccessMatchersForController
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
|
||||
set(:group) { create(:group) }
|
||||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
group.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'GET index' do
|
||||
def go(params = {})
|
||||
get :index, params.reverse_merge(group_id: group)
|
||||
end
|
||||
|
||||
context 'when feature flag is not enabled' do
|
||||
before do
|
||||
stub_feature_flags(group_clusters: false)
|
||||
end
|
||||
|
||||
it 'renders 404' do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(group_clusters: true)
|
||||
end
|
||||
|
||||
describe 'functionality' do
|
||||
context 'when group has one or more clusters' do
|
||||
let(:group) { create(:group) }
|
||||
|
||||
let!(:enabled_cluster) do
|
||||
create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group])
|
||||
end
|
||||
|
||||
let!(:disabled_cluster) do
|
||||
create(:cluster, :disabled, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
|
||||
end
|
||||
|
||||
it 'lists available clusters' do
|
||||
go
|
||||
|
||||
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) { group.clusters.page.total_pages }
|
||||
|
||||
before do
|
||||
allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
|
||||
create_list(:cluster, 2, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
|
||||
end
|
||||
|
||||
it 'redirects to the page' do
|
||||
go(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 group does not have a cluster' do
|
||||
let(:group) { create(:group) }
|
||||
|
||||
it 'returns an empty state page' do
|
||||
go
|
||||
|
||||
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, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
describe 'GET new' do
|
||||
def go
|
||||
get :new, group_id: group
|
||||
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
|
||||
go
|
||||
|
||||
expect(assigns(:authorize_url)).to include(key)
|
||||
expect(session[session_key_for_redirect_uri]).to eq(new_group_cluster_path(group))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when omniauth has not configured' do
|
||||
before do
|
||||
stub_omniauth_setting(providers: [])
|
||||
end
|
||||
|
||||
it 'does not have authorize_url' do
|
||||
go
|
||||
|
||||
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
|
||||
go
|
||||
|
||||
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
|
||||
go
|
||||
|
||||
expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
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
|
||||
end
|
||||
|
||||
describe 'POST create for new cluster' 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 go
|
||||
post :create_gcp, params.merge(group_id: group)
|
||||
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 { go }.to change { Clusters::Cluster.count }
|
||||
.and change { Clusters::Providers::Gcp.count }
|
||||
|
||||
cluster = group.clusters.first
|
||||
|
||||
expect(response).to redirect_to(group_cluster_path(group, 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 { go }.to change { Clusters::Cluster.count }
|
||||
.and change { Clusters::Providers::Gcp.count }
|
||||
expect(group.clusters.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 { 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
|
||||
end
|
||||
|
||||
describe 'POST create for existing cluster' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
name: 'new-cluster',
|
||||
platform_kubernetes_attributes: {
|
||||
api_url: 'http://my-url',
|
||||
token: 'test'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def go
|
||||
post :create_user, params.merge(group_id: group)
|
||||
end
|
||||
|
||||
describe 'functionality' 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 }
|
||||
|
||||
cluster = group.clusters.first
|
||||
|
||||
expect(response).to redirect_to(group_cluster_path(group, 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 { go }.to change { Clusters::Cluster.count }
|
||||
.and change { Clusters::Platforms::Kubernetes.count }
|
||||
|
||||
cluster = group.clusters.first
|
||||
|
||||
expect(response).to redirect_to(group_cluster_path(group, 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 { 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
|
||||
end
|
||||
|
||||
describe 'GET cluster_status' do
|
||||
let(:cluster) { create(:cluster, :providing_by_gcp, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
def go
|
||||
get :cluster_status,
|
||||
group_id: group.to_param,
|
||||
id: cluster,
|
||||
format: :json
|
||||
end
|
||||
|
||||
describe 'functionality' do
|
||||
it 'responds with matching schema' do
|
||||
go
|
||||
|
||||
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)
|
||||
|
||||
go
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
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
|
||||
end
|
||||
|
||||
describe 'GET show' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
def go
|
||||
get :show,
|
||||
group_id: group,
|
||||
id: cluster
|
||||
end
|
||||
|
||||
describe 'functionality' do
|
||||
it 'renders view' do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(assigns(:cluster)).to eq(cluster)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
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
|
||||
end
|
||||
|
||||
describe 'PUT update' do
|
||||
def go(format: :html)
|
||||
put :update, params.merge(
|
||||
group_id: group.to_param,
|
||||
id: cluster,
|
||||
format: format
|
||||
)
|
||||
end
|
||||
|
||||
let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
enabled: false,
|
||||
name: 'my-new-cluster-name'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates and redirects back to show page' do
|
||||
go
|
||||
|
||||
cluster.reload
|
||||
expect(response).to redirect_to(group_cluster_path(group, 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')
|
||||
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'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates and redirects back to show page' do
|
||||
go(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
|
||||
go(format: :json)
|
||||
|
||||
expect(response).to have_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
set(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
describe 'DELETE destroy' do
|
||||
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
def go
|
||||
delete :destroy,
|
||||
group_id: group,
|
||||
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 { go }
|
||||
.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(group_clusters_path(group))
|
||||
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, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
it 'destroys and redirects back to clusters list' do
|
||||
expect { go }
|
||||
.to change { Clusters::Cluster.count }.by(-1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(-1)
|
||||
|
||||
expect(response).to redirect_to(group_clusters_path(group))
|
||||
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, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
it 'destroys and redirects back to clusters list' do
|
||||
expect { go }
|
||||
.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(group_clusters_path(group))
|
||||
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, cluster_type: :group_type, groups: [group]) }
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
context 'no group_id param' do
|
||||
it 'does not respond to any action without group_id param' do
|
||||
expect { get :index }.to raise_error(ActionController::UrlGenerationError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -122,7 +122,7 @@ describe Projects::ClustersController do
|
|||
it 'has new object' do
|
||||
go
|
||||
|
||||
expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::Cluster)
|
||||
expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ describe Projects::ClustersController do
|
|||
it 'has new object' do
|
||||
go
|
||||
|
||||
expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::Cluster)
|
||||
expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -396,20 +396,6 @@ describe Projects::ClustersController do
|
|||
end
|
||||
|
||||
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)
|
||||
put :update, params.merge(namespace_id: project.namespace.to_param,
|
||||
project_id: project.to_param,
|
||||
|
|
@ -423,105 +409,73 @@ describe Projects::ClustersController do
|
|||
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
|
||||
end
|
||||
|
||||
context 'when cluster is provided by GCP' do
|
||||
it "updates and redirects back to show page" do
|
||||
go
|
||||
let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
|
||||
|
||||
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
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
expect(cluster.enabled).to be_truthy
|
||||
end
|
||||
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'
|
||||
}
|
||||
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
|
||||
go
|
||||
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
|
||||
expect(cluster.name).to eq('my-new-cluster-name')
|
||||
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
|
||||
end
|
||||
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
|
||||
expect(cluster.name).to eq('my-new-cluster-name')
|
||||
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
|
||||
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',
|
||||
platform_kubernetes_attributes: {
|
||||
namespace: 'my-namespace'
|
||||
}
|
||||
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',
|
||||
platform_kubernetes_attributes: {
|
||||
namespace: 'my-namespace'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it "updates and redirects back to show page" do
|
||||
go(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')
|
||||
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
context 'when invalid parameters are used' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
enabled: false,
|
||||
platform_kubernetes_attributes: {
|
||||
namespace: 'my invalid namespace #@'
|
||||
}
|
||||
it "updates and redirects back to show page" do
|
||||
go(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')
|
||||
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid parameters are used' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
enabled: false,
|
||||
platform_kubernetes_attributes: {
|
||||
namespace: 'my invalid namespace #@'
|
||||
}
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
it "rejects changes" do
|
||||
go(format: :json)
|
||||
it "rejects changes" do
|
||||
go(format: :json)
|
||||
|
||||
expect(response).to have_http_status(:bad_request)
|
||||
end
|
||||
expect(response).to have_http_status(:bad_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ FactoryBot.define do
|
|||
username 'xxxxxx'
|
||||
password 'xxxxxx'
|
||||
|
||||
after(:create) do |platform_kubernetes, evaluator|
|
||||
before(:create) do |platform_kubernetes, evaluator|
|
||||
pem_file = File.expand_path(Rails.root.join('spec/fixtures/clusters/sample_cert.pem'))
|
||||
platform_kubernetes.ca_cert = File.read(pem_file)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -35,37 +35,6 @@ describe 'Clusters', :js do
|
|||
expect(page).to have_selector('.gl-responsive-table-row', count: 2)
|
||||
end
|
||||
|
||||
context 'inline update of cluster' do
|
||||
it 'user can update cluster' do
|
||||
expect(page).to have_selector('.js-project-feature-toggle')
|
||||
end
|
||||
|
||||
context 'with successful request' do
|
||||
it 'user sees updated cluster' do
|
||||
expect do
|
||||
page.find('.js-project-feature-toggle').click
|
||||
wait_for_requests
|
||||
end.to change { cluster.reload.enabled }
|
||||
|
||||
expect(page).not_to have_selector('.is-checked')
|
||||
expect(cluster.reload).not_to be_enabled
|
||||
end
|
||||
end
|
||||
|
||||
context 'with failed request' do
|
||||
it 'user sees not update cluster and error message' do
|
||||
expect_any_instance_of(Clusters::UpdateService).to receive(:execute).and_call_original
|
||||
allow_any_instance_of(Clusters::Cluster).to receive(:valid?) { false }
|
||||
|
||||
page.find('.js-project-feature-toggle').click
|
||||
|
||||
expect(page).to have_content('Something went wrong on our end.')
|
||||
expect(page).to have_selector('.is-checked')
|
||||
expect(cluster.reload).to be_enabled
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user clicks on a cluster' do
|
||||
before do
|
||||
click_link cluster.name
|
||||
|
|
|
|||
|
|
@ -343,4 +343,26 @@ describe Clusters::Cluster do
|
|||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#allow_user_defined_namespace?' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
||||
|
||||
subject { cluster.allow_user_defined_namespace? }
|
||||
|
||||
context 'project type cluster' do
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'group type cluster' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp, :group) }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'instance type cluster' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -58,6 +58,18 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
|
|||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'for group cluster' do
|
||||
let(:namespace) { 'namespace-123' }
|
||||
let(:cluster) { build(:cluster, :group, :provided_by_user) }
|
||||
let(:kubernetes) { cluster.platform_kubernetes }
|
||||
|
||||
before do
|
||||
kubernetes.namespace = namespace
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validates api_url' do
|
||||
|
|
|
|||
|
|
@ -24,5 +24,47 @@ describe Clusters::ClusterPolicy, :models do
|
|||
it { expect(policy).to be_allowed :update_cluster }
|
||||
it { expect(policy).to be_allowed :admin_cluster }
|
||||
end
|
||||
|
||||
context 'group cluster' do
|
||||
let(:cluster) { create(:cluster, :group) }
|
||||
let(:group) { cluster.group }
|
||||
let(:project) { create(:project, namespace: group) }
|
||||
|
||||
context 'when group developer' do
|
||||
before do
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
it { expect(policy).to be_disallowed :update_cluster }
|
||||
it { expect(policy).to be_disallowed :admin_cluster }
|
||||
end
|
||||
|
||||
context 'when group maintainer' do
|
||||
before do
|
||||
group.add_maintainer(user)
|
||||
end
|
||||
|
||||
it { expect(policy).to be_allowed :update_cluster }
|
||||
it { expect(policy).to be_allowed :admin_cluster }
|
||||
end
|
||||
|
||||
context 'when project maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it { expect(policy).to be_disallowed :update_cluster }
|
||||
it { expect(policy).to be_disallowed :admin_cluster }
|
||||
end
|
||||
|
||||
context 'when project developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it { expect(policy).to be_disallowed :update_cluster }
|
||||
it { expect(policy).to be_disallowed :admin_cluster }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@ describe GroupPolicy do
|
|||
|
||||
let(:maintainer_permissions) do
|
||||
[
|
||||
:create_projects
|
||||
:create_projects,
|
||||
:read_cluster,
|
||||
:create_cluster,
|
||||
:update_cluster,
|
||||
:admin_cluster
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ describe ProjectPolicy do
|
|||
:create_build, :read_build, :update_build, :admin_build, :destroy_build,
|
||||
:create_pipeline_schedule, :read_pipeline_schedule, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule,
|
||||
:create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment,
|
||||
:create_cluster, :read_cluster, :update_cluster, :admin_cluster, :destroy_cluster,
|
||||
:create_cluster, :read_cluster, :update_cluster, :admin_cluster,
|
||||
:create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment
|
||||
]
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ describe ProjectPolicy do
|
|||
:create_build, :read_build, :update_build, :admin_build, :destroy_build,
|
||||
:create_pipeline_schedule, :read_pipeline_schedule, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule,
|
||||
:create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment,
|
||||
:create_cluster, :read_cluster, :update_cluster, :admin_cluster, :destroy_cluster,
|
||||
:create_cluster, :read_cluster, :update_cluster, :admin_cluster,
|
||||
:create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -82,5 +82,12 @@ describe Clusters::ClusterPresenter do
|
|||
|
||||
it { is_expected.to eq(project_cluster_path(project, cluster)) }
|
||||
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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
# 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
|
||||
|
|
@ -60,14 +60,6 @@ describe Clusters::Applications::CreateService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'invalid application' do
|
||||
let(:params) { { application: 'non-existent' } }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'knative application' do
|
||||
let(:params) do
|
||||
{
|
||||
|
|
@ -100,5 +92,39 @@ describe Clusters::Applications::CreateService do
|
|||
expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'group cluster' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp, :group) }
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
|
||||
end
|
||||
|
||||
where(:application, :association, :allowed) do
|
||||
'helm' | :application_helm | true
|
||||
'ingress' | :application_ingress | true
|
||||
'runner' | :application_runner | false
|
||||
'jupyter' | :application_jupyter | false
|
||||
'prometheus' | :application_prometheus | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:params) { { application: application } }
|
||||
|
||||
it 'executes for each application' do
|
||||
if allowed
|
||||
expect do
|
||||
subject
|
||||
|
||||
cluster.reload
|
||||
end.to change(cluster, association)
|
||||
else
|
||||
expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -62,5 +62,32 @@ describe Clusters::UpdateService do
|
|||
expect(cluster.errors[:"platform_kubernetes.namespace"]).to be_present
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue