Commit 5ab87438 authored by Mikolaj Wawrzyniak's avatar Mikolaj Wawrzyniak

Remove read_cluster_health permission

Because we are moving cluster health dashboard to the core
https://gitlab.com/gitlab-org/gitlab/-/issues/208224
permission read_cluster_health is no longer viable.
parent f243a31c
...@@ -896,7 +896,7 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then ...@@ -896,7 +896,7 @@ if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
++ CI_SERVER_VERSION_PATCH=0 ++ CI_SERVER_VERSION_PATCH=0
++ export CI_SERVER_REVISION=f4cc00ae823 ++ export CI_SERVER_REVISION=f4cc00ae823
++ CI_SERVER_REVISION=f4cc00ae823 ++ CI_SERVER_REVISION=f4cc00ae823
++ export GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_clusters,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,cluster_health,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal ++ export GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_clusters,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
++ GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_clusters,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,cluster_health,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal ++ GITLAB_FEATURES=audit_events,burndown_charts,code_owners,contribution_analytics,description_diffs,elastic_search,group_bulk_edit,group_burndown_charts,group_webhooks,issuable_default_templates,issue_weights,jenkins_integration,ldap_group_sync,member_lock,merge_request_approvers,multiple_issue_assignees,multiple_ldap_servers,multiple_merge_request_assignees,protected_refs_for_users,push_rules,related_issues,repository_mirrors,repository_size_limit,scoped_issue_board,usage_quotas,visual_review_app,wip_limits,adjourned_deletion_for_projects_and_groups,admin_audit_log,auditor_user,batch_comments,blocking_merge_requests,board_assignee_lists,board_milestone_lists,ci_cd_projects,cluster_deployments,code_analytics,code_owner_approval_required,commit_committer_check,cross_project_pipelines,custom_file_templates,custom_file_templates_for_namespace,custom_project_templates,custom_prometheus_metrics,cycle_analytics_for_groups,db_load_balancing,default_project_deletion_protection,dependency_proxy,deploy_board,design_management,email_additional_text,extended_audit_events,external_authorization_service_api_management,feature_flags,file_locks,geo,github_project_service_integration,group_allowed_email_domains,group_project_templates,group_saml,issues_analytics,jira_dev_panel_integration,ldap_group_sync_filter,merge_pipelines,merge_request_performance_metrics,merge_trains,metrics_reports,multiple_approval_rules,multiple_clusters,multiple_group_issue_boards,object_storage,operations_dashboard,packages,productivity_analytics,project_aliases,protected_environments,reject_unsigned_commits,required_ci_templates,scoped_labels,service_desk,smartcard_auth,group_timelogs,type_of_work_analytics,unprotection_restrictions,ci_project_subscriptions,cluster_health,container_scanning,dast,dependency_scanning,epics,group_ip_restriction,incident_management,insights,license_management,personal_access_token_expiration_policy,pod_logs,prometheus_alerts,pseudonymizer,report_approver_rules,sast,security_dashboard,tracing,web_ide_terminal
++ export CI_PROJECT_ID=17893 ++ export CI_PROJECT_ID=17893
++ CI_PROJECT_ID=17893 ++ CI_PROJECT_ID=17893
......
...@@ -9,7 +9,6 @@ module EE ...@@ -9,7 +9,6 @@ module EE
prepended do prepended do
before_action :expire_etag_cache, only: [:show] before_action :expire_etag_cache, only: [:show]
before_action :authorize_read_prometheus!, only: :prometheus_proxy before_action :authorize_read_prometheus!, only: :prometheus_proxy
before_action :authorize_read_cluster_health!, only: [:metrics_dashboard]
end end
def metrics def metrics
...@@ -71,10 +70,6 @@ module EE ...@@ -71,10 +70,6 @@ module EE
private private
def authorize_read_cluster_health!
access_denied! unless can?(current_user, :read_cluster_health, cluster)
end
def expire_etag_cache def expire_etag_cache
return if request.format.json? || !clusterable.environments_cluster_path(cluster) return if request.format.json? || !clusterable.environments_cluster_path(cluster)
......
...@@ -8,9 +8,5 @@ module EE ...@@ -8,9 +8,5 @@ module EE
def has_multiple_clusters? def has_multiple_clusters?
clusterable.feature_available?(:multiple_clusters) clusterable.feature_available?(:multiple_clusters)
end end
def show_cluster_health_graphs?
clusterable.feature_available?(:cluster_health)
end
end end
end end
...@@ -108,7 +108,6 @@ class License < ApplicationRecord ...@@ -108,7 +108,6 @@ class License < ApplicationRecord
EEP_FEATURES.freeze EEP_FEATURES.freeze
EEU_FEATURES = EEP_FEATURES + %i[ EEU_FEATURES = EEP_FEATURES + %i[
cluster_health
compliance_framework compliance_framework
container_scanning container_scanning
credentials_inventory credentials_inventory
......
...@@ -11,15 +11,8 @@ module EE ...@@ -11,15 +11,8 @@ module EE
License.feature_available?(:cluster_deployments) License.feature_available?(:cluster_deployments)
end end
with_scope :global
condition(:cluster_health_available) do
License.feature_available?(:cluster_health)
end
rule { can?(:read_cluster) & cluster_deployments_available } rule { can?(:read_cluster) & cluster_deployments_available }
.enable :read_cluster_environments .enable :read_cluster_environments
rule { can?(:read_cluster) & cluster_health_available }.enable :read_cluster_health
end end
end end
end end
......
...@@ -65,11 +65,6 @@ module EE ...@@ -65,11 +65,6 @@ module EE
@subject.feature_available?(:group_timelogs) @subject.feature_available?(:group_timelogs)
end end
with_scope :global
condition(:cluster_health_available) do
License.feature_available?(:cluster_health)
end
with_scope :global with_scope :global
condition(:commit_committer_check_disabled_globally) do condition(:commit_committer_check_disabled_globally) do
!PushRule.global&.commit_committer_check !PushRule.global&.commit_committer_check
...@@ -223,8 +218,6 @@ module EE ...@@ -223,8 +218,6 @@ module EE
rule { ~group_timelogs_available }.prevent :read_group_timelogs rule { ~group_timelogs_available }.prevent :read_group_timelogs
rule { can?(:read_cluster) & cluster_health_available }.enable :read_cluster_health
rule { ~(admin | allow_to_manage_default_branch_protection) }.policy do rule { ~(admin | allow_to_manage_default_branch_protection) }.policy do
prevent :update_default_branch_protection prevent :update_default_branch_protection
end end
......
...@@ -102,11 +102,6 @@ module EE ...@@ -102,11 +102,6 @@ module EE
end end
end end
with_scope :global
condition(:cluster_health_available) do
License.feature_available?(:cluster_health)
end
with_scope :subject with_scope :subject
condition(:group_push_rules_enabled) do condition(:group_push_rules_enabled) do
@subject.group && ::Feature.enabled?(:group_push_rules, @subject.group.root_ancestor) @subject.group && ::Feature.enabled?(:group_push_rules, @subject.group.root_ancestor)
...@@ -407,8 +402,6 @@ module EE ...@@ -407,8 +402,6 @@ module EE
prevent :modify_merge_request_committer_setting prevent :modify_merge_request_committer_setting
end end
rule { can?(:read_cluster) & cluster_health_available }.enable :read_cluster_health
rule { can?(:read_merge_request) & code_review_analytics_enabled }.enable :read_code_review_analytics rule { can?(:read_merge_request) & code_review_analytics_enabled }.enable :read_code_review_analytics
rule { can?(:read_project) & requirements_available }.enable :read_requirement rule { can?(:read_project) & requirements_available }.enable :read_requirement
......
---
title: Make cluster health dashboard available to all self-hosted paid tiers users
merge_request: 35333
author:
type: changed
...@@ -12,7 +12,7 @@ module EE ...@@ -12,7 +12,7 @@ module EE
def permissions_by_route def permissions_by_route
super.concat([ super.concat([
ROUTE.new(::Gitlab::Metrics::Dashboard::Url.alert_regex, :read_prometheus_alerts), ROUTE.new(::Gitlab::Metrics::Dashboard::Url.alert_regex, :read_prometheus_alerts),
ROUTE.new(::Gitlab::Metrics::Dashboard::Url.clusters_regex, :read_cluster_health) ROUTE.new(::Gitlab::Metrics::Dashboard::Url.clusters_regex, :read_cluster)
]) ])
end end
end end
......
...@@ -57,25 +57,7 @@ RSpec.describe Admin::ClustersController do ...@@ -57,25 +57,7 @@ RSpec.describe Admin::ClustersController do
end end
describe 'GET #metrics_dashboard' do describe 'GET #metrics_dashboard' do
context 'with license' do it_behaves_like 'the default dashboard'
before do
stub_licensed_features(cluster_health: true)
end
it_behaves_like 'the default dashboard'
end
context 'without license' do
before do
stub_licensed_features(cluster_health: false)
end
it 'has status not found' do
get :metrics_dashboard, params: metrics_params, format: :json
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
end end
......
...@@ -87,25 +87,7 @@ RSpec.describe Groups::ClustersController do ...@@ -87,25 +87,7 @@ RSpec.describe Groups::ClustersController do
sign_in(user) sign_in(user)
end end
context 'with license' do it_behaves_like 'the default dashboard'
before do
stub_licensed_features(cluster_health: true)
end
it_behaves_like 'the default dashboard'
end
context 'without license' do
before do
stub_licensed_features(cluster_health: false)
end
it 'has status not found' do
get :metrics_dashboard, params: metrics_params, format: :json
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
end end
......
...@@ -84,25 +84,7 @@ RSpec.describe Projects::ClustersController do ...@@ -84,25 +84,7 @@ RSpec.describe Projects::ClustersController do
sign_in(user) sign_in(user)
end end
context 'with license' do it_behaves_like 'the default dashboard'
before do
stub_licensed_features(cluster_health: true)
end
it_behaves_like 'the default dashboard'
end
context 'without license' do
before do
stub_licensed_features(cluster_health: false)
end
it 'has status not found' do
get :metrics_dashboard, params: metrics_params, format: :json
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
end end
......
...@@ -12,8 +12,6 @@ RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory ...@@ -12,8 +12,6 @@ RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory
let_it_be(:cluster_path) { project_cluster_path(clusterable, cluster) } let_it_be(:cluster_path) { project_cluster_path(clusterable, cluster) }
before do before do
stub_licensed_features(cluster_health: true)
clusterable.add_maintainer(current_user) clusterable.add_maintainer(current_user)
sign_in(current_user) sign_in(current_user)
......
...@@ -19,7 +19,7 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st ...@@ -19,7 +19,7 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st
.to receive(:url) .to receive(:url)
.and_return(urls.root_url.chomp('/')) .and_return(urls.root_url.chomp('/'))
stub_licensed_features(prometheus_alerts: true, cluster_health: true) stub_licensed_features(prometheus_alerts: true)
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
...@@ -106,20 +106,6 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st ...@@ -106,20 +106,6 @@ RSpec.describe 'Metrics rendering', :js, :kubeclient, :use_clean_rails_memory_st
.with(cluster, 'GET', 'query_range', hash_including('start', 'end', 'step')) .with(cluster, 'GET', 'query_range', hash_including('start', 'end', 'step'))
.at_least(:once) .at_least(:once)
end end
# Delete when moving to CE
context 'unlicensed' do
before do
stub_licensed_features(cluster_health: false)
end
it 'shows no embedded metrics' do
visit project_issue_path(project, issue)
expect(page).to have_no_css('div.metrics-embed')
expect(page).to have_no_css('div.js-render-metrics')
end
end
end end
def import_common_metrics def import_common_metrics
......
...@@ -42,20 +42,4 @@ RSpec.describe ClustersHelper do ...@@ -42,20 +42,4 @@ RSpec.describe ClustersHelper do
it_behaves_like 'feature availablilty', :multiple_clusters it_behaves_like 'feature availablilty', :multiple_clusters
end end
end end
describe '#show_cluster_health_graphs?' do
subject { helper.show_cluster_health_graphs? }
context 'project level' do
let(:clusterable) { instance_double(Project) }
it_behaves_like 'feature availablilty', :cluster_health
end
context 'group level' do
let(:clusterable) { instance_double(Group) }
it_behaves_like 'feature availablilty', :cluster_health
end
end
end end
...@@ -34,21 +34,13 @@ RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do ...@@ -34,21 +34,13 @@ RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do
let(:query_params) { { group: 'Cluster Health', title: 'CPU Usage', y_label: 'CPU (cores)' } } let(:query_params) { { group: 'Cluster Health', title: 'CPU Usage', y_label: 'CPU (cores)' } }
let(:url) { urls.metrics_namespace_project_cluster_url(*params, **query_params) } let(:url) { urls.metrics_namespace_project_cluster_url(*params, **query_params) }
context 'with cluster health license' do context 'with user who can read cluster' do
before do
stub_licensed_features(cluster_health: true)
end
it_behaves_like 'redacts the embed placeholder' it_behaves_like 'redacts the embed placeholder'
it_behaves_like 'retains the embed placeholder when applicable' it_behaves_like 'retains the embed placeholder when applicable'
end end
context 'without cluster health license' do context 'without user who can read cluster' do
let(:doc) { filter(input, current_user: project.owner) } let(:doc) { filter(input, current_user: create(:user)) }
before do
stub_licensed_features(cluster_health: false)
end
it 'redacts the embed placeholder' do it 'redacts the embed placeholder' do
expect(doc.to_s).to be_empty expect(doc.to_s).to be_empty
......
...@@ -23,42 +23,4 @@ RSpec.describe Clusters::InstancePolicy, :enable_admin_mode do ...@@ -23,42 +23,4 @@ RSpec.describe Clusters::InstancePolicy, :enable_admin_mode do
it { is_expected.not_to be_allowed(:read_cluster_environments) } it { is_expected.not_to be_allowed(:read_cluster_environments) }
end end
context 'when cluster is readable' do
context 'and cluster health is available' do
before do
stub_licensed_features(cluster_health: true)
end
it { is_expected.to be_allowed(:read_cluster_health) }
end
context 'and cluster health is unavailable' do
before do
stub_licensed_features(cluster_health: false)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
end
context 'when cluster is not readable to user' do
let(:user) { build(:user) }
context 'when cluster health is available' do
before do
stub_licensed_features(cluster_health: true)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
context 'when cluster health is unavailable' do
before do
stub_licensed_features(cluster_health: false)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
end
end end
...@@ -897,48 +897,6 @@ RSpec.describe GroupPolicy do ...@@ -897,48 +897,6 @@ RSpec.describe GroupPolicy do
end end
end end
describe 'read_cluster_health' do
let(:current_user) { owner }
context 'when cluster is readable' do
context 'and cluster health is available' do
before do
stub_licensed_features(cluster_health: true)
end
it { is_expected.to be_allowed(:read_cluster_health) }
end
context 'and cluster health is unavailable' do
before do
stub_licensed_features(cluster_health: false)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
end
context 'when cluster is not readable to user' do
let(:current_user) { build(:user) }
context 'when cluster health is available' do
before do
stub_licensed_features(cluster_health: true)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
context 'when cluster health is unavailable' do
before do
stub_licensed_features(cluster_health: false)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
end
end
describe 'update_default_branch_protection' do describe 'update_default_branch_protection' do
context 'for an admin' do context 'for an admin' do
let(:current_user) { admin } let(:current_user) { admin }
......
...@@ -1252,48 +1252,6 @@ RSpec.describe ProjectPolicy do ...@@ -1252,48 +1252,6 @@ RSpec.describe ProjectPolicy do
end end
end end
describe 'read_cluster_health' do
let(:current_user) { owner }
context 'when cluster is readable' do
context 'and cluster health is available' do
before do
stub_licensed_features(cluster_health: true)
end
it { is_expected.to be_allowed(:read_cluster_health) }
end
context 'and cluster health is unavailable' do
before do
stub_licensed_features(cluster_health: false)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
end
context 'when cluster is not readable to user' do
let(:current_user) { build(:user) }
context 'when cluster health is available' do
before do
stub_licensed_features(cluster_health: true)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
context 'when cluster health is unavailable' do
before do
stub_licensed_features(cluster_health: false)
end
it { is_expected.to be_disallowed(:read_cluster_health) }
end
end
end
shared_examples 'merge request rules' do shared_examples 'merge request rules' do
let(:project) { create(:project, namespace: owner.namespace) } let(:project) { create(:project, namespace: owner.namespace) }
......
...@@ -17,30 +17,11 @@ RSpec.describe 'clusters/clusters/show' do ...@@ -17,30 +17,11 @@ RSpec.describe 'clusters/clusters/show' do
allow(view).to receive(:clusterable).and_return(clusterable_presenter) allow(view).to receive(:clusterable).and_return(clusterable_presenter)
end end
context 'with feature cluster_health available' do it 'displays the Cluster health section' do
before do render
stub_licensed_features(cluster_health: true)
end
it 'displays the Cluster health section' do expect(rendered).to have_selector('#cluster-health-tab')
render expect(rendered).to have_content('Health')
expect(rendered).to have_selector('#cluster-health-tab')
expect(rendered).to have_content('Health')
end
end
context 'without feature cluster_health available' do
before do
stub_licensed_features(cluster_health: false)
end
it 'does not show the Cluster health section' do
render
expect(rendered).not_to have_selector('#cluster-health')
expect(rendered).not_to have_content('Cluster health')
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