Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml42
-rw-r--r--.rubocop.yml2
-rw-r--r--.rubocop_todo/rspec/factory_bot/avoid_create.yml47
-rw-r--r--app/controllers/clusters/base_controller.rb2
-rw-r--r--app/controllers/clusters/clusters_controller.rb1
-rw-r--r--app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb53
-rw-r--r--app/controllers/projects/environments/prometheus_api_controller.rb20
-rw-r--r--app/controllers/projects/prometheus/alerts_controller.rb21
-rw-r--r--app/models/organizations/organization.rb1
-rw-r--r--app/models/user.rb21
-rw-r--r--app/validators/abstract_path_validator.rb17
-rw-r--r--app/validators/organizations/path_validator.rb15
-rw-r--r--db/docs/subscription_add_on_purchases.yml10
-rw-r--r--db/docs/subscription_add_ons.yml10
-rw-r--r--db/migrate/20230531134916_create_subscription_add_ons.rb12
-rw-r--r--db/migrate/20230531135001_create_subscription_add_on_purchases.rb18
-rw-r--r--db/migrate/20230531142032_add_foreign_key_subscription_add_on_id_on_subscription_add_on_purchases.rb18
-rw-r--r--db/migrate/20230531142053_add_foreign_key_namespace_id_on_subscription_add_on_purchases.rb15
-rw-r--r--db/schema_migrations/202305311349161
-rw-r--r--db/schema_migrations/202305311350011
-rw-r--r--db/schema_migrations/202305311420321
-rw-r--r--db/schema_migrations/202305311420531
-rw-r--r--db/structure.sql61
-rw-r--r--doc/administration/geo/replication/container_registry.md21
-rw-r--r--doc/administration/geo/replication/troubleshooting.md4
-rw-r--r--doc/administration/geo/setup/index.md8
-rw-r--r--doc/development/gitlab_shell/index.md2
-rw-r--r--doc/raketasks/restore_gitlab.md2
-rw-r--r--doc/tutorials/left_sidebar/img/sidebar_bottom_v16_1.pngbin22244 -> 7229 bytes
-rw-r--r--doc/tutorials/left_sidebar/img/sidebar_middle_v16_1.pngbin21452 -> 7789 bytes
-rw-r--r--doc/user/workspace/index.md2
-rw-r--r--generator_templates/gitlab_internal_events/event_definition.yml16
-rw-r--r--generator_templates/gitlab_internal_events/metric_definition.yml5
-rw-r--r--lib/api/ml/mlflow/entrypoint.rb2
-rw-r--r--lib/generators/gitlab/analytics/internal_events_generator.rb112
-rw-r--r--lib/gitlab/path_regex.rb16
-rw-r--r--lib/google_cloud/authentication.rb20
-rw-r--r--lib/google_cloud/logging_service/logger.rb41
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb68
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb75
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb86
-rw-r--r--spec/controllers/projects/environments/prometheus_api_controller_spec.rb96
-rw-r--r--spec/controllers/projects/prometheus/alerts_controller_spec.rb71
-rw-r--r--spec/factories/merge_request_diffs.rb2
-rw-r--r--spec/factories/merge_requests.rb8
-rw-r--r--spec/features/clusters/cluster_health_dashboard_spec.rb126
-rw-r--r--spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb102
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb17
-rw-r--r--spec/lib/google_cloud/authentication_spec.rb53
-rw-r--r--spec/lib/google_cloud/logging_service/logger_spec.rb61
-rw-r--r--spec/models/merge_request_diff_spec.rb6
-rw-r--r--spec/models/organizations/organization_spec.rb31
-rw-r--r--spec/models/preloaders/merge_request_diff_preloader_spec.rb2
-rw-r--r--spec/models/user_spec.rb99
-rw-r--r--spec/requests/api/ml/mlflow/experiments_spec.rb5
-rw-r--r--spec/requests/api/ml/mlflow/runs_spec.rb5
-rw-r--r--spec/support/helpers/models/merge_request_without_merge_request_diff.rb7
-rw-r--r--spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb163
-rw-r--r--spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb9
-rw-r--r--spec/validators/organizations/path_validator_spec.rb40
61 files changed, 856 insertions, 929 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index f57662acac9..b4f268a6563 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -161,16 +161,14 @@
- "scripts/rspec_helpers.sh"
.ci-build-images-patterns: &ci-build-images-patterns
- - ".gitlab-ci.yml"
- ".gitlab/ci/build-images.gitlab-ci.yml"
.ci-review-patterns: &ci-review-patterns
- - ".gitlab-ci.yml"
- ".gitlab/ci/frontend.gitlab-ci.yml"
- ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/review.gitlab-ci.yml"
- ".gitlab/ci/cng/**/*"
- - ".gitlab/ci/review-apps/**/*"
+ - ".gitlab/ci/review-apps/*.yml"
- "scripts/review_apps/**/*"
- "scripts/trigger-build.rb"
- "{,ee/,jh/}{bin,config}/**/*.rb"
@@ -179,13 +177,12 @@
- "lib/gitlab/ci/templates/**/*.gitlab-ci.yml"
.ci-qa-patterns: &ci-qa-patterns
- - ".gitlab-ci.yml"
- ".gitlab/ci/frontend.gitlab-ci.yml"
- ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/qa.gitlab-ci.yml"
- ".gitlab/ci/package-and-test/*.yml"
- - ".gitlab/ci/review-apps/qa.gitlab-ci.yml"
- - ".gitlab/ci/review-apps/rules.gitlab-ci.yml"
+ - ".gitlab/ci/package-and-test-nightly/*.yml"
+ - ".gitlab/ci/qa-common/*.yml"
.gitaly-patterns: &gitaly-patterns
- "GITALY_SERVER_VERSION"
@@ -323,8 +320,9 @@
- "{,ee/,jh/}{bin,config,db,generator_templates,lib}/**/*"
- "{,ee/,jh/}spec/**/*"
# CI changes
- - ".gitlab-ci.yml"
- - ".gitlab/ci/**/*"
+ - ".gitlab/ci/database.gitlab-ci.yml"
+ - ".gitlab/ci/rails.gitlab-ci.yml"
+ - ".gitlab/ci/rails/*.yml"
- "*_VERSION"
- "scripts/rspec_helpers.sh"
# Mapped patterns (see tests.yml)
@@ -377,8 +375,7 @@
- "GITALY_SERVER_VERSION"
- "lib/gitlab/setup_helper.rb"
# CI changes
- - ".gitlab-ci.yml"
- - ".gitlab/ci/**/*"
+ - ".gitlab/ci/database.gitlab-ci.yml"
# DB backup patterns
.db-backup-patterns: &db-backup-patterns
@@ -402,7 +399,7 @@
- ".dockerignore"
- "{,jh/}qa/**/*"
-# Code patterns + .ci-patterns
+# Code patterns
.code-patterns: &code-patterns
- "{package.json,yarn.lock}"
- ".browserslistrc"
@@ -412,7 +409,6 @@
- "Dockerfile.assets"
- "vendor/assets/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- - ".gitlab-ci.yml"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "Rakefile"
@@ -420,9 +416,6 @@
- "config.ru"
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
- # CI changes
- - ".gitlab-ci.yml"
- - ".gitlab/ci/**/*"
# Mapped patterns (see tests.yml)
- "data/whats_new/*.yml"
- "doc/index.md"
@@ -444,9 +437,6 @@
- "config.ru"
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,storybook,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
- # CI changes
- - ".gitlab-ci.yml"
- - ".gitlab/ci/**/*"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
@@ -475,9 +465,6 @@
- "config.ru"
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
- # CI changes
- - ".gitlab-ci.yml"
- - ".gitlab/ci/**/*"
# QA changes
- ".dockerignore"
- "{,jh/}qa/**/*"
@@ -495,7 +482,6 @@
- "Dockerfile.assets"
- "vendor/assets/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- - ".gitlab-ci.yml"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "Rakefile"
@@ -503,9 +489,6 @@
- "config.ru"
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,storybook,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
- # CI changes
- - ".gitlab-ci.yml"
- - ".gitlab/ci/**/*"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
@@ -533,7 +516,6 @@
- "Dockerfile.assets"
- "vendor/assets/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- - ".gitlab-ci.yml"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "Rakefile"
@@ -541,9 +523,6 @@
- "config.ru"
- "{,ee/,jh/}{app,bin,config,db,generator_templates,haml_lint,lib,locale,public,scripts,storybook,symbol,vendor}/**/*"
- "doc/api/graphql/reference/*" # Files in this folder are auto-generated
- # CI changes
- - ".gitlab-ci.yml"
- - ".gitlab/ci/**/*"
# Backstage changes
- "Dangerfile"
- "danger/**/*"
@@ -599,7 +578,6 @@
- "{,ee/,jh/}config/feature_flags/{development,ops}/*.yml"
.glfm-patterns: &glfm-patterns
- - ".gitlab/ci/rules.gitlab-ci.yml"
- "glfm_specification/**/*"
- "scripts/glfm/**/*"
- "scripts/lib/glfm/**/*"
@@ -1247,7 +1225,7 @@
- <<: *if-merge-request
changes: *frontend-dependency-patterns
- <<: *if-merge-request
- changes: [".gitlab/ci/rules.gitlab-ci.yml", ".gitlab/ci/frontend.gitlab-ci.yml"]
+ changes: [".gitlab/ci/frontend.gitlab-ci.yml"]
- <<: *if-automated-merge-request
changes: *code-backstage-patterns
- <<: *if-security-merge-request
@@ -1270,7 +1248,7 @@
changes: *frontend-dependency-patterns
when: never
- <<: *if-merge-request
- changes: [".gitlab/ci/rules.gitlab-ci.yml", ".gitlab/ci/frontend.gitlab-ci.yml"]
+ changes: [".gitlab/ci/frontend.gitlab-ci.yml"]
when: never
- <<: *if-merge-request
changes: *code-backstage-patterns
diff --git a/.rubocop.yml b/.rubocop.yml
index 1630d59c17a..95240342be3 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -557,6 +557,7 @@ RSpec/FactoryBot/AvoidCreate:
- 'spec/components/**/*.rb'
- 'spec/mailers/**/*.rb'
- 'spec/routes/directs/*.rb'
+ - 'spec/lib/sidebars/**/*.rb'
- 'ee/spec/presenters/**/*.rb'
- 'ee/spec/serializers/**/*.rb'
- 'ee/spec/helpers/**/*.rb'
@@ -564,6 +565,7 @@ RSpec/FactoryBot/AvoidCreate:
- 'ee/spec/components/**/*.rb'
- 'ee/spec/mailers/**/*.rb'
- 'ee/spec/routes/directs/*.rb'
+ - 'ee/spec/lib/sidebars/**/*.rb'
RSpec/FactoryBot/StrategyInCallback:
Enabled: true
diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml
index d98ad31f754..060b17970ac 100644
--- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml
+++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml
@@ -6,7 +6,6 @@ RSpec/FactoryBot/AvoidCreate:
- 'ee/spec/components/namespaces/free_user_cap/notification_alert_component_spec.rb'
- 'ee/spec/components/namespaces/free_user_cap/usage_quota_alert_component_spec.rb'
- 'ee/spec/components/namespaces/free_user_cap/usage_quota_trial_alert_component_spec.rb'
- - 'ee/spec/components/namespaces/storage/limit_alert_component_spec.rb'
- 'ee/spec/components/namespaces/storage/pre_enforcement_alert_component_spec.rb'
- 'ee/spec/components/namespaces/storage/project_pre_enforcement_alert_component_spec.rb'
- 'ee/spec/components/namespaces/storage/subgroup_pre_enforcement_alert_component_spec.rb'
@@ -49,7 +48,6 @@ RSpec/FactoryBot/AvoidCreate:
- 'ee/spec/helpers/ee/security_orchestration_helper_spec.rb'
- 'ee/spec/helpers/ee/subscribable_banner_helper_spec.rb'
- 'ee/spec/helpers/ee/todos_helper_spec.rb'
- - 'ee/spec/helpers/trials_helper_spec.rb'
- 'ee/spec/helpers/ee/users/callouts_helper_spec.rb'
- 'ee/spec/helpers/ee/welcome_helper_spec.rb'
- 'ee/spec/helpers/ee/wiki_helper_spec.rb'
@@ -79,9 +77,19 @@ RSpec/FactoryBot/AvoidCreate:
- 'ee/spec/helpers/subscriptions_helper_spec.rb'
- 'ee/spec/helpers/timeboxes_helper_spec.rb'
- 'ee/spec/helpers/trial_status_widget_helper_spec.rb'
+ - 'ee/spec/helpers/trials_helper_spec.rb'
- 'ee/spec/helpers/users/identity_verification_helper_spec.rb'
- 'ee/spec/helpers/users_helper_spec.rb'
- 'ee/spec/helpers/vulnerabilities_helper_spec.rb'
+ - 'ee/spec/lib/sidebars/groups/menus/analytics_menu_spec.rb'
+ - 'ee/spec/lib/sidebars/groups/menus/epics_menu_spec.rb'
+ - 'ee/spec/lib/sidebars/groups/menus/security_compliance_menu_spec.rb'
+ - 'ee/spec/lib/sidebars/groups/menus/wiki_menu_spec.rb'
+ - 'ee/spec/lib/sidebars/groups/super_sidebar_panel_spec.rb'
+ - 'ee/spec/lib/sidebars/projects/super_sidebar_panel_spec.rb'
+ - 'ee/spec/lib/sidebars/search/panel_spec.rb'
+ - 'ee/spec/lib/sidebars/user_profile/panel_spec.rb'
+ - 'ee/spec/lib/sidebars/user_settings/panel_spec.rb'
- 'ee/spec/mailers/ci_minutes_usage_mailer_spec.rb'
- 'ee/spec/mailers/credentials_inventory_mailer_spec.rb'
- 'ee/spec/mailers/devise_mailer_spec.rb'
@@ -330,6 +338,40 @@ RSpec/FactoryBot/AvoidCreate:
- 'spec/helpers/whats_new_helper_spec.rb'
- 'spec/helpers/wiki_helper_spec.rb'
- 'spec/helpers/wiki_page_version_helper_spec.rb'
+ - 'spec/lib/sidebars/admin/menus/abuse_reports_menu_spec.rb'
+ - 'spec/lib/sidebars/admin/menus/monitoring_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/menus/ci_cd_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/menus/group_information_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/menus/issues_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/menus/kubernetes_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/menus/merge_requests_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/menus/observability_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/menus/settings_menu_spec.rb'
+ - 'spec/lib/sidebars/groups/super_sidebar_panel_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/analytics_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/confluence_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/deployments_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/hidden_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/issues_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/merge_requests_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/monitor_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/project_information_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/repository_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/security_compliance_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/settings_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/menus/shimo_menu_spec.rb'
+ - 'spec/lib/sidebars/projects/panel_spec.rb'
+ - 'spec/lib/sidebars/projects/super_sidebar_panel_spec.rb'
+ - 'spec/lib/sidebars/search/panel_spec.rb'
+ - 'spec/lib/sidebars/user_profile/panel_spec.rb'
+ - 'spec/lib/sidebars/user_settings/panel_spec.rb'
+ - 'spec/lib/sidebars/your_work/menus/issues_menu_spec.rb'
+ - 'spec/lib/sidebars/your_work/menus/merge_requests_menu_spec.rb'
+ - 'spec/lib/sidebars/your_work/menus/todos_menu_spec.rb'
+ - 'spec/lib/sidebars/your_work/panel_spec.rb'
- 'spec/mailers/abuse_report_mailer_spec.rb'
- 'spec/mailers/devise_mailer_spec.rb'
- 'spec/mailers/emails/auto_devops_spec.rb'
@@ -376,7 +418,6 @@ RSpec/FactoryBot/AvoidCreate:
- 'spec/presenters/packages/conan/package_presenter_spec.rb'
- 'spec/presenters/packages/detail/package_presenter_spec.rb'
- 'spec/presenters/packages/helm/index_presenter_spec.rb'
- - 'spec/presenters/packages/npm/package_presenter_spec.rb'
- 'spec/presenters/packages/nuget/package_metadata_presenter_spec.rb'
- 'spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb'
- 'spec/presenters/packages/nuget/packages_versions_presenter_spec.rb'
diff --git a/app/controllers/clusters/base_controller.rb b/app/controllers/clusters/base_controller.rb
index dd5be596ad1..e7b76b87ad9 100644
--- a/app/controllers/clusters/base_controller.rb
+++ b/app/controllers/clusters/base_controller.rb
@@ -10,7 +10,7 @@ class Clusters::BaseController < ApplicationController
feature_category :deployment_management
urgency :low, [
- :index, :show, :environments, :cluster_status, :prometheus_proxy,
+ :index, :show, :environments, :cluster_status,
:destroy, :new_cluster_docs, :connect, :new, :create_user
]
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 873aa5e18dc..2f6331a6822 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -2,7 +2,6 @@
class Clusters::ClustersController < Clusters::BaseController
include RoutableActions
- include Metrics::Dashboard::PrometheusApiProxy
include MetricsDashboard
before_action :cluster, only: [:cluster_status, :show, :update, :destroy, :clear_cache]
diff --git a/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb b/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb
deleted file mode 100644
index 6a24a7308b7..00000000000
--- a/app/controllers/concerns/metrics/dashboard/prometheus_api_proxy.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-module Metrics::Dashboard::PrometheusApiProxy
- extend ActiveSupport::Concern
- include RenderServiceResults
-
- included do
- before_action :authorize_read_prometheus!, only: [:prometheus_proxy]
- end
-
- def prometheus_proxy
- return not_found if Feature.enabled?(:remove_monitor_metrics)
-
- variable_substitution_result =
- proxy_variable_substitution_service.new(proxyable, permit_params).execute
-
- return error_response(variable_substitution_result) if variable_substitution_result[:status] == :error
-
- prometheus_result = ::Prometheus::ProxyService.new(
- proxyable,
- proxy_method,
- proxy_path,
- variable_substitution_result[:params]
- ).execute
-
- return continue_polling_response if prometheus_result.nil?
- return error_response(prometheus_result) if prometheus_result[:status] == :error
-
- success_response(prometheus_result)
- end
-
- private
-
- def proxyable
- raise NotImplementedError, "#{self.class} must implement method: #{__callee__}"
- end
-
- def proxy_variable_substitution_service
- raise NotImplementedError, "#{self.class} must implement method: #{__callee__}"
- end
-
- def permit_params
- params.permit!
- end
-
- def proxy_method
- request.method
- end
-
- def proxy_path
- params[:proxy_path]
- end
-end
diff --git a/app/controllers/projects/environments/prometheus_api_controller.rb b/app/controllers/projects/environments/prometheus_api_controller.rb
deleted file mode 100644
index cbb16d596a0..00000000000
--- a/app/controllers/projects/environments/prometheus_api_controller.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-class Projects::Environments::PrometheusApiController < Projects::ApplicationController
- include Metrics::Dashboard::PrometheusApiProxy
-
- before_action :proxyable
-
- feature_category :metrics
- urgency :low
-
- private
-
- def proxyable
- @proxyable ||= project.environments.find(params[:id])
- end
-
- def proxy_variable_substitution_service
- ::Prometheus::ProxyVariableSubstitutionService
- end
-end
diff --git a/app/controllers/projects/prometheus/alerts_controller.rb b/app/controllers/projects/prometheus/alerts_controller.rb
index 27ac64e5758..80a8dbf4729 100644
--- a/app/controllers/projects/prometheus/alerts_controller.rb
+++ b/app/controllers/projects/prometheus/alerts_controller.rb
@@ -3,8 +3,6 @@
module Projects
module Prometheus
class AlertsController < Projects::ApplicationController
- include MetricsDashboard
-
respond_to :json
protect_from_forgery except: [:notify]
@@ -14,7 +12,6 @@ module Projects
prepend_before_action :repository, :project_without_auth, only: [:notify]
before_action :authorize_read_prometheus_alerts!, except: [:notify]
- before_action :alert, only: [:metrics_dashboard]
feature_category :incident_management
urgency :low
@@ -33,17 +30,6 @@ module Projects
.new(project, params.permit!)
end
- def alert
- @alert ||= alerts_finder(metric: params[:id]).execute.first || render_404
- end
-
- def alerts_finder(opts = {})
- Projects::Prometheus::AlertsFinder.new({
- project: project,
- environment: params[:environment_id]
- }.reverse_merge(opts))
- end
-
def extract_alert_manager_token(request)
Doorkeeper::OAuth::Token.from_bearer_authorization(request)
end
@@ -52,13 +38,6 @@ module Projects
@project ||= Project
.find_by_full_path("#{params[:namespace_id]}/#{params[:project_id]}")
end
-
- def metrics_dashboard_params
- {
- embedded: true,
- prometheus_alert_id: alert.id
- }
- end
end
end
end
diff --git a/app/models/organizations/organization.rb b/app/models/organizations/organization.rb
index 5eaef1419c1..764f752b557 100644
--- a/app/models/organizations/organization.rb
+++ b/app/models/organizations/organization.rb
@@ -17,6 +17,7 @@ module Organizations
validates :path,
presence: true,
+ 'organizations/path': true,
length: { minimum: 2, maximum: 255 }
def default?
diff --git a/app/models/user.rb b/app/models/user.rb
index 03d2b5d19ba..3016532fc1c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1673,13 +1673,14 @@ class User < ApplicationRecord
self.note = "#{new_note}\n#{note}".strip
block_or_ban
-
DeleteUserWorker.perform_in(DELETION_DELAY_IN_DAYS, deleted_by.id, id, params.to_h)
- else
- block if params[:hard_delete]
- DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
+ return
end
+
+ block if params[:hard_delete]
+
+ DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
end
# rubocop: disable CodeReuse/ServiceClass
@@ -2351,9 +2352,19 @@ class User < ApplicationRecord
ban
end
+ def has_possible_spam_contributions?
+ events
+ .for_action('commented')
+ .or(events.for_action('created').where(target_type: %w[Issue MergeRequest]))
+ .any?
+ end
+
def should_delay_delete?(deleted_by)
is_deleting_own_record = deleted_by.id == id
- is_deleting_own_record && ::Feature.enabled?(:delay_delete_own_user)
+
+ is_deleting_own_record &&
+ ::Feature.enabled?(:delay_delete_own_user) &&
+ has_possible_spam_contributions?
end
def pbkdf2?
diff --git a/app/validators/abstract_path_validator.rb b/app/validators/abstract_path_validator.rb
index 45ac695c5ec..ff390a624c5 100644
--- a/app/validators/abstract_path_validator.rb
+++ b/app/validators/abstract_path_validator.rb
@@ -26,11 +26,22 @@ class AbstractPathValidator < ActiveModel::EachValidator
return
end
- full_path = record.build_full_path
- return unless full_path
+ if build_full_path_to_validate_against_reserved_names?
+ path_to_validate_against_reserved_names = record.build_full_path
+ return unless path_to_validate_against_reserved_names
+ else
+ path_to_validate_against_reserved_names = value
+ end
- unless self.class.valid_path?(full_path)
+ unless self.class.valid_path?(path_to_validate_against_reserved_names)
record.errors.add(attribute, "#{value} is a reserved name")
end
end
+
+ def build_full_path_to_validate_against_reserved_names?
+ # By default, entities using the `Routable` concern can build full paths.
+ # But entities like `Organization` do not have a parent, and hence cannot build full paths,
+ # and this method can be overridden to return `false` in such cases.
+ true
+ end
end
diff --git a/app/validators/organizations/path_validator.rb b/app/validators/organizations/path_validator.rb
new file mode 100644
index 00000000000..a1c22654a32
--- /dev/null
+++ b/app/validators/organizations/path_validator.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Organizations
+ class PathValidator < ::NamespacePathValidator
+ def self.path_regex
+ Gitlab::PathRegex.organization_path_regex
+ end
+
+ def build_full_path_to_validate_against_reserved_names?
+ # full paths cannot be built for organizations because organizations do not have a parent
+ # and it does not include the `Routable` concern.
+ false
+ end
+ end
+end
diff --git a/db/docs/subscription_add_on_purchases.yml b/db/docs/subscription_add_on_purchases.yml
new file mode 100644
index 00000000000..21915cff545
--- /dev/null
+++ b/db/docs/subscription_add_on_purchases.yml
@@ -0,0 +1,10 @@
+---
+table_name: subscription_add_on_purchases
+description: Stores add-on purchase information
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122662
+milestone: '16.1'
+feature_categories:
+- subscription_management
+classes:
+- GitlabSubscriptions::AddOnPurchase
+gitlab_schema: gitlab_main
diff --git a/db/docs/subscription_add_ons.yml b/db/docs/subscription_add_ons.yml
new file mode 100644
index 00000000000..93730f80a99
--- /dev/null
+++ b/db/docs/subscription_add_ons.yml
@@ -0,0 +1,10 @@
+---
+table_name: subscription_add_ons
+description: Stores available add-ons for which purchases are stored in `subscription_add_on_purchases`.
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122662
+milestone: '16.1'
+feature_categories:
+- subscription_management
+classes:
+- GitlabSubscriptions::AddOn
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230531134916_create_subscription_add_ons.rb b/db/migrate/20230531134916_create_subscription_add_ons.rb
new file mode 100644
index 00000000000..5faee049534
--- /dev/null
+++ b/db/migrate/20230531134916_create_subscription_add_ons.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class CreateSubscriptionAddOns < Gitlab::Database::Migration[2.1]
+ def change
+ create_table :subscription_add_ons, if_not_exists: true do |t|
+ t.timestamps_with_timezone null: false
+
+ t.integer :name, limit: 2, null: false, index: { unique: true }
+ t.text :description, null: false, limit: 512
+ end
+ end
+end
diff --git a/db/migrate/20230531135001_create_subscription_add_on_purchases.rb b/db/migrate/20230531135001_create_subscription_add_on_purchases.rb
new file mode 100644
index 00000000000..6fdf1fdd495
--- /dev/null
+++ b/db/migrate/20230531135001_create_subscription_add_on_purchases.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateSubscriptionAddOnPurchases < Gitlab::Database::Migration[2.1]
+ def change
+ create_table :subscription_add_on_purchases, if_not_exists: true do |t|
+ t.timestamps_with_timezone null: false
+
+ t.bigint :subscription_add_on_id, null: false
+ t.bigint :namespace_id, null: false
+ t.integer :quantity, null: false
+ t.date :expires_on, null: false
+ t.text :purchase_xid, null: false, limit: 255
+
+ t.index :subscription_add_on_id
+ t.index :namespace_id
+ end
+ end
+end
diff --git a/db/migrate/20230531142032_add_foreign_key_subscription_add_on_id_on_subscription_add_on_purchases.rb b/db/migrate/20230531142032_add_foreign_key_subscription_add_on_id_on_subscription_add_on_purchases.rb
new file mode 100644
index 00000000000..234cd2fa3af
--- /dev/null
+++ b/db/migrate/20230531142032_add_foreign_key_subscription_add_on_id_on_subscription_add_on_purchases.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddForeignKeySubscriptionAddOnIdOnSubscriptionAddOnPurchases < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :subscription_add_on_purchases,
+ :subscription_add_ons,
+ column: :subscription_add_on_id,
+ on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :subscription_add_on_purchases, column: :subscription_add_on_id
+ end
+ end
+end
diff --git a/db/migrate/20230531142053_add_foreign_key_namespace_id_on_subscription_add_on_purchases.rb b/db/migrate/20230531142053_add_foreign_key_namespace_id_on_subscription_add_on_purchases.rb
new file mode 100644
index 00000000000..7f7083a3a9c
--- /dev/null
+++ b/db/migrate/20230531142053_add_foreign_key_namespace_id_on_subscription_add_on_purchases.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddForeignKeyNamespaceIdOnSubscriptionAddOnPurchases < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :subscription_add_on_purchases, :namespaces, column: :namespace_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :subscription_add_on_purchases, column: :namespace_id
+ end
+ end
+end
diff --git a/db/schema_migrations/20230531134916 b/db/schema_migrations/20230531134916
new file mode 100644
index 00000000000..5cf00727101
--- /dev/null
+++ b/db/schema_migrations/20230531134916
@@ -0,0 +1 @@
+fc2e3d8e6aca7b00569340b0468488a4b0545b39e67857a5b40824f6d0a62a97 \ No newline at end of file
diff --git a/db/schema_migrations/20230531135001 b/db/schema_migrations/20230531135001
new file mode 100644
index 00000000000..32850b297da
--- /dev/null
+++ b/db/schema_migrations/20230531135001
@@ -0,0 +1 @@
+1a672c9412b8ceeec35fd375bf86dde325781c9cb94340995d2cab4bb804e4bf \ No newline at end of file
diff --git a/db/schema_migrations/20230531142032 b/db/schema_migrations/20230531142032
new file mode 100644
index 00000000000..bae2817773a
--- /dev/null
+++ b/db/schema_migrations/20230531142032
@@ -0,0 +1 @@
+3e77f991a4daa9756b541255e3b8da9d8accb52a5a4b625613771982e3dff3b5 \ No newline at end of file
diff --git a/db/schema_migrations/20230531142053 b/db/schema_migrations/20230531142053
new file mode 100644
index 00000000000..55da4601012
--- /dev/null
+++ b/db/schema_migrations/20230531142053
@@ -0,0 +1 @@
+0a4b3b8848f486e34e1f0426bae4e15f67e851447fc3fe397cf2039e03b185b5 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index cc22a017044..23e2df53c91 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -23031,6 +23031,45 @@ CREATE SEQUENCE status_page_settings_project_id_seq
ALTER SEQUENCE status_page_settings_project_id_seq OWNED BY status_page_settings.project_id;
+CREATE TABLE subscription_add_on_purchases (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ subscription_add_on_id bigint NOT NULL,
+ namespace_id bigint NOT NULL,
+ quantity integer NOT NULL,
+ expires_on date NOT NULL,
+ purchase_xid text NOT NULL,
+ CONSTRAINT check_3313c4d200 CHECK ((char_length(purchase_xid) <= 255))
+);
+
+CREATE SEQUENCE subscription_add_on_purchases_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE subscription_add_on_purchases_id_seq OWNED BY subscription_add_on_purchases.id;
+
+CREATE TABLE subscription_add_ons (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ name smallint NOT NULL,
+ description text NOT NULL,
+ CONSTRAINT check_4c39d15ada CHECK ((char_length(description) <= 512))
+);
+
+CREATE SEQUENCE subscription_add_ons_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE subscription_add_ons_id_seq OWNED BY subscription_add_ons.id;
+
CREATE TABLE subscriptions (
id integer NOT NULL,
user_id integer,
@@ -25847,6 +25886,10 @@ ALTER TABLE ONLY status_page_published_incidents ALTER COLUMN id SET DEFAULT nex
ALTER TABLE ONLY status_page_settings ALTER COLUMN project_id SET DEFAULT nextval('status_page_settings_project_id_seq'::regclass);
+ALTER TABLE ONLY subscription_add_on_purchases ALTER COLUMN id SET DEFAULT nextval('subscription_add_on_purchases_id_seq'::regclass);
+
+ALTER TABLE ONLY subscription_add_ons ALTER COLUMN id SET DEFAULT nextval('subscription_add_ons_id_seq'::regclass);
+
ALTER TABLE ONLY subscriptions ALTER COLUMN id SET DEFAULT nextval('subscriptions_id_seq'::regclass);
ALTER TABLE ONLY suggestions ALTER COLUMN id SET DEFAULT nextval('suggestions_id_seq'::regclass);
@@ -28317,6 +28360,12 @@ ALTER TABLE ONLY status_page_published_incidents
ALTER TABLE ONLY status_page_settings
ADD CONSTRAINT status_page_settings_pkey PRIMARY KEY (project_id);
+ALTER TABLE ONLY subscription_add_on_purchases
+ ADD CONSTRAINT subscription_add_on_purchases_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY subscription_add_ons
+ ADD CONSTRAINT subscription_add_ons_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY subscriptions
ADD CONSTRAINT subscriptions_pkey PRIMARY KEY (id);
@@ -32871,6 +32920,12 @@ CREATE UNIQUE INDEX index_status_page_published_incidents_on_issue_id ON status_
CREATE INDEX index_status_page_settings_on_project_id ON status_page_settings USING btree (project_id);
+CREATE INDEX index_subscription_add_on_purchases_on_namespace_id ON subscription_add_on_purchases USING btree (namespace_id);
+
+CREATE INDEX index_subscription_add_on_purchases_on_subscription_add_on_id ON subscription_add_on_purchases USING btree (subscription_add_on_id);
+
+CREATE UNIQUE INDEX index_subscription_add_ons_on_name ON subscription_add_ons USING btree (name);
+
CREATE INDEX index_subscriptions_on_project_id ON subscriptions USING btree (project_id);
CREATE UNIQUE INDEX index_subscriptions_on_subscribable_and_user_id_and_project_id ON subscriptions USING btree (subscribable_id, subscribable_type, user_id, project_id);
@@ -35420,6 +35475,9 @@ ALTER TABLE ONLY abuse_reports
ALTER TABLE ONLY protected_environment_approval_rules
ADD CONSTRAINT fk_405568b491 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY subscription_add_on_purchases
+ ADD CONSTRAINT fk_410004d68b FOREIGN KEY (subscription_add_on_id) REFERENCES subscription_add_ons(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY ci_pipeline_schedule_variables
ADD CONSTRAINT fk_41c35fda51 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE CASCADE;
@@ -35768,6 +35826,9 @@ ALTER TABLE ONLY issues
ALTER TABLE ONLY ml_candidates
ADD CONSTRAINT fk_a1d5f1bc45 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE SET NULL;
+ALTER TABLE ONLY subscription_add_on_purchases
+ ADD CONSTRAINT fk_a1db288990 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE p_ci_builds
ADD CONSTRAINT fk_a2141b1522 FOREIGN KEY (auto_canceled_by_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
diff --git a/doc/administration/geo/replication/container_registry.md b/doc/administration/geo/replication/container_registry.md
index 66c67f29c1c..1c1d9074a04 100644
--- a/doc/administration/geo/replication/container_registry.md
+++ b/doc/administration/geo/replication/container_registry.md
@@ -73,12 +73,11 @@ To configure Container Registry replication:
Make sure that you have Container Registry set up and working on
the **primary** site before following the next steps.
-We need to make Container Registry send notification events to the
-**primary** site.
+To be able to replicate new container images, the Container Registry must send notification events to the
+**primary** site for every push. The token shared between the Container Registry and the web nodes on the
+**primary** is used to make communication more secure.
-For each application and Sidekiq node on the **primary** site:
-
-1. SSH into the node and login as the `root` user:
+1. SSH into your GitLab **primary** server and login as root (for GitLab HA, you only need a Registry node):
```shell
sudo -i
@@ -107,15 +106,17 @@ For each application and Sidekiq node on the **primary** site:
that starts with a letter. You can generate one with `< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32 | sed "s/^[0-9]*//"; echo`
NOTE:
- If you use an external Registry (not the one integrated with GitLab), you also have to specify
+ If you use an external Registry (not the one integrated with GitLab), you only need to specify
the notification secret (`registry['notification_secret']`) in the
`/etc/gitlab/gitlab.rb` file.
- NOTE:
- If you use GitLab HA, you also have to specify the notification secret (`registry['notification_secret']`) in
- `/etc/gitlab/gitlab.rb` file for every web node.
+1. For GitLab HA only. Edit `/etc/gitlab/gitlab.rb` on every web node:
+
+ ```ruby
+ registry['notification_secret'] = '<replace_with_a_secret_token_generated_above>'
+ ```
-1. Reconfigure each node:
+1. Reconfigure each node you just updated:
```shell
gitlab-ctl reconfigure
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index 9dcca8d0ac5..758d75ee294 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -120,7 +120,9 @@ The following environment variables are supported.
#### Sync status Rake task
Current sync information can be found manually by running this Rake task on any
-node running Rails (Puma, Sidekiq, or Geo Log Cursor) on the Geo **secondary** site:
+node running Rails (Puma, Sidekiq, or Geo Log Cursor) on the Geo **secondary** site.
+
+GitLab does **not** verify objects that are stored in Object Storage. If you are using Object Storage, you will see all of the "verified" checks showing 0 successes. This is expected and not a cause for concern.
```shell
sudo gitlab-rake geo:status
diff --git a/doc/administration/geo/setup/index.md b/doc/administration/geo/setup/index.md
index 4dbd2ef6167..94df58cc9eb 100644
--- a/doc/administration/geo/setup/index.md
+++ b/doc/administration/geo/setup/index.md
@@ -30,11 +30,13 @@ a single-node Geo site or a multi-node Geo site.
If both Geo sites are based on the [1K reference architecture](../../reference_architectures/1k_users.md):
-1. [Set up the database replication](database.md) (`primary (read-write) <-> secondary (read-only)` topology).
+1. Set up the database replication based on your choice of PostgreSQL instances (`primary (read-write) <-> secondary (read-only)` topology):
+ - [Using Omnibus PostgreSQL instances](database.md) .
+ - [Using external PostgreSQL instances](external_database.md)
1. [Configure GitLab](../replication/configuration.md) to set the **primary** and **secondary** sites.
-1. Optional: [Configure Object storage](../../object_storage.md)
+1. Recommended: [Configure unified URLs](../secondary_proxy/index.md) to use a single, unified URL for all Geo sites.
+1. Optional: [Configure Object storage replication](../../object_storage.md)
1. Optional: [Configure a secondary LDAP server](../../auth/ldap/index.md) for the **secondary** sites. See [notes on LDAP](../index.md#ldap).
-1. Optional: [Configure Geo secondary proxying](../secondary_proxy/index.md) to use a single, unified URL for all Geo sites. This step is recommended to accelerate most read requests while transparently proxying writes to the primary Geo site.
1. Optional: [Configure Container Registry for the secondary site](../replication/container_registry.md).
1. Follow the [Using a Geo Site](../replication/usage.md) guide.
diff --git a/doc/development/gitlab_shell/index.md b/doc/development/gitlab_shell/index.md
index 0663341f806..ef034761a1f 100644
--- a/doc/development/gitlab_shell/index.md
+++ b/doc/development/gitlab_shell/index.md
@@ -218,5 +218,5 @@ sequenceDiagram
## Related topics
- [LICENSE](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/LICENSE)
-- [PROCESS.md](https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/PROCESS.md)
+- [Processes](process.md)
- [Using the GitLab Shell chart](https://docs.gitlab.com/charts/charts/gitlab/gitlab-shell/)
diff --git a/doc/raketasks/restore_gitlab.md b/doc/raketasks/restore_gitlab.md
index c7abc7ac562..b6e9824f9b4 100644
--- a/doc/raketasks/restore_gitlab.md
+++ b/doc/raketasks/restore_gitlab.md
@@ -14,7 +14,7 @@ information. Be sure to read and test the complete restore process at least
once before attempting to perform it in a production environment.
You can restore a backup only to _the exact same version and type (CE/EE)_ of
-GitLab that you created it on (for example CE 9.1.0).
+GitLab that you created it on (for example CE 15.1.4).
If your backup is a different version than the current installation, you must
[downgrade](../update/package/downgrade.md) or [upgrade](../update/package/index.md#upgrade-to-a-specific-version-using-the-official-repositories) your GitLab installation
diff --git a/doc/tutorials/left_sidebar/img/sidebar_bottom_v16_1.png b/doc/tutorials/left_sidebar/img/sidebar_bottom_v16_1.png
index 75e6f496c0c..dd2c7b82d40 100644
--- a/doc/tutorials/left_sidebar/img/sidebar_bottom_v16_1.png
+++ b/doc/tutorials/left_sidebar/img/sidebar_bottom_v16_1.png
Binary files differ
diff --git a/doc/tutorials/left_sidebar/img/sidebar_middle_v16_1.png b/doc/tutorials/left_sidebar/img/sidebar_middle_v16_1.png
index 75fb1ae7ffe..67e89c2c0a4 100644
--- a/doc/tutorials/left_sidebar/img/sidebar_middle_v16_1.png
+++ b/doc/tutorials/left_sidebar/img/sidebar_middle_v16_1.png
Binary files differ
diff --git a/doc/user/workspace/index.md b/doc/user/workspace/index.md
index 038b5e44af6..95174437703 100644
--- a/doc/user/workspace/index.md
+++ b/doc/user/workspace/index.md
@@ -154,6 +154,6 @@ If you already have running workspaces, an administrator must manually delete th
## Arbitrary user IDs
You can provide your own container image, which can run as any Linux user ID. It's not possible for GitLab to predict the Linux user ID for a container image.
-GitLab uses the Linux root group ID permission to create, update, or delete files in the container. Depending on the container runtime used by the Kubernetes cluster, all containers might have a default group ID of `0`.
+GitLab uses the Linux root group ID permission to create, update, or delete files in a container. The container runtime used by the Kubernetes cluster must ensure all containers have a default Linux group ID of `0`.
If you have a container image that does not support arbitrary user IDs, you cannot create, update, or delete files in a workspace. To create a container image that supports arbitrary user IDs, see the [OpenShift documentation](https://docs.openshift.com/container-platform/4.12/openshift_images/create-images.html#use-uid_create-images).
diff --git a/generator_templates/gitlab_internal_events/event_definition.yml b/generator_templates/gitlab_internal_events/event_definition.yml
new file mode 100644
index 00000000000..26adc55726e
--- /dev/null
+++ b/generator_templates/gitlab_internal_events/event_definition.yml
@@ -0,0 +1,16 @@
+---
+description: <%= args.last %>
+category: GitlabInternalEvents
+action: <%= event %>
+label_description:
+property_description:
+value_description:
+extra_properties:
+identifiers:<%= event_identifiers %>
+product_section: <%= options.fetch(:section) %>
+product_stage: <%= options.fetch(:stage) %>
+product_group: <%= options.fetch(:group) %>
+milestone: "<%= milestone %>"
+introduced_by_url: <%= options.fetch(:mr) %>
+distributions:<%= distributions %>
+tiers:<%= tiers %>
diff --git a/generator_templates/gitlab_internal_events/metric_definition.yml b/generator_templates/gitlab_internal_events/metric_definition.yml
index 88823355acc..c0ff71a09ce 100644
--- a/generator_templates/gitlab_internal_events/metric_definition.yml
+++ b/generator_templates/gitlab_internal_events/metric_definition.yml
@@ -1,6 +1,5 @@
---
key_path: <%= args.second %>
-name: <%= args.second %>
description: <%= args.last %>
product_section: <%= options.fetch(:section) %>
product_stage: <%= options.fetch(:stage) %>
@@ -14,5 +13,5 @@ time_frame: <%= args.third %>
data_source: redis_hll
data_category: optional
instrumentation_class: <%= class_name %>
-<%= distribution %>
-<%= tier %>
+distribution: <%= distributions %>
+tier: <%= tiers %>
diff --git a/lib/api/ml/mlflow/entrypoint.rb b/lib/api/ml/mlflow/entrypoint.rb
index 880b1efeb5a..048234eccd1 100644
--- a/lib/api/ml/mlflow/entrypoint.rb
+++ b/lib/api/ml/mlflow/entrypoint.rb
@@ -26,7 +26,7 @@ module API
authenticate!
- not_found! unless Feature.enabled?(:ml_experiment_tracking, user_project)
+ not_found! unless can?(current_user, :read_model_experiments, user_project)
end
rescue_from ActiveRecord::ActiveRecordError do |e|
diff --git a/lib/generators/gitlab/analytics/internal_events_generator.rb b/lib/generators/gitlab/analytics/internal_events_generator.rb
index 775dac969f2..e945b4de3db 100644
--- a/lib/generators/gitlab/analytics/internal_events_generator.rb
+++ b/lib/generators/gitlab/analytics/internal_events_generator.rb
@@ -26,15 +26,24 @@ module Gitlab
end
end.freeze
+ NEGATIVE_ANSWERS = %w[no n].freeze
+ POSITIVE_ANSWERS = %w[yes y].freeze
TOP_LEVEL_DIR = 'config'
TOP_LEVEL_DIR_EE = 'ee'
DESCRIPTION_MIN_LENGTH = 50
KNOWN_EVENTS_PATH = 'lib/gitlab/usage_data_counters/known_events/common.yml'
KNOWN_EVENTS_PATH_EE = 'ee/lib/ee/gitlab/usage_data_counters/known_events/common.yml'
+ DESCRIPTION_INQUIRY = %(
+ Please describe in at least #{DESCRIPTION_MIN_LENGTH} characters
+ what %{entity} %{entity_type} represents,
+ consider mentioning: %{considerations}.
+ Your answer will be processed by a full-text search tool and help others find and reuse this %{entity_type}.
+ ).freeze
+
source_root File.expand_path('../../../../generator_templates/gitlab_internal_events', __dir__)
- desc 'Generates metric definitions yml files and known events entries'
+ desc 'Generates metric definitions, event definition yml files and known events entries'
class_option :skip_namespace,
hide: true
@@ -80,12 +89,20 @@ module Gitlab
def create_metric_file
validate!
+ template "event_definition.yml",
+ event_file_path(event),
+ ask_description(event, "event", "what the event is supposed to track, where, and when")
+
time_frames.each do |time_frame|
template "metric_definition.yml",
- file_path(time_frame),
+ metric_file_path(time_frame),
key_path(time_frame),
time_frame,
- ask_description(time_frame)
+ ask_description(
+ key_path(time_frame),
+ "metric",
+ "events, and event attributes in the description"
+ )
end
# ToDo: Delete during https://gitlab.com/groups/gitlab-org/-/epics/9542 cleanup
@@ -96,45 +113,68 @@ module Gitlab
def known_event_entry
<<~YML
- - name: #{options[:event]}
- aggregation: weekly
+ - name: #{event}
+ aggregation: weekly
YML
end
- def ask_description(time_frame)
+ def event_identifiers
+ return unless include_default_event_properties?
+
+ "\n- project\n- user\n- namespace"
+ end
+
+ def include_default_event_properties?
question = <<~DESC
- Please describe in at least #{DESCRIPTION_MIN_LENGTH} characters
- what #{key_path(time_frame)} metric represents,
- consider mentioning: events, and event attributes in the description.
- your answer will be processed to power a full-text search tool and help others find and reuse this metric.
+ By convention all events automatically include the following properties:
+ * environment: string,
+ * source: string (eg: ruby, javascript)
+ * user_id: number
+ * project_id: number
+ * namespace_id: number
+ * plan: string (eg: free, premium, ultimate)
+ Would you like to add default properties to the event? Y(es)/N(o)
DESC
+ answer = Gitlab::TaskHelpers.prompt(question, POSITIVE_ANSWERS + NEGATIVE_ANSWERS)
+ POSITIVE_ANSWERS.include?(answer)
+ end
+
+ def event_file_path(event)
+ path = File.join(TOP_LEVEL_DIR, 'events', "#{event}.yml")
+ path = File.join(TOP_LEVEL_DIR_EE, path) unless free?
+ path
+ end
+
+ def event
+ options[:event]
+ end
+
+ def ask_description(entity, type, considerations)
say("")
- description = ask(question)
+ desc = ask(format(DESCRIPTION_INQUIRY, entity: entity, entity_type: type, considerations: considerations))
- while description.length < DESCRIPTION_MIN_LENGTH
- error_mgs = <<~ERROR
- Provided description is to short: #{description.length} of required #{DESCRIPTION_MIN_LENGTH} characters
+ while desc.length < DESCRIPTION_MIN_LENGTH
+ error_msg = <<~ERROR
+ Provided description is too short: #{desc.length} of required #{DESCRIPTION_MIN_LENGTH} characters
ERROR
- say(set_color(error_mgs), :red)
+ say(set_color(error_msg, :red))
- description = ask("Please provide description that is #{DESCRIPTION_MIN_LENGTH} characters long.\n")
+ desc = ask("Please provide description that is #{DESCRIPTION_MIN_LENGTH} characters long.\n")
end
- description
+ desc
end
- def distribution
- content = [
- free? ? "- ce" : nil,
- "- ee"
- ].compact.join("\n")
+ def distributions
+ dist = "\n"
+ dist += "- ce\n" if free?
- "distribution:\n#{content}"
+ "#{dist}- ee"
end
- def tier
- "tier:\n- #{options[:tiers].join("\n- ")}"
+ def tiers
+ "\n- #{options[:tiers].join("\n- ")}"
end
def milestone
@@ -146,10 +186,10 @@ module Gitlab
end
def key_path(time_frame)
- "count_distinct_#{options[:unique_on]}_from_#{options[:event]}_#{time_frame}"
+ "count_distinct_#{options[:unique_on]}_from_#{event}_#{time_frame}"
end
- def file_path(time_frame)
+ def metric_file_path(time_frame)
path = File.join(TOP_LEVEL_DIR, 'metrics', TIME_FRAME_DIRS[time_frame], "#{key_path(time_frame)}.yml")
path = File.join(TOP_LEVEL_DIR_EE, path) unless free?
path
@@ -161,6 +201,7 @@ module Gitlab
def validate!
raise "Required file: #{known_events_file_name} does not exists." unless File.exist?(known_events_file_name)
+ raise "An event '#{event}' already exists" if event_exists?
validate_tiers!
@@ -197,6 +238,23 @@ module Gitlab
raise "Metric definition with key path '#{key_path(time_frame)}' already exists"
end
+ def event_exists?
+ return true if ::Gitlab::UsageDataCounters::HLLRedisCounter.known_event?(event)
+
+ existing_events_from_definitions.include?(event)
+ end
+
+ def existing_events_from_definitions
+ events_glob_path = File.join(TOP_LEVEL_DIR, 'events', "*.yml")
+ ee_events_glob_path = File.join(TOP_LEVEL_DIR_EE, events_glob_path)
+
+ [ee_events_glob_path, events_glob_path].flat_map do |glob_path|
+ Dir.glob(glob_path).map do |path|
+ YAML.safe_load(File.read(path))["action"]
+ end
+ end
+ end
+
def free?
options[:tiers].include? "free"
end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index b0804c2ff66..e112423f167 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -122,6 +122,7 @@ module Gitlab
ILLEGAL_PROJECT_PATH_WORDS = PROJECT_WILDCARD_ROUTES
ILLEGAL_GROUP_PATH_WORDS = (PROJECT_WILDCARD_ROUTES | GROUP_ROUTES).freeze
+ ILLEGAL_ORGANIZATION_PATH_WORDS = (TOP_LEVEL_ROUTES | PROJECT_WILDCARD_ROUTES | GROUP_ROUTES).freeze
# The namespace regex is used in JavaScript to validate usernames in the "Register" form. However, Javascript
# does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`.
@@ -138,6 +139,17 @@ module Gitlab
PROJECT_PATH_FORMAT_REGEX = /(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX}/.freeze
FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/){,#{Namespace::NUMBER_OF_ANCESTORS_ALLOWED}}#{NAMESPACE_FORMAT_REGEX}}.freeze
+ def organization_route_regex
+ @organization_route_regex ||= begin
+ illegal_words = Regexp.new(Regexp.union(ILLEGAL_ORGANIZATION_PATH_WORDS).source, Regexp::IGNORECASE)
+
+ single_line_regexp %r{
+ (?!(#{illegal_words})/)
+ #{NAMESPACE_FORMAT_REGEX}
+ }x
+ end
+ end
+
def root_namespace_route_regex
@root_namespace_route_regex ||= begin
illegal_words = Regexp.new(Regexp.union(TOP_LEVEL_ROUTES).source, Regexp::IGNORECASE)
@@ -195,6 +207,10 @@ module Gitlab
@full_namespace_path_regex ||= %r{\A#{full_namespace_route_regex}/\z}
end
+ def organization_path_regex
+ @organization_path_regex ||= %r{\A#{organization_route_regex}/\z}
+ end
+
def full_project_path_regex
@full_project_path_regex ||= %r{\A#{full_namespace_route_regex}/#{project_route_regex}/\z}
end
diff --git a/lib/google_cloud/authentication.rb b/lib/google_cloud/authentication.rb
new file mode 100644
index 00000000000..68dd0bdcccb
--- /dev/null
+++ b/lib/google_cloud/authentication.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module GoogleCloud
+ class Authentication
+ def initialize(scope:)
+ @scope = scope
+ end
+
+ def generate_access_token(client_email, private_key)
+ credentials = Google::Auth::ServiceAccountCredentials.make_creds(
+ json_key_io: StringIO.new({ client_email: client_email, private_key: private_key }.to_json),
+ scope: @scope
+ )
+ credentials.fetch_access_token!["access_token"]
+ rescue StandardError => e
+ ::Gitlab::ErrorTracking.track_exception(e, client_email: client_email)
+ nil
+ end
+ end
+end
diff --git a/lib/google_cloud/logging_service/logger.rb b/lib/google_cloud/logging_service/logger.rb
new file mode 100644
index 00000000000..2c6dd6ea732
--- /dev/null
+++ b/lib/google_cloud/logging_service/logger.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module GoogleCloud
+ module LoggingService
+ class Logger
+ WRITE_URL = "https://logging.googleapis.com/v2/entries:write"
+ SCOPE = "https://www.googleapis.com/auth/logging.write"
+
+ def initialize
+ @auth = GoogleCloud::Authentication.new(scope: SCOPE)
+ end
+
+ def log(client_email, private_key, payload)
+ access_token = @auth.generate_access_token(client_email, private_key)
+
+ return unless access_token
+
+ headers = build_headers(access_token)
+
+ post(WRITE_URL, body: payload, headers: headers)
+ end
+
+ private
+
+ def build_headers(access_token)
+ { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' }
+ end
+
+ def post(url, body:, headers:)
+ Gitlab::HTTP.post(
+ url,
+ body: body,
+ headers: headers
+ )
+ rescue URI::InvalidURIError => e
+ Gitlab::ErrorTracking.log_exception(e)
+ rescue *Gitlab::HTTP::HTTP_ERRORS
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8a59f043171..a118014a0d4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -29623,9 +29623,18 @@ msgstr ""
msgid "NamespaceLimits|%{linkStart}%{username}%{linkEnd} changed the limit to %{limit} at %{date}"
msgstr ""
+msgid "NamespaceLimits|Confirm deletion"
+msgstr ""
+
msgid "NamespaceLimits|Confirm limits change"
msgstr ""
+msgid "NamespaceLimits|Deletion confirmation"
+msgstr ""
+
+msgid "NamespaceLimits|Do you confirm the deletion of the selected namespace from the exclusion list?"
+msgstr ""
+
msgid "NamespaceLimits|Enter a valid number greater or equal to zero."
msgstr ""
@@ -29650,6 +29659,9 @@ msgstr ""
msgid "NamespaceLimits|Storage Phased Notification"
msgstr ""
+msgid "NamespaceLimits|There was an error deleting the namespace: \"%{errorMessage}\"."
+msgstr ""
+
msgid "NamespaceLimits|There was an error fetching the exclusion list, try refreshing the page."
msgstr ""
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index d04cd20f4e6..35bfb829bf7 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -102,39 +102,6 @@ RSpec.describe Admin::ClustersController, feature_category: :deployment_manageme
end
end
- it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
- let(:cluster) { create(:cluster, :instance, :provided_by_gcp) }
-
- let(:metrics_dashboard_req_params) do
- {
- id: cluster.id
- }
- end
- end
-
- describe 'GET #prometheus_proxy' do
- let(:user) { admin }
- let(:proxyable) do
- create(:cluster, :instance, :provided_by_gcp)
- end
-
- it_behaves_like 'metrics dashboard prometheus api proxy' do
- context 'with anonymous user' do
- let(:prometheus_body) { nil }
-
- before do
- sign_out(admin)
- end
-
- it 'returns 404' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
- end
-
describe 'POST #create_user' do
let(:params) do
{
@@ -283,41 +250,6 @@ RSpec.describe Admin::ClustersController, feature_category: :deployment_manageme
let(:subject) { get_show }
end
- describe 'functionality' do
- context 'when remove_monitor_metrics FF is disabled' do
- before do
- stub_feature_flags(remove_monitor_metrics: false)
- end
-
- render_views
-
- it 'responds successfully' do
- get_show
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(assigns(:cluster)).to eq(cluster)
- end
-
- it 'renders integration tab view' do
- get_show(tab: 'integrations')
-
- expect(response).to render_template('clusters/clusters/_integrations')
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'when remove_monitor_metrics FF is enabled' do
- render_views
-
- it 'renders details tab view' do
- get_show(tab: 'integrations')
-
- expect(response).to render_template('clusters/clusters/_details')
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
describe 'security' do
it { expect { get_show }.to be_allowed_for(:admin) }
it { expect { get_show }.to be_denied_for(:user) }
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index f36494c3d78..6c747d4f00f 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -115,46 +115,6 @@ RSpec.describe Groups::ClustersController, feature_category: :deployment_managem
end
end
- it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
- let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
-
- let(:metrics_dashboard_req_params) do
- {
- id: cluster.id,
- group_id: group.name
- }
- end
- end
-
- describe 'GET #prometheus_proxy' do
- let(:proxyable) do
- create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group])
- end
-
- it_behaves_like 'metrics dashboard prometheus api proxy' do
- let(:proxyable_params) do
- {
- id: proxyable.id.to_s,
- group_id: group.name
- }
- end
-
- context 'with anonymous user' do
- let(:prometheus_body) { nil }
-
- before do
- sign_out(user)
- end
-
- it 'returns 404' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
- end
-
describe 'POST create for existing cluster' do
let(:params) do
{
@@ -353,41 +313,6 @@ RSpec.describe Groups::ClustersController, feature_category: :deployment_managem
let(:subject) { go }
end
- describe 'functionality' do
- context 'when remove_monitor_metrics FF is disabled' do
- before do
- stub_feature_flags(remove_monitor_metrics: false)
- end
-
- render_views
-
- it 'renders view' do
- go
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(assigns(:cluster)).to eq(cluster)
- end
-
- it 'renders integration tab view', :aggregate_failures do
- go(tab: 'integrations')
-
- expect(response).to render_template('clusters/clusters/_integrations')
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'when remove_monitor_metrics FF is enabled' do
- render_views
-
- it 'renders details tab view', :aggregate_failures do
- go(tab: 'integrations')
-
- expect(response).to render_template('clusters/clusters/_details')
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
describe 'security' do
it('is allowed for admin when admin mode is enabled', :enable_admin_mode) { expect { go }.to be_allowed_for(:admin) }
it('is denied for admin when admin mode is disabled') { expect { go }.to be_denied_for(:admin) }
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index f976b5bfe67..bface886674 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -113,57 +113,6 @@ RSpec.describe Projects::ClustersController, feature_category: :deployment_manag
end
end
- describe 'GET #prometheus_proxy' do
- let(:proxyable) do
- create(:cluster, :provided_by_gcp, projects: [project])
- end
-
- it_behaves_like 'metrics dashboard prometheus api proxy' do
- let(:proxyable_params) do
- {
- id: proxyable.id.to_s,
- namespace_id: project.namespace.full_path,
- project_id: project.path
- }
- end
-
- context 'with anonymous user' do
- let(:prometheus_body) { nil }
-
- before do
- sign_out(user)
- end
-
- it 'redirects to signin page' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to redirect_to(new_user_session_path)
- end
- end
-
- context 'with a public project' do
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
- end
-
- context 'with guest user' do
- let(:prometheus_body) { nil }
-
- before do
- project.add_guest(user)
- end
-
- it 'returns 404' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
- end
- end
-
it_behaves_like 'GET #metrics_dashboard for dashboard', 'Cluster health' do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
@@ -396,41 +345,6 @@ RSpec.describe Projects::ClustersController, feature_category: :deployment_manag
let(:subject) { go }
end
- describe 'functionality' do
- context 'when remove_monitor_metrics FF is disabled' do
- before do
- stub_feature_flags(remove_monitor_metrics: false)
- end
-
- render_views
-
- it "renders view" do
- go
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(assigns(:cluster)).to eq(cluster)
- end
-
- it 'renders integration tab view' do
- go(tab: 'integrations')
-
- expect(response).to render_template('clusters/clusters/_integrations')
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'when remove_monitor_metrics FF is enabled' do
- render_views
-
- it 'renders details tab view', :aggregate_failures do
- go(tab: 'integrations')
-
- expect(response).to render_template('clusters/clusters/_details')
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
describe 'security' do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { go }.to be_allowed_for(:admin)
diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
deleted file mode 100644
index ef2d743c82f..00000000000
--- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Projects::Environments::PrometheusApiController do
- let_it_be(:user) { create(:user) }
- let_it_be_with_reload(:project) { create(:project) }
- let_it_be(:proxyable) { create(:environment, project: project) }
-
- before do
- project.add_reporter(user)
- sign_in(user)
- end
-
- describe 'GET #prometheus_proxy' do
- it_behaves_like 'metrics dashboard prometheus api proxy' do
- let(:proxyable_params) do
- {
- id: proxyable.id.to_s,
- namespace_id: project.namespace.full_path,
- project_id: project.path
- }
- end
-
- context 'with variables' do
- let(:prometheus_body) { '{"status":"success"}' }
- let(:pod_name) { "pod1" }
-
- before do
- expected_params[:query] = %{up{pod_name="#{pod_name}"}}
- expected_params[:variables] = { 'pod_name' => pod_name }
- end
-
- it 'replaces variables with values' do
- get :prometheus_proxy, params: prometheus_proxy_params.merge(
- query: 'up{pod_name="{{pod_name}}"}', variables: { 'pod_name' => pod_name }
- )
-
- expect(response).to have_gitlab_http_status(:success)
- expect(Prometheus::ProxyService).to have_received(:new)
- .with(proxyable, 'GET', 'query', expected_params)
- end
-
- context 'with invalid variables' do
- let(:params_with_invalid_variables) do
- prometheus_proxy_params.merge(
- query: 'up{pod_name="{{pod_name}}"}', variables: ['a']
- )
- end
-
- it 'returns 400' do
- get :prometheus_proxy, params: params_with_invalid_variables
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(Prometheus::ProxyService).not_to receive(:new)
- end
- end
- end
-
- context 'with anonymous user' do
- let(:prometheus_body) { nil }
-
- before do
- sign_out(user)
- end
-
- it 'redirects to signin page' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to redirect_to(new_user_session_path)
- end
- end
-
- context 'with a public project' do
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
- end
-
- context 'with guest user' do
- let(:prometheus_body) { nil }
-
- before do
- project.add_guest(user)
- end
-
- it 'returns 404' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
- end
- end
-end
diff --git a/spec/controllers/projects/prometheus/alerts_controller_spec.rb b/spec/controllers/projects/prometheus/alerts_controller_spec.rb
index 44292b9ce19..3e64631fbf1 100644
--- a/spec/controllers/projects/prometheus/alerts_controller_spec.rb
+++ b/spec/controllers/projects/prometheus/alerts_controller_spec.rb
@@ -6,7 +6,6 @@ RSpec.describe Projects::Prometheus::AlertsController, feature_category: :incide
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
- let_it_be(:metric) { create(:prometheus_metric, project: project) }
before do
project.add_maintainer(user)
@@ -43,16 +42,6 @@ RSpec.describe Projects::Prometheus::AlertsController, feature_category: :incide
end
end
- shared_examples 'project non-specific metric' do |status|
- let(:other) { create(:prometheus_alert) }
-
- it "returns #{status}" do
- make_request(id: other.prometheus_metric_id)
-
- expect(response).to have_gitlab_http_status(status)
- end
- end
-
describe 'POST #notify' do
let(:alert_1) { build(:alert_management_alert, :prometheus, project: project) }
let(:alert_2) { build(:alert_management_alert, :prometheus, project: project) }
@@ -115,67 +104,7 @@ RSpec.describe Projects::Prometheus::AlertsController, feature_category: :incide
end
end
- describe 'GET #metrics_dashboard', feature_category: :metrics do
- let!(:alert) do
- create(:prometheus_alert, project: project, environment: environment, prometheus_metric: metric)
- end
-
- before do
- stub_feature_flags(remove_monitor_metrics: false)
- end
-
- it 'returns a json object with the correct keys' do
- get :metrics_dashboard, params: request_params(id: metric.id, environment_id: alert.environment.id), format: :json
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.keys).to contain_exactly('dashboard', 'status', 'metrics_data')
- end
-
- it 'is the correct embed' do
- get :metrics_dashboard, params: request_params(id: metric.id, environment_id: alert.environment.id), format: :json
-
- title = json_response['dashboard']['panel_groups'][0]['panels'][0]['title']
-
- expect(title).to eq(metric.title)
- end
-
- it 'finds the first alert embed without environment_id' do
- get :metrics_dashboard, params: request_params(id: metric.id), format: :json
-
- title = json_response['dashboard']['panel_groups'][0]['panels'][0]['title']
-
- expect(title).to eq(metric.title)
- end
-
- it 'returns 404 for non-existant alerts' do
- get :metrics_dashboard, params: request_params(id: 0), format: :json
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
-
- it 'returns 404 when metrics dashboard feature is unavailable' do
- stub_feature_flags(remove_monitor_metrics: true)
-
- get :metrics_dashboard, params: request_params(id: 0), format: :json
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
def project_params(opts = {})
opts.reverse_merge(namespace_id: project.namespace, project_id: project)
end
-
- def request_params(opts = {}, defaults = {})
- project_params(opts.reverse_merge(defaults))
- end
-
- def alert_path(alert)
- project_prometheus_alert_path(
- project,
- alert.prometheus_metric_id,
- environment_id: alert.environment,
- format: :json
- )
- end
end
diff --git a/spec/factories/merge_request_diffs.rb b/spec/factories/merge_request_diffs.rb
index f93f3f22109..d81f355737e 100644
--- a/spec/factories/merge_request_diffs.rb
+++ b/spec/factories/merge_request_diffs.rb
@@ -2,7 +2,7 @@
FactoryBot.define do
factory :merge_request_diff do
- association :merge_request, factory: :merge_request_without_merge_request_diff
+ association :merge_request, :skip_diff_creation
state { :collected }
commits_count { 1 }
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 4941a31982f..390db24dde8 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -313,6 +313,12 @@ FactoryBot.define do
sequence(:source_branch) { |n| "feature#{n}" }
end
+ trait :skip_diff_creation do
+ before(:create) do |merge_request, _|
+ merge_request.skip_ensure_merge_request_diff = true
+ end
+ end
+
after(:build) do |merge_request|
target_project = merge_request.target_project
source_project = merge_request.source_project
@@ -357,7 +363,5 @@ FactoryBot.define do
merge_request.update!(labels: evaluator.labels)
end
end
-
- factory :merge_request_without_merge_request_diff, class: 'MergeRequestWithoutMergeRequestDiff'
end
end
diff --git a/spec/features/clusters/cluster_health_dashboard_spec.rb b/spec/features/clusters/cluster_health_dashboard_spec.rb
deleted file mode 100644
index e932f8c6b98..00000000000
--- a/spec/features/clusters/cluster_health_dashboard_spec.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Cluster Health board', :js, :kubeclient, :use_clean_rails_memory_store_caching, :sidekiq_inline,
-feature_category: :deployment_management do
- include KubernetesHelpers
- include PrometheusHelpers
-
- let_it_be(:current_user) { create(:user) }
- let_it_be(:clusterable) { create(:project) }
- let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
- let_it_be(:cluster_path) { project_cluster_path(clusterable, cluster) }
-
- before do
- stub_feature_flags(remove_monitor_metrics: false)
-
- clusterable.add_maintainer(current_user)
-
- sign_in(current_user)
- end
-
- it 'shows cluster board section within the page' do
- visit cluster_path
-
- expect(page).to have_text('Health')
-
- click_link 'Health'
-
- expect(page).to have_css('.cluster-health-graphs')
- end
-
- context 'feature remove_monitor_metrics enabled' do
- before do
- stub_feature_flags(remove_monitor_metrics: true)
- end
-
- it 'does not show the cluster health tab' do
- visit cluster_path
-
- expect(page).not_to have_text('Health')
- end
-
- it 'does not show the cluster health section' do
- visit project_cluster_path(clusterable, cluster, { tab: 'health' })
-
- expect(page).not_to have_text('you must first enable Prometheus in the Integrations tab')
- end
- end
-
- context 'no prometheus available' do
- it 'shows enable Prometheus message' do
- visit cluster_path
-
- click_link 'Health'
-
- expect(page).to have_text('you must first enable Prometheus in the Integrations tab')
- end
- end
-
- context 'when there is cluster with enabled prometheus' do
- before do
- create(:clusters_integrations_prometheus, enabled: true, cluster: cluster)
- stub_kubeclient_discover(cluster.platform.api_url)
- end
-
- context 'waiting for data' do
- before do
- stub_empty_response
- end
-
- it 'shows container and waiting for data message' do
- visit cluster_path
-
- click_link 'Health'
-
- wait_for_requests
-
- expect(page).to have_css('.prometheus-graphs')
- expect(page).to have_text('Waiting for performance data')
- end
- end
-
- context 'connected, prometheus returns data' do
- before do
- stub_connected
-
- visit cluster_path
-
- click_link 'Health'
-
- wait_for_requests
- end
-
- it 'renders charts' do
- expect(page).to have_css('.prometheus-graphs')
- expect(page).to have_css('.prometheus-graph')
- expect(page).to have_css('.prometheus-graph-title')
- expect(page).to have_css('[_echarts_instance_]')
- expect(page).to have_css('.prometheus-graph', count: 2)
- expect(page).to have_content('Avg')
- end
-
- it 'focuses the single panel on toggle' do
- click_button('More actions', match: :first)
- click_button('Expand panel')
-
- expect(page).to have_css('.prometheus-graph', count: 1)
-
- click_button('Collapse panel')
-
- expect(page).to have_css('.prometheus-graph', count: 2)
- end
- end
-
- def stub_empty_response
- stub_prometheus_request(/prometheus-prometheus-server/, status: 204, body: {})
- stub_prometheus_request(%r{prometheus/api/v1}, status: 204, body: {})
- end
-
- def stub_connected
- stub_prometheus_request(/prometheus-prometheus-server/, body: prometheus_values_body)
- stub_prometheus_request(%r{prometheus/api/v1}, body: prometheus_values_body)
- end
- end
-end
diff --git a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb
index 38992a29dcb..0afd3201853 100644
--- a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb
+++ b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
include UsageDataHelpers
let(:temp_dir) { Dir.mktmpdir }
- let(:tmpfile) { Tempfile.new('test-metadata') }
let(:ee_temp_dir) { Dir.mktmpdir }
+ let(:tmpfile) { Tempfile.new('test-metadata') }
let(:existing_key_paths) { {} }
let(:description) { "This metric counts unique users viewing analytics metrics dashboard section" }
let(:group) { "group::analytics instrumentation" }
@@ -16,6 +16,8 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
let(:mr) { "https://gitlab.com/some-group/some-project/-/merge_requests/123" }
let(:event) { "view_analytics_dashboard" }
let(:unique_on) { "user_id" }
+ let(:time_frames) { %w[7d] }
+ let(:include_default_identifiers) { 'yes' }
let(:options) do
{
time_frames: time_frames,
@@ -34,7 +36,6 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
let(:metric_definition_7d) do
{
"key_path" => key_path_7d,
- "name" => key_path_7d,
"description" => description,
"product_section" => section,
"product_stage" => stage,
@@ -54,10 +55,12 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
end
before do
- stub_const("#{described_class}::TOP_LEVEL_DIR", temp_dir)
stub_const("#{described_class}::TOP_LEVEL_DIR_EE", ee_temp_dir)
+ stub_const("#{described_class}::TOP_LEVEL_DIR", temp_dir)
stub_const("#{described_class}::KNOWN_EVENTS_PATH", tmpfile.path)
stub_const("#{described_class}::KNOWN_EVENTS_PATH_EE", tmpfile.path)
+ # Stub version so that `milestone` key remains constant between releases to prevent flakiness.
+ stub_const('Gitlab::VERSION', '13.9.0')
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:ask)
@@ -65,8 +68,8 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
.and_return(description)
end
- allow(Gitlab::Usage::MetricDefinition)
- .to receive(:definitions).and_return(existing_key_paths)
+ allow(Gitlab::TaskHelpers).to receive(:prompt).and_return(include_default_identifiers)
+ allow(Gitlab::Usage::MetricDefinition).to receive(:definitions).and_return(existing_key_paths)
end
after do
@@ -75,12 +78,85 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
FileUtils.rm_rf(tmpfile.path)
end
- describe 'Creating metric definition file' do
- before do
- # Stub version so that `milestone` key remains constant between releases to prevent flakiness.
- stub_const('Gitlab::VERSION', '13.9.0')
+ describe 'Creating event definition file' do
+ let(:event_definition_path) { Dir.glob(File.join(temp_dir, "events/#{event}.yml")).first }
+ let(:identifiers) { %w[project user namespace] }
+ let(:event_definition) do
+ {
+ "category" => "GitlabInternalEvents",
+ "action" => event,
+ "description" => description,
+ "product_section" => section,
+ "product_stage" => stage,
+ "product_group" => group,
+ "label_description" => nil,
+ "property_description" => nil,
+ "value_description" => nil,
+ "extra_properties" => nil,
+ "identifiers" => identifiers,
+ "milestone" => "13.9",
+ "introduced_by_url" => mr,
+ "distributions" => %w[ce ee],
+ "tiers" => %w[free premium ultimate]
+ }
end
+ it 'creates an event definition file using the template' do
+ described_class.new([], options).invoke_all
+
+ expect(YAML.safe_load(File.read(event_definition_path))).to eq(event_definition)
+ end
+
+ context 'for ultimate only feature' do
+ let(:event_definition_path) do
+ Dir.glob(File.join(ee_temp_dir, temp_dir, "events/#{event}.yml")).first
+ end
+
+ it 'creates an event definition file using the template' do
+ described_class.new([], options.merge(tiers: %w[ultimate])).invoke_all
+
+ expect(YAML.safe_load(File.read(event_definition_path)))
+ .to eq(event_definition.merge("tiers" => ["ultimate"], "distributions" => ["ee"]))
+ end
+ end
+
+ context 'without default identifiers' do
+ let(:include_default_identifiers) { 'no' }
+
+ it 'creates an event definition file using the template' do
+ described_class.new([], options).invoke_all
+
+ expect(YAML.safe_load(File.read(event_definition_path)))
+ .to eq(event_definition.merge("identifiers" => nil))
+ end
+ end
+
+ context 'with duplicated event' do
+ context 'in known_events files' do
+ before do
+ allow(::Gitlab::UsageDataCounters::HLLRedisCounter)
+ .to receive(:known_event?).with(event).and_return(true)
+ end
+
+ it 'raises error' do
+ expect { described_class.new([], options).invoke_all }.to raise_error(RuntimeError)
+ end
+ end
+
+ context 'in event definition files' do
+ before do
+ Dir.mkdir(File.join(temp_dir, "events"))
+ File.write(File.join(temp_dir, "events", "#{event}.yml"), { action: event }.to_yaml)
+ end
+
+ it 'raises error' do
+ expect { described_class.new([], options).invoke_all }.to raise_error(RuntimeError)
+ end
+ end
+ end
+ end
+
+ describe 'Creating metric definition file' do
context 'for single time frame' do
let(:time_frames) { %w[7d] }
@@ -146,10 +222,14 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
it 'asks again for description' do
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:ask)
+ .with(/By convention all events automatically include the following properties/)
+ .and_return(include_default_identifiers)
+
+ allow(instance).to receive(:ask).twice
.with(/Please describe in at least 50 characters/)
.and_return("I am to short")
- expect(instance).to receive(:ask)
+ expect(instance).to receive(:ask).twice
.with(/Please provide description that is 50 characters long/)
.and_return(description)
end
@@ -166,7 +246,6 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
let(:metric_definition_28d) do
metric_definition_7d.merge(
"key_path" => key_path_28d,
- "name" => key_path_28d,
"time_frame" => "28d"
)
end
@@ -186,7 +265,6 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
let(:metric_definition_28d) do
metric_definition_7d.merge(
"key_path" => key_path_28d,
- "name" => key_path_28d,
"time_frame" => "28d"
)
end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 718b20c59ed..53dc145dcc4 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -258,6 +258,23 @@ RSpec.describe Gitlab::PathRegex do
end
end
+ describe '.organization_path_regex' do
+ subject { described_class.organization_path_regex }
+
+ it 'rejects reserved words' do
+ expect(subject).not_to match('admin/')
+ expect(subject).not_to match('api/')
+ expect(subject).not_to match('create/')
+ expect(subject).not_to match('new/')
+ end
+
+ it 'accepts other words' do
+ expect(subject).to match('simple/')
+ expect(subject).to match('org/')
+ expect(subject).to match('my_org/')
+ end
+ end
+
describe '.full_namespace_path_regex' do
subject { described_class.full_namespace_path_regex }
diff --git a/spec/lib/google_cloud/authentication_spec.rb b/spec/lib/google_cloud/authentication_spec.rb
new file mode 100644
index 00000000000..5c7f3e51152
--- /dev/null
+++ b/spec/lib/google_cloud/authentication_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GoogleCloud::Authentication, feature_category: :audit_events do
+ describe '#generate_access_token' do
+ let_it_be(:client_email) { 'test@example.com' }
+ let_it_be(:private_key) { 'private_key' }
+ let_it_be(:scope) { 'https://www.googleapis.com/auth/logging.write' }
+ let_it_be(:json_key_io) { StringIO.new({ client_email: client_email, private_key: private_key }.to_json) }
+
+ let(:service_account_credentials) { instance_double('Google::Auth::ServiceAccountCredentials') }
+
+ subject(:generate_access_token) do
+ described_class.new(scope: scope).generate_access_token(client_email, private_key)
+ end
+
+ before do
+ allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds).with(json_key_io: json_key_io,
+ scope: scope).and_return(service_account_credentials)
+ allow(StringIO).to receive(:new).with({ client_email: client_email,
+ private_key: private_key }.to_json).and_return(json_key_io)
+ end
+
+ context 'when credentials are valid' do
+ before do
+ allow(service_account_credentials).to receive(:fetch_access_token!).and_return({ 'access_token' => 'token' })
+ end
+
+ it 'calls make_creds with correct parameters' do
+ expect(Google::Auth::ServiceAccountCredentials).to receive(:make_creds).with(json_key_io: json_key_io,
+ scope: scope)
+
+ generate_access_token
+ end
+
+ it 'fetches access token' do
+ expect(generate_access_token).to eq('token')
+ end
+ end
+
+ context 'when an error occurs' do
+ before do
+ allow(service_account_credentials).to receive(:fetch_access_token!).and_raise(StandardError)
+ end
+
+ it 'handles the exception and returns nil' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ expect(generate_access_token).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/google_cloud/logging_service/logger_spec.rb b/spec/lib/google_cloud/logging_service/logger_spec.rb
new file mode 100644
index 00000000000..31f8bb27ec5
--- /dev/null
+++ b/spec/lib/google_cloud/logging_service/logger_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GoogleCloud::LoggingService::Logger, feature_category: :audit_events do
+ let_it_be(:client_email) { 'test@example.com' }
+ let_it_be(:private_key) { 'private_key' }
+ let_it_be(:payload) { [{ logName: 'test-log' }.to_json] }
+ let_it_be(:access_token) { 'access_token' }
+ let_it_be(:expected_headers) do
+ { 'Authorization' => "Bearer #{access_token}", 'Content-Type' => 'application/json' }
+ end
+
+ subject(:log) { described_class.new.log(client_email, private_key, payload) }
+
+ describe '#log' do
+ context 'when access token is available' do
+ before do
+ allow_next_instance_of(GoogleCloud::Authentication) do |instance|
+ allow(instance).to receive(:generate_access_token).with(client_email, private_key).and_return(access_token)
+ end
+ end
+
+ it 'generates access token and calls Gitlab::HTTP.post with correct parameters' do
+ expect(Gitlab::HTTP).to receive(:post).with(
+ described_class::WRITE_URL,
+ body: payload,
+ headers: expected_headers
+ )
+
+ log
+ end
+
+ context 'when URI::InvalidURIError is raised' do
+ before do
+ allow(Gitlab::HTTP).to receive(:post).and_raise(URI::InvalidURIError)
+ end
+
+ it 'logs the exception' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception)
+
+ log
+ end
+ end
+ end
+
+ context 'when access token is not available' do
+ let(:access_token) { nil }
+
+ it 'does not call Gitlab::HTTP.post' do
+ allow_next_instance_of(GoogleCloud::Authentication) do |instance|
+ allow(instance).to receive(:generate_access_token).with(client_email, private_key).and_return(access_token)
+ end
+
+ expect(Gitlab::HTTP).not_to receive(:post)
+
+ log
+ end
+ end
+ end
+end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index e2c87b0d85c..57a9963c0f1 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -1178,14 +1178,14 @@ RSpec.describe MergeRequestDiff, feature_category: :code_review_workflow do
end
describe '.latest_diff_for_merge_requests' do
- let_it_be(:merge_request_1) { create(:merge_request_without_merge_request_diff) }
+ let_it_be(:merge_request_1) { create(:merge_request, :skip_diff_creation) }
let_it_be(:merge_request_1_diff_1) { create(:merge_request_diff, merge_request: merge_request_1, created_at: 3.days.ago) }
let_it_be(:merge_request_1_diff_2) { create(:merge_request_diff, merge_request: merge_request_1, created_at: 1.day.ago) }
- let_it_be(:merge_request_2) { create(:merge_request_without_merge_request_diff) }
+ let_it_be(:merge_request_2) { create(:merge_request, :skip_diff_creation) }
let_it_be(:merge_request_2_diff_1) { create(:merge_request_diff, merge_request: merge_request_2, created_at: 3.days.ago) }
- let_it_be(:merge_request_3) { create(:merge_request_without_merge_request_diff) }
+ let_it_be(:merge_request_3) { create(:merge_request, :skip_diff_creation) }
subject { described_class.latest_diff_for_merge_requests([merge_request_1, merge_request_2]) }
diff --git a/spec/models/organizations/organization_spec.rb b/spec/models/organizations/organization_spec.rb
index bb3d0c2307d..3b202b85b48 100644
--- a/spec/models/organizations/organization_spec.rb
+++ b/spec/models/organizations/organization_spec.rb
@@ -18,6 +18,37 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
it { is_expected.to validate_length_of(:name).is_at_most(255) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_length_of(:path).is_at_least(2).is_at_most(255) }
+
+ describe 'path validator' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:default_path_error) do
+ "can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.', '.git' or '.atom'."
+ end
+
+ let(:reserved_path_error) do
+ "is a reserved name"
+ end
+
+ where(:path, :valid, :error_message) do
+ 'path.' | false | ref(:default_path_error)
+ 'path.git' | false | ref(:default_path_error)
+ 'new' | false | ref(:reserved_path_error)
+ '.path' | true | nil
+ 'org__path' | true | nil
+ 'some-name' | true | nil
+ 'simple' | true | nil
+ end
+
+ with_them do
+ it 'validates organization path' do
+ organization = build(:organization, name: 'Default', path: path)
+
+ expect(organization.valid?).to be(valid)
+ expect(organization.errors.full_messages.to_sentence).to include(error_message) if error_message.present?
+ end
+ end
+ end
end
context 'when using scopes' do
diff --git a/spec/models/preloaders/merge_request_diff_preloader_spec.rb b/spec/models/preloaders/merge_request_diff_preloader_spec.rb
index 9a76d42e73f..9ca5039c4e6 100644
--- a/spec/models/preloaders/merge_request_diff_preloader_spec.rb
+++ b/spec/models/preloaders/merge_request_diff_preloader_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Preloaders::MergeRequestDiffPreloader do
let_it_be(:merge_request_1) { create(:merge_request) }
let_it_be(:merge_request_2) { create(:merge_request) }
- let_it_be(:merge_request_3) { create(:merge_request_without_merge_request_diff) }
+ let_it_be(:merge_request_3) { create(:merge_request, :skip_diff_creation) }
let(:merge_requests) { [merge_request_1, merge_request_2, merge_request_3] }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index d10a8afc51e..0e3382b4c6f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe User, feature_category: :user_profile do
+ using RSpec::Parameterized::TableSyntax
+
include ProjectForksHelper
include TermsHelper
include ExclusiveLeaseHelpers
@@ -4168,8 +4170,6 @@ RSpec.describe User, feature_category: :user_profile do
end
describe '#following_users_allowed?' do
- using RSpec::Parameterized::TableSyntax
-
let_it_be(:user) { create(:user) }
let_it_be(:followee) { create(:user) }
@@ -6021,27 +6021,42 @@ RSpec.describe User, feature_category: :user_profile do
let(:user) { create(:user, note: "existing note") }
let(:deleted_by) { create(:user) }
- it 'blocks the user then schedules them for deletion if a hard delete is specified' do
- expect(DeleteUserWorker).to receive(:perform_async).with(deleted_by.id, user.id, { hard_delete: true })
+ shared_examples 'schedules user for deletion without delay' do
+ it 'schedules user for deletion without delay' do
+ expect(DeleteUserWorker).to receive(:perform_async).with(deleted_by.id, user.id, {})
+ expect(DeleteUserWorker).not_to receive(:perform_in)
+
+ user.delete_async(deleted_by: deleted_by)
+ end
+ end
+
+ shared_examples 'it does not block the user' do
+ it 'does not block the user' do
+ user.delete_async(deleted_by: deleted_by)
+ expect(user).not_to be_blocked
+ end
+ end
+
+ it 'blocks the user if hard delete is specified' do
user.delete_async(deleted_by: deleted_by, params: { hard_delete: true })
expect(user).to be_blocked
end
- it 'schedules user for deletion without blocking them' do
- expect(DeleteUserWorker).to receive(:perform_async).with(deleted_by.id, user.id, {})
-
- user.delete_async(deleted_by: deleted_by)
+ it_behaves_like 'schedules user for deletion without delay'
- expect(user).not_to be_blocked
- end
+ it_behaves_like 'it does not block the user'
context 'when target user is the same as deleted_by' do
let(:deleted_by) { user }
subject { user.delete_async(deleted_by: deleted_by) }
+ before do
+ allow(user).to receive(:has_possible_spam_contributions?).and_return(true)
+ end
+
shared_examples 'schedules the record for deletion with the correct delay' do
it 'schedules the record for deletion with the correct delay' do
freeze_time do
@@ -6061,12 +6076,64 @@ RSpec.describe User, feature_category: :user_profile do
expect(user).not_to be_banned
end
+ context 'with possible spam contribution' do
+ context 'with comments' do
+ it_behaves_like 'schedules the record for deletion with the correct delay' do
+ before do
+ allow(user).to receive(:has_possible_spam_contributions?).and_call_original
+
+ note = create(:note_on_issue, author: user)
+ create(:event, :commented, target: note, author: user)
+ end
+ end
+ end
+
+ context 'with other types' do
+ where(:resource, :action, :delayed) do
+ 'Issue' | :created | true
+ 'MergeRequest' | :created | true
+ 'Issue' | :closed | false
+ 'MergeRequest' | :closed | false
+ 'WorkItem' | :created | false
+ end
+
+ with_them do
+ before do
+ allow(user).to receive(:has_possible_spam_contributions?).and_call_original
+
+ case resource
+ when 'Issue'
+ create(:event, action, :for_issue, author: user)
+ when 'MergeRequest'
+ create(:event, action, :for_merge_request, author: user)
+ when 'WorkItem'
+ create(:event, action, :for_work_item, author: user)
+ end
+ end
+
+ if params[:delayed]
+ it_behaves_like 'schedules the record for deletion with the correct delay'
+ else
+ it_behaves_like 'schedules user for deletion without delay'
+ end
+ end
+ end
+ end
+
+ context 'when user has no possible spam contributions' do
+ before do
+ allow(user).to receive(:has_possible_spam_contributions?).and_return(false)
+ end
+
+ it_behaves_like 'schedules user for deletion without delay'
+ end
+
context 'when the user is a spammer' do
before do
allow(user).to receive(:spammer?).and_return(true)
end
- context 'when the user acount is less than 7 days old' do
+ context 'when the user account is less than 7 days old' do
it_behaves_like 'schedules the record for deletion with the correct delay'
it 'creates an abuse report with the correct data' do
@@ -6140,13 +6207,9 @@ RSpec.describe User, feature_category: :user_profile do
stub_feature_flags(delay_delete_own_user: false)
end
- it 'schedules user for deletion without blocking them' do
- expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id, {})
-
- subject
+ it_behaves_like 'schedules user for deletion without delay'
- expect(user).not_to be_blocked
- end
+ it_behaves_like 'it does not block the user'
it 'does not update the note' do
expect { user.delete_async(deleted_by: deleted_by) }.not_to change { user.note }
@@ -7303,8 +7366,6 @@ RSpec.describe User, feature_category: :user_profile do
let(:user_id) { user.id }
describe 'update user' do
- using RSpec::Parameterized::TableSyntax
-
where(:attributes) do
[
{ state: 'blocked' },
diff --git a/spec/requests/api/ml/mlflow/experiments_spec.rb b/spec/requests/api/ml/mlflow/experiments_spec.rb
index 1a2577e69e7..fc2e814752c 100644
--- a/spec/requests/api/ml/mlflow/experiments_spec.rb
+++ b/spec/requests/api/ml/mlflow/experiments_spec.rb
@@ -20,7 +20,6 @@ RSpec.describe API::Ml::Mlflow::Experiments, feature_category: :mlops do
end
let(:current_user) { developer }
- let(:ff_value) { true }
let(:access_token) { tokens[:write] }
let(:headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
let(:project_id) { project.id }
@@ -52,10 +51,6 @@ RSpec.describe API::Ml::Mlflow::Experiments, feature_category: :mlops do
response
end
- before do
- stub_feature_flags(ml_experiment_tracking: ff_value)
- end
-
describe 'GET /projects/:id/ml/mlflow/api/2.0/mlflow/experiments/get' do
let(:experiment_iid) { experiment.iid.to_s }
let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/experiments/get?experiment_id=#{experiment_iid}" }
diff --git a/spec/requests/api/ml/mlflow/runs_spec.rb b/spec/requests/api/ml/mlflow/runs_spec.rb
index 746372b7978..a85fe4d867a 100644
--- a/spec/requests/api/ml/mlflow/runs_spec.rb
+++ b/spec/requests/api/ml/mlflow/runs_spec.rb
@@ -26,7 +26,6 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do
end
let(:current_user) { developer }
- let(:ff_value) { true }
let(:access_token) { tokens[:write] }
let(:headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
let(:project_id) { project.id }
@@ -40,10 +39,6 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do
response
end
- before do
- stub_feature_flags(ml_experiment_tracking: ff_value)
- end
-
RSpec.shared_examples 'MLflow|run_id param error cases' do
context 'when run id is not passed' do
let(:params) { {} }
diff --git a/spec/support/helpers/models/merge_request_without_merge_request_diff.rb b/spec/support/helpers/models/merge_request_without_merge_request_diff.rb
deleted file mode 100644
index e9f97a2c95a..00000000000
--- a/spec/support/helpers/models/merge_request_without_merge_request_diff.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class MergeRequestWithoutMergeRequestDiff < ::MergeRequest # rubocop:disable Gitlab/NamespacedClass
- self.inheritance_column = :_type_disabled
-
- def ensure_merge_request_diff; end
-end
diff --git a/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb b/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb
deleted file mode 100644
index 9cdde13b36b..00000000000
--- a/spec/support/shared_examples/controllers/metrics/dashboard/prometheus_api_proxy_shared_examples.rb
+++ /dev/null
@@ -1,163 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples_for 'metrics dashboard prometheus api proxy' do
- let(:service_params) { [proxyable, 'GET', 'query', expected_params] }
- let(:service_result) { { status: :success, body: prometheus_body } }
- let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
- let(:proxyable_params) do
- {
- id: proxyable.id.to_s
- }
- end
-
- let(:expected_params) do
- ActionController::Parameters.new(
- prometheus_proxy_params(
- proxy_path: 'query',
- controller: described_class.controller_path,
- action: 'prometheus_proxy'
- )
- ).permit!
- end
-
- before do
- stub_feature_flags(remove_monitor_metrics: false)
-
- allow_next_instance_of(Prometheus::ProxyService, *service_params) do |proxy_service|
- allow(proxy_service).to receive(:execute).and_return(service_result)
- end
- end
-
- context 'with valid requests' do
- context 'with success result' do
- let(:prometheus_body) { '{"status":"success"}' }
- let(:prometheus_json_body) { Gitlab::Json.parse(prometheus_body) }
-
- it 'returns prometheus response' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq(prometheus_json_body)
- end
-
- context 'with nil query' do
- let(:params_without_query) do
- prometheus_proxy_params.except(:query)
- end
-
- before do
- expected_params.delete(:query)
- end
-
- it 'does not raise error' do
- get :prometheus_proxy, params: params_without_query
-
- expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
- end
- end
- end
-
- context 'with nil result' do
- let(:service_result) { nil }
-
- it 'returns 204 no_content' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(json_response['status']).to eq(_('processing'))
- expect(json_response['message']).to eq(_('Not ready yet. Try again later.'))
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- context 'with 404 result' do
- let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
-
- it 'returns body' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['body']).to eq('value')
- end
- end
-
- context 'with error result' do
- context 'with http_status' do
- let(:service_result) do
- { http_status: :service_unavailable, status: :error, message: 'error message' }
- end
-
- it 'sets the http response status code' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:service_unavailable)
- expect(json_response['status']).to eq('error')
- expect(json_response['message']).to eq('error message')
- end
- end
-
- context 'without http_status' do
- let(:service_result) { { status: :error, message: 'error message' } }
-
- it 'returns bad_request' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['status']).to eq('error')
- expect(json_response['message']).to eq('error message')
- end
- end
- end
-
- context 'when metrics dashboard feature is unavailable' do
- before do
- stub_feature_flags(remove_monitor_metrics: true)
- end
-
- it 'returns 404 not found' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(response.body).to be_empty
- end
- end
- end
-
- context 'with inappropriate requests' do
- let(:prometheus_body) { nil }
-
- context 'without correct permissions' do
- let(:user2) { create(:user) }
-
- before do
- sign_out(user)
- sign_in(user2)
- end
-
- it 'returns 404' do
- get :prometheus_proxy, params: prometheus_proxy_params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context 'with invalid proxyable id' do
- let(:prometheus_body) { nil }
-
- it 'returns 404' do
- get :prometheus_proxy, params: prometheus_proxy_params(id: proxyable.id + 1)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- private
-
- def prometheus_proxy_params(params = {})
- {
- proxy_path: 'query',
- query: '1'
- }.merge(proxyable_params).merge(params)
- end
-end
diff --git a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
index 2ca62698daf..f2c38d70508 100644
--- a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb
@@ -47,8 +47,13 @@ RSpec.shared_examples 'MLflow|shared error cases' do
end
end
- context 'when ff is disabled' do
- let(:ff_value) { false }
+ context 'when model experiments is unavailable' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :read_model_experiments, project)
+ .and_return(false)
+ end
it "is Not Found" do
is_expected.to have_gitlab_http_status(:not_found)
diff --git a/spec/validators/organizations/path_validator_spec.rb b/spec/validators/organizations/path_validator_spec.rb
new file mode 100644
index 00000000000..415c10d98df
--- /dev/null
+++ b/spec/validators/organizations/path_validator_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Organizations::PathValidator, feature_category: :cell do
+ let(:validator) { described_class.new(attributes: [:path]) }
+
+ describe '.valid_path?' do
+ it 'handles invalid utf8' do
+ expect(described_class.valid_path?(+"a\0weird\255path")).to be_falsey
+ end
+ end
+
+ describe '#validates_each' do
+ it 'adds a message when the path is not in the correct format' do
+ organization = build(:organization)
+
+ validator.validate_each(organization, :path, "Path with spaces, and comma's!")
+
+ expect(organization.errors[:path]).to include(Gitlab::PathRegex.namespace_format_message)
+ end
+
+ it 'adds a message when the path is reserved when creating' do
+ organization = build(:organization, path: 'help')
+
+ validator.validate_each(organization, :path, 'help')
+
+ expect(organization.errors[:path]).to include('help is a reserved name')
+ end
+
+ it 'adds a message when the path is reserved when updating' do
+ organization = create(:organization)
+ organization.path = 'help'
+
+ validator.validate_each(organization, :path, 'help')
+
+ expect(organization.errors[:path]).to include('help is a reserved name')
+ end
+ end
+end