Commit dacd0ee1 authored by Dylan Griffith's avatar Dylan Griffith

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.
parent 4efc8574
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
module Clusters module Clusters
class InstancePolicy < BasePolicy 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 rule { admin }.policy do
enable :read_cluster enable :read_cluster
enable :add_cluster enable :add_cluster
...@@ -14,7 +9,5 @@ module Clusters ...@@ -14,7 +9,5 @@ module Clusters
enable :update_cluster enable :update_cluster
enable :admin_cluster enable :admin_cluster
end end
rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
end end
end end
# 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
# frozen_string_literal: true # frozen_string_literal: true
class GroupPolicy < BasePolicy class GroupPolicy < BasePolicy
include ClusterableActions
desc "Group is public" desc "Group is public"
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
condition(:public_group) { @subject.public? } condition(:public_group) { @subject.public? }
...@@ -29,9 +27,6 @@ class GroupPolicy < BasePolicy ...@@ -29,9 +27,6 @@ class GroupPolicy < BasePolicy
GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any? GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any?
end end
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
condition(:can_have_multiple_clusters) { multiple_clusters_available? }
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
condition(:request_access_enabled) { @subject.request_access_enabled } condition(:request_access_enabled) { @subject.request_access_enabled }
...@@ -121,8 +116,6 @@ class GroupPolicy < BasePolicy ...@@ -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 { 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 { developer & developer_maintainer_access }.enable :create_projects
rule { create_projects_disabled }.prevent :create_projects rule { create_projects_disabled }.prevent :create_projects
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
class ProjectPolicy < BasePolicy class ProjectPolicy < BasePolicy
extend ClassMethods extend ClassMethods
include ClusterableActions
READONLY_FEATURES_WHEN_ARCHIVED = %i[ READONLY_FEATURES_WHEN_ARCHIVED = %i[
issue issue
...@@ -114,9 +113,6 @@ class ProjectPolicy < BasePolicy ...@@ -114,9 +113,6 @@ class ProjectPolicy < BasePolicy
@subject.feature_available?(:merge_requests, @user) @subject.feature_available?(:merge_requests, @user)
end end
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
condition(:can_have_multiple_clusters) { multiple_clusters_available? }
condition(:internal_builds_disabled) do condition(:internal_builds_disabled) do
!@subject.builds_enabled? !@subject.builds_enabled?
end end
...@@ -430,8 +426,6 @@ class ProjectPolicy < BasePolicy ...@@ -430,8 +426,6 @@ class ProjectPolicy < BasePolicy
(~guest & can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request) (~guest & can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request)
end.enable :read_merge_request_iid 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 rule { ~can?(:read_cross_project) & ~classification_label_authorized }.policy do
# Preventing access here still allows the projects to be listed. Listing # Preventing access here still allows the projects to be listed. Listing
# projects doesn't check the `:read_project` ability. But instead counts # projects doesn't check the `:read_project` ability. But instead counts
......
...@@ -13,7 +13,8 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated ...@@ -13,7 +13,8 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
end end
def can_add_cluster? def can_add_cluster?
can?(current_user, :add_cluster, clusterable) can?(current_user, :add_cluster, clusterable) &&
(has_no_clusters? || multiple_clusters_available?)
end end
def can_create_cluster? def can_create_cluster?
...@@ -63,4 +64,15 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated ...@@ -63,4 +64,15 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
def learn_more_link def learn_more_link
raise NotImplementedError raise NotImplementedError
end end
private
# Overridden on EE module
def multiple_clusters_available?
false
end
def has_no_clusters?
clusterable.clusters.empty?
end
end end
...@@ -10,23 +10,26 @@ module Clusters ...@@ -10,23 +10,26 @@ module Clusters
def execute(access_token: nil) def execute(access_token: nil)
raise ArgumentError, 'Unknown clusterable provided' unless clusterable 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 = params.merge(user: current_user).merge(clusterable_params)
cluster_params[:provider_gcp_attributes].try do |provider| cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token provider[:access_token] = access_token
end end
create_cluster(cluster_params).tap do |cluster| cluster = Clusters::Cluster.new(cluster_params)
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
end 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) cluster.tap do |cluster|
Clusters::Cluster.create(cluster_params) cluster.save && ClusterProvisionWorker.perform_async(cluster.id)
end end
end
private
def clusterable def clusterable
@clusterable ||= params.delete(:clusterable) @clusterable ||= params.delete(:clusterable)
......
...@@ -65,7 +65,7 @@ module API ...@@ -65,7 +65,7 @@ module API
use :create_params_ee use :create_params_ee
end end
post ':id/clusters/user' do 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 user_cluster = ::Clusters::CreateService
.new(current_user, create_cluster_user_params) .new(current_user, create_cluster_user_params)
......
...@@ -257,12 +257,22 @@ describe API::ProjectClusters do ...@@ -257,12 +257,22 @@ describe API::ProjectClusters do
post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end 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 it 'responds with 403' do
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(403)
end
it 'returns an appropriate message' do expect(json_response['message']).to eq('403 Forbidden')
expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters')
end end
end end
end end
......
...@@ -24,14 +24,6 @@ shared_examples 'clusterable policies' do ...@@ -24,14 +24,6 @@ shared_examples 'clusterable policies' do
context 'with no clusters' do context 'with no clusters' do
it { expect_allowed(:add_cluster) } it { expect_allowed(:add_cluster) }
end end
context 'with an existing cluster' do
before do
cluster
end
it { expect_disallowed(:add_cluster) }
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment