From dacd0ee18b617f0c81c4a478a4d801b3c37e0c56 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Wed, 10 Jul 2019 15:33:09 +1000 Subject: Refactor: model errors for multi cluster validation The current approach requires catching exceptions to handle these errors and callers are already handling model validations so it seems more appropriate. Also it seemed to convoluted to add this logic directly to the model since the model needs to check too many possible associations to determine whether or not there are more than one cluster since the model doesn't know what it's being created on. Additionally we only wanted to validate during create to avoid the risk of existing models becoming invalid by many different edge cases. --- app/policies/clusters/instance_policy.rb | 7 ------- app/policies/concerns/clusterable_actions.rb | 14 -------------- app/policies/group_policy.rb | 7 ------- app/policies/project_policy.rb | 6 ------ app/presenters/clusterable_presenter.rb | 14 +++++++++++++- app/services/clusters/create_service.rb | 17 ++++++++++------- lib/api/project_clusters.rb | 2 +- spec/requests/api/project_clusters_spec.rb | 16 +++++++++++++--- .../policies/clusterable_shared_examples.rb | 8 -------- 9 files changed, 37 insertions(+), 54 deletions(-) delete mode 100644 app/policies/concerns/clusterable_actions.rb diff --git a/app/policies/clusters/instance_policy.rb b/app/policies/clusters/instance_policy.rb index f72096e8fc6..bd7ff413afe 100644 --- a/app/policies/clusters/instance_policy.rb +++ b/app/policies/clusters/instance_policy.rb @@ -2,11 +2,6 @@ module Clusters class InstancePolicy < BasePolicy - include ClusterableActions - - condition(:has_clusters, scope: :subject) { clusterable_has_clusters? } - condition(:can_have_multiple_clusters) { multiple_clusters_available? } - rule { admin }.policy do enable :read_cluster enable :add_cluster @@ -14,7 +9,5 @@ module Clusters enable :update_cluster enable :admin_cluster end - - rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster end end diff --git a/app/policies/concerns/clusterable_actions.rb b/app/policies/concerns/clusterable_actions.rb deleted file mode 100644 index 08ddd742ea9..00000000000 --- a/app/policies/concerns/clusterable_actions.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module ClusterableActions - private - - # Overridden on EE module - def multiple_clusters_available? - false - end - - def clusterable_has_clusters? - !subject.clusters.empty? - end -end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index ea86858181d..9219283992f 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class GroupPolicy < BasePolicy - include ClusterableActions - desc "Group is public" with_options scope: :subject, score: 0 condition(:public_group) { @subject.public? } @@ -29,9 +27,6 @@ class GroupPolicy < BasePolicy GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any? end - condition(:has_clusters, scope: :subject) { clusterable_has_clusters? } - condition(:can_have_multiple_clusters) { multiple_clusters_available? } - with_options scope: :subject, score: 0 condition(:request_access_enabled) { @subject.request_access_enabled } @@ -121,8 +116,6 @@ class GroupPolicy < BasePolicy rule { owner & (~share_with_group_locked | ~has_parent | ~parent_share_with_group_locked | can_change_parent_share_with_group_lock) }.enable :change_share_with_group_lock - rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster - rule { developer & developer_maintainer_access }.enable :create_projects rule { create_projects_disabled }.prevent :create_projects diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 3c9ffbb2065..e79bac6bee3 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -2,7 +2,6 @@ class ProjectPolicy < BasePolicy extend ClassMethods - include ClusterableActions READONLY_FEATURES_WHEN_ARCHIVED = %i[ issue @@ -114,9 +113,6 @@ class ProjectPolicy < BasePolicy @subject.feature_available?(:merge_requests, @user) end - condition(:has_clusters, scope: :subject) { clusterable_has_clusters? } - condition(:can_have_multiple_clusters) { multiple_clusters_available? } - condition(:internal_builds_disabled) do !@subject.builds_enabled? end @@ -430,8 +426,6 @@ class ProjectPolicy < BasePolicy (~guest & can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request) end.enable :read_merge_request_iid - rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster - rule { ~can?(:read_cross_project) & ~classification_label_authorized }.policy do # Preventing access here still allows the projects to be listed. Listing # projects doesn't check the `:read_project` ability. But instead counts diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index 34bdf156623..fff6d23efdf 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -13,7 +13,8 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated end def can_add_cluster? - can?(current_user, :add_cluster, clusterable) + can?(current_user, :add_cluster, clusterable) && + (has_no_clusters? || multiple_clusters_available?) end def can_create_cluster? @@ -63,4 +64,15 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated def learn_more_link raise NotImplementedError end + + private + + # Overridden on EE module + def multiple_clusters_available? + false + end + + def has_no_clusters? + clusterable.clusters.empty? + end end diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 886e484caaf..5fb5e15c32d 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -10,24 +10,27 @@ module Clusters def execute(access_token: nil) raise ArgumentError, 'Unknown clusterable provided' unless clusterable - raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster? cluster_params = params.merge(user: current_user).merge(clusterable_params) cluster_params[:provider_gcp_attributes].try do |provider| provider[:access_token] = access_token end - create_cluster(cluster_params).tap do |cluster| - ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted? + cluster = Clusters::Cluster.new(cluster_params) + + unless can_create_cluster? + cluster.errors.add(:base, _('Instance does not support multiple Kubernetes clusters')) end - end - private + return cluster if cluster.errors.present? - def create_cluster(cluster_params) - Clusters::Cluster.create(cluster_params) + cluster.tap do |cluster| + cluster.save && ClusterProvisionWorker.perform_async(cluster.id) + end end + private + def clusterable @clusterable ||= params.delete(:clusterable) end diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb index dcc8d94fb79..4f093e9be08 100644 --- a/lib/api/project_clusters.rb +++ b/lib/api/project_clusters.rb @@ -65,7 +65,7 @@ module API use :create_params_ee end post ':id/clusters/user' do - authorize! :add_cluster, user_project, 'Instance does not support multiple Kubernetes clusters' + authorize! :add_cluster, user_project user_cluster = ::Clusters::CreateService .new(current_user, create_cluster_user_params) diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index a6e08ab3ab6..e8ed016db69 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -257,12 +257,22 @@ describe API::ProjectClusters do post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params end + it 'responds with 400' do + expect(response).to have_gitlab_http_status(400) + + expect(json_response['message']['base'].first).to eq('Instance does not support multiple Kubernetes clusters') + end + end + + context 'non-authorized user' do + before do + post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params + end + it 'responds with 403' do expect(response).to have_gitlab_http_status(403) - end - it 'returns an appropriate message' do - expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters') + expect(json_response['message']).to eq('403 Forbidden') end end end diff --git a/spec/support/shared_examples/policies/clusterable_shared_examples.rb b/spec/support/shared_examples/policies/clusterable_shared_examples.rb index d99f94c76c3..4f9873d53e4 100644 --- a/spec/support/shared_examples/policies/clusterable_shared_examples.rb +++ b/spec/support/shared_examples/policies/clusterable_shared_examples.rb @@ -24,14 +24,6 @@ shared_examples 'clusterable policies' do context 'with no clusters' do it { expect_allowed(:add_cluster) } end - - context 'with an existing cluster' do - before do - cluster - end - - it { expect_disallowed(:add_cluster) } - end end end end -- cgit v1.2.1