summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacques Erasmus <jerasmus@gitlab.com>2019-05-27 08:23:17 +0000
committerSean McGivern <sean@gitlab.com>2019-05-27 08:23:17 +0000
commit3c913df28eb390bc2746b05b4911a62b76593d8b (patch)
treed4df3cce34ea114b6c8ba99ffcccf8e3dd13e735
parent340ea9aa9dfc412bf7c4335a11b81c08e8330655 (diff)
downloadgitlab-ce-3c913df28eb390bc2746b05b4911a62b76593d8b.tar.gz
Add changelog entry
Added a changelog entry for the feature
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js48
-rw-r--r--app/assets/stylesheets/pages/clusters.scss2
-rw-r--r--app/models/clusters/cluster.rb65
-rw-r--r--app/presenters/clusters/cluster_presenter.rb4
-rw-r--r--app/views/clusters/clusters/_banner.html.haml12
-rw-r--r--app/views/clusters/clusters/show.html.haml3
-rw-r--r--changelogs/unreleased/55447-validate-k8s-credentials.yml5
-rw-r--r--lib/gitlab/kubernetes/errors.rb23
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb3
-rw-r--r--spec/features/groups/clusters/user_spec.rb1
-rw-r--r--spec/features/projects/clusters/user_spec.rb1
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js16
-rw-r--r--spec/models/clusters/cluster_spec.rb163
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb40
15 files changed, 317 insertions, 75 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 561b6bdd9f1..70af333a0dd 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -1,5 +1,6 @@
import Visibility from 'visibilityjs';
import Vue from 'vue';
+import AccessorUtilities from '~/lib/utils/accessor';
import { GlToast } from '@gitlab/ui';
import PersistentUserCallout from '../persistent_user_callout';
import { s__, sprintf } from '../locale';
@@ -43,8 +44,10 @@ export default class Clusters {
helpPath,
ingressHelpPath,
ingressDnsHelpPath,
+ clusterId,
} = document.querySelector('.js-edit-cluster-form').dataset;
+ this.clusterId = clusterId;
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
@@ -69,6 +72,10 @@ export default class Clusters {
this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success');
this.creatingContainer = document.querySelector('.js-cluster-creating');
+ this.unreachableContainer = document.querySelector('.js-cluster-api-unreachable');
+ this.authenticationFailureContainer = document.querySelector(
+ '.js-cluster-authentication-failure',
+ );
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.showTokenButton = document.querySelector('.js-show-cluster-token');
@@ -125,6 +132,13 @@ export default class Clusters {
PersistentUserCallout.factory(callout);
}
+ addBannerCloseHandler(el, status) {
+ el.querySelector('.js-close-banner').addEventListener('click', () => {
+ el.classList.add('hidden');
+ this.setBannerDismissedState(status, true);
+ });
+ }
+
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
@@ -133,6 +147,9 @@ export default class Clusters {
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
+ // Add event listener to all the banner close buttons
+ this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
+ this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
}
removeListeners() {
@@ -205,6 +222,8 @@ export default class Clusters {
this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden');
this.creatingContainer.classList.add('hidden');
+ this.unreachableContainer.classList.add('hidden');
+ this.authenticationFailureContainer.classList.add('hidden');
}
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
@@ -228,9 +247,32 @@ export default class Clusters {
}
}
+ setBannerDismissedState(status, isDismissed) {
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ window.localStorage.setItem(
+ `cluster_${this.clusterId}_banner_dismissed`,
+ `${status}_${isDismissed}`,
+ );
+ }
+ }
+
+ isBannerDismissed(status) {
+ let bannerState;
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ bannerState = window.localStorage.getItem(`cluster_${this.clusterId}_banner_dismissed`);
+ }
+
+ return bannerState === `${status}_true`;
+ }
+
updateContainer(prevStatus, status, error) {
this.hideAll();
+ if (this.isBannerDismissed(status)) {
+ return;
+ }
+ this.setBannerDismissedState(status, false);
+
// We poll all the time but only want the `created` banner to show when newly created
if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) {
switch (status) {
@@ -241,6 +283,12 @@ export default class Clusters {
this.errorContainer.classList.remove('hidden');
this.errorReasonContainer.textContent = error;
break;
+ case 'unreachable':
+ this.unreachableContainer.classList.remove('hidden');
+ break;
+ case 'authentication_failure':
+ this.authenticationFailureContainer.classList.remove('hidden');
+ break;
case 'scheduled':
case 'creating':
this.creatingContainer.classList.remove('hidden');
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index 809ba6d4953..255383d89c8 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -69,6 +69,8 @@
align-self: flex-start;
font-weight: 500;
font-size: 20px;
+ color: $orange-900;
+ opacity: 1;
margin: $gl-padding-8 14px 0 0;
}
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 3c6e185f9e2..57a1e461b2d 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -5,8 +5,10 @@ module Clusters
include Presentable
include Gitlab::Utils::StrongMemoize
include FromUnion
+ include ReactiveCaching
self.table_name = 'clusters'
+ self.reactive_cache_key = -> (cluster) { [cluster.class.model_name.singular, cluster.id] }
PROJECT_ONLY_APPLICATIONS = {
Applications::Jupyter.application_name => Applications::Jupyter,
@@ -57,6 +59,8 @@ module Clusters
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
+ after_save :clear_reactive_cache!
+
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
@@ -123,15 +127,19 @@ module Clusters
end
def status_name
- if provider
- provider.status_name
- else
- :created
+ provider&.status_name || connection_status.presence || :created
+ end
+
+ def connection_status
+ with_reactive_cache do |data|
+ data[:connection_status]
end
end
- def created?
- status_name == :created
+ def calculate_reactive_cache
+ return unless enabled?
+
+ { connection_status: retrieve_connection_status }
end
def applications
@@ -221,6 +229,51 @@ module Clusters
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end
+ def retrieve_connection_status
+ kubeclient.core_client.discover
+ rescue *Gitlab::Kubernetes::Errors::CONNECTION
+ :unreachable
+ rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION
+ :authentication_failure
+ rescue Kubeclient::HttpError => e
+ kubeclient_error_status(e.message)
+ rescue => e
+ Gitlab::Sentry.track_acceptable_exception(e, extra: { cluster_id: id })
+
+ :unknown_failure
+ else
+ :connected
+ end
+
+ # KubeClient uses the same error class
+ # For connection errors (eg. timeout) and
+ # for Kubernetes errors.
+ def kubeclient_error_status(message)
+ if message&.match?(/timed out|timeout/i)
+ :unreachable
+ else
+ :authentication_failure
+ end
+ end
+
+ # To keep backward compatibility with AUTO_DEVOPS_DOMAIN
+ # environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
+ # is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
+ # ProjectAutoDevops#Domain, project variables or group variables,
+ # as the AUTO_DEVOPS_DOMAIN is needed for CI_ENVIRONMENT_URL
+ #
+ # This method should is scheduled to be removed on
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/56959
+ def legacy_auto_devops_domain
+ if project_type?
+ project&.auto_devops&.domain.presence ||
+ project.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence ||
+ project.group&.variables&.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
+ elsif group_type?
+ group.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
+ end
+ end
+
def restrict_modification
if provider&.on_creation?
errors.add(:base, "cannot modify during creation")
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index 33b217c8498..1634d2479a0 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -22,10 +22,6 @@ module Clusters
"https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp?
end
- def can_toggle_cluster?
- can?(current_user, :update_cluster, cluster) && created?
- end
-
def can_read_cluster?
can?(current_user, :read_cluster, cluster)
end
diff --git a/app/views/clusters/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml
index 160c5f009a7..a5de67be96b 100644
--- a/app/views/clusters/clusters/_banner.html.haml
+++ b/app/views/clusters/clusters/_banner.html.haml
@@ -5,5 +5,17 @@
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
+.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
+ .col-11
+ = s_('ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct.')
+ .col-1.p-0
+ %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
+
+.hidden.js-cluster-authentication-failure.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
+ .col-11
+ = s_('ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid.')
+ .col-1.p-0
+ %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
+
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index deb6b21e2be..4dfbb310142 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -24,7 +24,8 @@
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'),
- manage_prometheus_path: manage_prometheus_path } }
+ manage_prometheus_path: manage_prometheus_path,
+ cluster_id: @cluster.id } }
.js-cluster-application-notice
.flash-container
diff --git a/changelogs/unreleased/55447-validate-k8s-credentials.yml b/changelogs/unreleased/55447-validate-k8s-credentials.yml
new file mode 100644
index 00000000000..81f0efdb325
--- /dev/null
+++ b/changelogs/unreleased/55447-validate-k8s-credentials.yml
@@ -0,0 +1,5 @@
+---
+title: Validate Kubernetes credentials at cluster creation
+merge_request: 27403
+author:
+type: added
diff --git a/lib/gitlab/kubernetes/errors.rb b/lib/gitlab/kubernetes/errors.rb
new file mode 100644
index 00000000000..81bf636eef7
--- /dev/null
+++ b/lib/gitlab/kubernetes/errors.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ module Errors
+ CONNECTION = [
+ SocketError,
+ OpenSSL::SSL::SSLError,
+ Errno::ECONNRESET,
+ Errno::ENETUNREACH,
+ Errno::ECONNREFUSED,
+ Errno::EHOSTUNREACH,
+ Net::OpenTimeout,
+ Net::ReadTimeout,
+ IPAddr::InvalidAddressError
+ ].freeze
+
+ AUTHENTICATION = [
+ OpenSSL::X509::CertificateError
+ ].freeze
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 700266bc026..acebc86f900 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2505,6 +2505,9 @@ msgstr ""
msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
+msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
+msgstr ""
+
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr ""
@@ -2559,6 +2562,9 @@ msgstr ""
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
+msgid "ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct."
+msgstr ""
+
msgid "ClusterIntegration|Zone"
msgstr ""
diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb
index d2e46d15730..683c57a97f8 100644
--- a/spec/features/clusters/cluster_detail_page_spec.rb
+++ b/spec/features/clusters/cluster_detail_page_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe 'Clusterable > Show page' do
+ include KubernetesHelpers
+
let(:current_user) { create(:user) }
let(:cluster_ingress_help_text_selector) { '.js-ingress-domain-help-text' }
let(:hide_modifier_selector) { '.hide' }
@@ -83,6 +85,7 @@ describe 'Clusterable > Show page' do
shared_examples 'editing a user-provided cluster' do
before do
+ stub_kubeclient_discover(cluster.platform.api_url)
clusterable.add_maintainer(current_user)
visit cluster_path
end
diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb
index b661b5cbaef..84a8691a7f2 100644
--- a/spec/features/groups/clusters/user_spec.rb
+++ b/spec/features/groups/clusters/user_spec.rb
@@ -14,6 +14,7 @@ describe 'User Cluster', :js do
allow(Groups::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
end
context 'when user does not have a cluster and visits cluster index page' do
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index fe4f737a7da..31cc09ae911 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -12,6 +12,7 @@ describe 'User Cluster', :js do
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
end
context 'when user does not have a cluster and visits cluster index page' do
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index 73897107f67..66b22fa2681 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -209,6 +209,22 @@ describe('Clusters', () => {
expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
});
});
+
+ describe('when cluster is unreachable', () => {
+ it('should show the unreachable warning container', () => {
+ cluster.updateContainer(null, 'unreachable');
+
+ expect(cluster.unreachableContainer.classList.contains('hidden')).toBe(false);
+ });
+ });
+
+ describe('when cluster has an authentication failure', () => {
+ it('should show the authentication failure warning container', () => {
+ cluster.updateContainer(null, 'authentication_failure');
+
+ expect(cluster.authenticationFailureContainer.classList.contains('hidden')).toBe(false);
+ });
+ });
});
describe('installApplication', () => {
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index f066ed6b620..4739e62289a 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -2,7 +2,10 @@
require 'spec_helper'
-describe Clusters::Cluster do
+describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
+ include ReactiveCachingHelpers
+ include KubernetesHelpers
+
it_behaves_like 'having unique enum values'
subject { build(:cluster) }
@@ -23,7 +26,6 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
- it { is_expected.to delegate_method(:status_name).to(:provider) }
it { is_expected.to delegate_method(:on_creation?).to(:provider) }
it { is_expected.to delegate_method(:active?).to(:platform_kubernetes).with_prefix }
it { is_expected.to delegate_method(:rbac?).to(:platform_kubernetes).with_prefix }
@@ -501,28 +503,6 @@ describe Clusters::Cluster do
end
end
- describe '#created?' do
- let(:cluster) { create(:cluster, :provided_by_gcp) }
-
- subject { cluster.created? }
-
- context 'when status_name is :created' do
- before do
- allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:created)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'when status_name is not :created' do
- before do
- allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:creating)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
describe '#allow_user_defined_namespace?' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
@@ -617,4 +597,139 @@ describe Clusters::Cluster do
it { is_expected.to be_truthy }
end
end
+
+ describe '#status_name' do
+ subject { cluster.status_name }
+
+ context 'the cluster has a provider' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ before do
+ cluster.provider.make_errored!
+ end
+
+ it { is_expected.to eq :errored }
+ end
+
+ context 'there is a cached connection status' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ before do
+ allow(cluster).to receive(:connection_status).and_return(:connected)
+ end
+
+ it { is_expected.to eq :connected }
+ end
+
+ context 'there is no connection status in the cache' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ before do
+ allow(cluster).to receive(:connection_status).and_return(nil)
+ end
+
+ it { is_expected.to eq :created }
+ end
+ end
+
+ describe '#connection_status' do
+ let(:cluster) { create(:cluster) }
+ let(:status) { :connected }
+
+ subject { cluster.connection_status }
+
+ it { is_expected.to be_nil }
+
+ context 'with a cached status' do
+ before do
+ stub_reactive_cache(cluster, connection_status: status)
+ end
+
+ it { is_expected.to eq(status) }
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ subject { cluster.calculate_reactive_cache }
+
+ context 'cluster is disabled' do
+ let(:cluster) { create(:cluster, :disabled) }
+
+ it 'does not populate the cache' do
+ expect(cluster).not_to receive(:retrieve_connection_status)
+
+ is_expected.to be_nil
+ end
+ end
+
+ context 'cluster is enabled' do
+ let(:cluster) { create(:cluster, :provided_by_user, :group) }
+
+ context 'connection to the cluster is successful' do
+ before do
+ stub_kubeclient_discover(cluster.platform.api_url)
+ end
+
+ it { is_expected.to eq(connection_status: :connected) }
+ end
+
+ context 'cluster cannot be reached' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(SocketError)
+ end
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+
+ context 'cluster cannot be authenticated to' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(OpenSSL::X509::CertificateError.new("Certificate error"))
+ end
+
+ it { is_expected.to eq(connection_status: :authentication_failure) }
+ end
+
+ describe 'Kubeclient::HttpError' do
+ let(:error_code) { 403 }
+ let(:error_message) { "Forbidden" }
+
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(Kubeclient::HttpError.new(error_code, error_message, nil))
+ end
+
+ it { is_expected.to eq(connection_status: :authentication_failure) }
+
+ context 'generic timeout' do
+ let(:error_message) { 'Timed out connecting to server'}
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+
+ context 'gateway timeout' do
+ let(:error_message) { '504 Gateway Timeout for GET https://kubernetes.example.com/api/v1'}
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+ end
+
+ context 'an uncategorised error is raised' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(StandardError)
+ end
+
+ it { is_expected.to eq(connection_status: :unknown_failure) }
+
+ it 'notifies Sentry' do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception)
+ .with(instance_of(StandardError), hash_including(extra: { cluster_id: cluster.id }))
+
+ subject
+ end
+ end
+ end
+ end
end
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 42701a5f8d1..7054a70e2ed 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -158,46 +158,6 @@ describe Clusters::ClusterPresenter do
it { is_expected.to include(cluster.name) }
end
- describe '#can_toggle_cluster' do
- let(:user) { create(:user) }
-
- before do
- allow(cluster).to receive(:current_user).and_return(user)
- end
-
- subject { described_class.new(cluster).can_toggle_cluster? }
-
- context 'when user can update' do
- before do
- allow_any_instance_of(described_class).to receive(:can?).with(user, :update_cluster, cluster).and_return(true)
- end
-
- context 'when cluster is created' do
- before do
- allow(cluster).to receive(:created?).and_return(true)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'when cluster is not created' do
- before do
- allow(cluster).to receive(:created?).and_return(false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
- context 'when user can not update' do
- before do
- allow_any_instance_of(described_class).to receive(:can?).with(user, :update_cluster, cluster).and_return(false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
describe '#cluster_type_description' do
subject { described_class.new(cluster).cluster_type_description }