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/docs.gitlab-ci.yml2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/stylesheets/pages/boards.scss13
-rw-r--r--app/assets/stylesheets/pages/issuable.scss28
-rw-r--r--app/controllers/projects/environments_controller.rb6
-rw-r--r--app/presenters/project_presenter.rb25
-rw-r--r--app/presenters/release_presenter.rb2
-rw-r--r--app/serializers/review_app_setup_entity.rb25
-rw-r--r--app/serializers/review_app_setup_serializer.rb5
-rw-r--r--app/services/prometheus/proxy_service.rb12
-rw-r--r--app/workers/concerns/cluster_queue.rb2
-rw-r--r--app/workers/group_destroy_worker.rb2
-rw-r--r--changelogs/unreleased/194144-speed-up-url-helpers.yml5
-rw-r--r--changelogs/unreleased/22465-rack-attack-authenticate-job-token-requests.yml5
-rw-r--r--changelogs/unreleased/27427-boards-sidebar-icon.yml5
-rw-r--r--changelogs/unreleased/39119-actioncontroller-urlgenerationerror-no-route-matches-action-evidenc.yml5
-rw-r--r--changelogs/unreleased/sh-disable-prom-metrics-on-failure.yml5
-rw-r--r--changelogs/unreleased/sidekiq-cluster-terminate-hung-workers.yml5
-rw-r--r--changelogs/unreleased/smaller-prom-redis-keys.yml5
-rw-r--r--config/feature_categories.yml21
-rw-r--r--config/initializers/7_prometheus_metrics.rb6
-rw-r--r--config/initializers/action_dispatch_journey_formatter.rb19
-rw-r--r--doc/administration/packages/container_registry.md2
-rw-r--r--doc/security/asset_proxy.md32
-rw-r--r--lib/gitlab/auth/auth_finders.rb11
-rw-r--r--lib/gitlab/auth/request_authenticator.rb11
-rw-r--r--lib/gitlab/ci/snippets/review_app_default.yml9
-rw-r--r--lib/gitlab/metrics.rb6
-rw-r--r--lib/gitlab/metrics/prometheus.rb10
-rw-r--r--lib/gitlab/patch/action_dispatch_journey_formatter.rb34
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/features/boards/sidebar_spec.rb1
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb10
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb87
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb51
-rw-r--r--spec/lib/gitlab/metrics/prometheus_spec.rb17
-rw-r--r--spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb33
-rw-r--r--spec/presenters/project_presenter_spec.rb82
-rw-r--r--spec/presenters/release_presenter_spec.rb24
-rw-r--r--spec/requests/api/releases_spec.rb10
-rw-r--r--spec/serializers/review_app_setup_entity_spec.rb54
-rw-r--r--spec/services/prometheus/proxy_service_spec.rb6
43 files changed, 627 insertions, 75 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index c776e546f11..4acc3c7d1fe 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -50,7 +50,7 @@ docs lint:
- .default-retry
- .default-only
- .only:changes-docs
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint"
+ image: "registry.gitlab.com/gitlab-org/gitlab-docs:docs-lint"
stage: test
dependencies: []
script:
diff --git a/Gemfile b/Gemfile
index 5f3721e7663..0c4f8d83782 100644
--- a/Gemfile
+++ b/Gemfile
@@ -327,7 +327,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false
# Prometheus
- gem 'prometheus-client-mmap', '~> 0.9.10'
+ gem 'prometheus-client-mmap', '~> 0.10.0'
gem 'raindrops', '~> 0.18'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index c5f3dc3e5a4..6a19d35774d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -749,7 +749,7 @@ GEM
parser
unparser
procto (0.0.3)
- prometheus-client-mmap (0.9.10)
+ prometheus-client-mmap (0.10.0)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@@ -1292,7 +1292,7 @@ DEPENDENCIES
pg (~> 1.1)
png_quantizator (~> 0.2.1)
premailer-rails (~> 1.10.3)
- prometheus-client-mmap (~> 0.9.10)
+ prometheus-client-mmap (~> 0.10.0)
pry-byebug (~> 3.5.1)
pry-rails (~> 0.3.4)
rack (~> 2.0.7)
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index c4c1bb0bcc1..31e87d1a7cf 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -386,22 +386,19 @@
margin: 5px;
}
-.issue-boards-sidebar {
+.right-sidebar.issue-boards-sidebar {
.gutter-toggle {
bottom: 15px;
width: 22px;
- color: $gray-darkest;
+ padding-left: $gl-padding-32;
svg {
position: absolute;
top: 50%;
+ right: 0;
margin-top: (-11px / 2);
- }
-
- &:hover {
- path {
- fill: $gray-darkest;
- }
+ height: $gl-font-size-12;
+ width: $gl-font-size-12;
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 11d5f4cd374..43636f65eb8 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -173,6 +173,20 @@
margin-top: 7px;
}
+ .gutter-toggle {
+ margin-left: 20px;
+ padding-left: 10px;
+
+ &:hover {
+ color: $gl-text-color;
+ }
+
+ &:hover,
+ &:focus {
+ text-decoration: none;
+ }
+ }
+
.block {
@include clearfix;
padding: $gl-padding 0;
@@ -195,20 +209,6 @@
margin-top: 0;
}
- .gutter-toggle {
- margin-left: 20px;
- padding-left: 10px;
-
- &:hover {
- color: $gl-text-color;
- }
-
- &:hover,
- &:focus {
- text-decoration: none;
- }
- }
-
&.assignee {
.author-link {
display: block;
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 953a6d5b18a..dc06cd8c166 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -23,6 +23,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def index
@environments = project.environments
.with_state(params[:scope] || :available)
+ @project = ProjectPresenter.new(project, current_user: current_user)
respond_to do |format|
format.html
@@ -31,6 +32,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
render json: {
environments: serialize_environments(request, response, params[:nested]),
+ review_app: serialize_review_app,
available_count: project.environments.available.count,
stopped_count: project.environments.stopped.count
}
@@ -242,6 +244,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
.represent(@environments)
end
+ def serialize_review_app
+ ReviewAppSetupSerializer.new(current_user: @current_user).represent(@project)
+ end
+
def authorize_stop_environment!
access_denied! unless can?(current_user, :stop_environment, environment)
end
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 81018398d5d..45f4668112b 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -276,8 +276,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def kubernetes_cluster_anchor_data
- if current_user && can?(current_user, :create_cluster, project)
-
+ if can_instantiate_cluster?
if clusters.empty?
AnchorData.new(false,
statistic_icon + _('Add Kubernetes cluster'),
@@ -294,7 +293,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def gitlab_ci_anchor_data
- if current_user && can_current_user_push_code? && repository.gitlab_ci_yml.blank? && !auto_devops_enabled?
+ if cicd_missing?
AnchorData.new(false,
statistic_icon + _('Set up CI/CD'),
add_ci_yml_path)
@@ -326,8 +325,28 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
count_of_extra_topics_not_shown > 0
end
+ def can_setup_review_app?
+ strong_memoize(:can_setup_review_app) do
+ (can_instantiate_cluster? && all_clusters_empty?) || cicd_missing?
+ end
+ end
+
+ def all_clusters_empty?
+ strong_memoize(:all_clusters_empty) do
+ project.all_clusters.empty?
+ end
+ end
+
private
+ def cicd_missing?
+ current_user && can_current_user_push_code? && repository.gitlab_ci_yml.blank? && !auto_devops_enabled?
+ end
+
+ def can_instantiate_cluster?
+ current_user && can?(current_user, :create_cluster, project)
+ end
+
def filename_path(filename)
if blob = repository.public_send(filename) # rubocop:disable GitlabSecurity/PublicSend
project_blob_path(
diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb
index b38bbc8d96c..099ac9b09cd 100644
--- a/app/presenters/release_presenter.rb
+++ b/app/presenters/release_presenter.rb
@@ -40,7 +40,7 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
def evidence_file_path
return unless release.evidence.present?
- evidence_project_release_url(project, tag, format: :json)
+ evidence_project_release_url(project, release.to_param, format: :json)
end
private
diff --git a/app/serializers/review_app_setup_entity.rb b/app/serializers/review_app_setup_entity.rb
new file mode 100644
index 00000000000..3a21fe24d9e
--- /dev/null
+++ b/app/serializers/review_app_setup_entity.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class ReviewAppSetupEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :can_setup_review_app?, as: :can_setup_review_app
+
+ expose :all_clusters_empty?, as: :all_clusters_empty, if: -> (_, _) { project.can_setup_review_app? } do |project|
+ project.all_clusters_empty?
+ end
+
+ expose :review_snippet, if: -> (_, _) { project.can_setup_review_app? } do |_|
+ YAML.safe_load(File.read(Rails.root.join('lib', 'gitlab', 'ci', 'snippets', 'review_app_default.yml'))).to_s
+ end
+
+ private
+
+ def current_user
+ request.current_user
+ end
+
+ def project
+ object
+ end
+end
diff --git a/app/serializers/review_app_setup_serializer.rb b/app/serializers/review_app_setup_serializer.rb
new file mode 100644
index 00000000000..4baec7679b0
--- /dev/null
+++ b/app/serializers/review_app_setup_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ReviewAppSetupSerializer < BaseSerializer
+ entity ReviewAppSetupEntity
+end
diff --git a/app/services/prometheus/proxy_service.rb b/app/services/prometheus/proxy_service.rb
index a62eb76b8ce..3585c90fc8f 100644
--- a/app/services/prometheus/proxy_service.rb
+++ b/app/services/prometheus/proxy_service.rb
@@ -5,9 +5,17 @@ module Prometheus
include ReactiveCaching
include Gitlab::Utils::StrongMemoize
- self.reactive_cache_key = ->(service) { service.cache_key }
+ self.reactive_cache_key = ->(service) { [] }
self.reactive_cache_lease_timeout = 30.seconds
- self.reactive_cache_refresh_interval = 30.seconds
+
+ # reactive_cache_refresh_interval should be set to a value higher than
+ # reactive_cache_lifetime. If the refresh_interval is less than lifetime
+ # then the ReactiveCachingWorker will re-query prometheus for this
+ # PromQL query even though it's (probably) already been picked up by
+ # the frontend
+ # refresh_interval should be set less than lifetime only if this data
+ # is expected to change *and* be fetched again by the frontend
+ self.reactive_cache_refresh_interval = 90.seconds
self.reactive_cache_lifetime = 1.minute
self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
diff --git a/app/workers/concerns/cluster_queue.rb b/app/workers/concerns/cluster_queue.rb
index 180b86b0124..60ba8785347 100644
--- a/app/workers/concerns/cluster_queue.rb
+++ b/app/workers/concerns/cluster_queue.rb
@@ -8,6 +8,6 @@ module ClusterQueue
included do
queue_namespace :gcp_cluster
- feature_category :kubernetes_configuration
+ feature_category :kubernetes_management
end
end
diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb
index 553fd359baf..fc751f8b612 100644
--- a/app/workers/group_destroy_worker.rb
+++ b/app/workers/group_destroy_worker.rb
@@ -4,7 +4,7 @@ class GroupDestroyWorker
include ApplicationWorker
include ExceptionBacktrace
- feature_category :groups
+ feature_category :subgroups
def perform(group_id, user_id)
begin
diff --git a/changelogs/unreleased/194144-speed-up-url-helpers.yml b/changelogs/unreleased/194144-speed-up-url-helpers.yml
new file mode 100644
index 00000000000..271dacefe35
--- /dev/null
+++ b/changelogs/unreleased/194144-speed-up-url-helpers.yml
@@ -0,0 +1,5 @@
+---
+title: Improve link generation performance
+merge_request: 22426
+author:
+type: performance
diff --git a/changelogs/unreleased/22465-rack-attack-authenticate-job-token-requests.yml b/changelogs/unreleased/22465-rack-attack-authenticate-job-token-requests.yml
new file mode 100644
index 00000000000..19cc7b83385
--- /dev/null
+++ b/changelogs/unreleased/22465-rack-attack-authenticate-job-token-requests.yml
@@ -0,0 +1,5 @@
+---
+title: Authenticate API requests with job tokens for Rack::Attack
+merge_request: 21412
+author:
+type: fixed
diff --git a/changelogs/unreleased/27427-boards-sidebar-icon.yml b/changelogs/unreleased/27427-boards-sidebar-icon.yml
new file mode 100644
index 00000000000..de8a5966d77
--- /dev/null
+++ b/changelogs/unreleased/27427-boards-sidebar-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Increase size of issue boards sidebar collapse button
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/39119-actioncontroller-urlgenerationerror-no-route-matches-action-evidenc.yml b/changelogs/unreleased/39119-actioncontroller-urlgenerationerror-no-route-matches-action-evidenc.yml
new file mode 100644
index 00000000000..25d8ef6651b
--- /dev/null
+++ b/changelogs/unreleased/39119-actioncontroller-urlgenerationerror-no-route-matches-action-evidenc.yml
@@ -0,0 +1,5 @@
+---
+title: Fix releases page when tag contains a slash
+merge_request: 22527
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-disable-prom-metrics-on-failure.yml b/changelogs/unreleased/sh-disable-prom-metrics-on-failure.yml
new file mode 100644
index 00000000000..d9db2847d2e
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-prom-metrics-on-failure.yml
@@ -0,0 +1,5 @@
+---
+title: Disable Prometheus metrics if initialization fails
+merge_request: 22355
+author:
+type: fixed
diff --git a/changelogs/unreleased/sidekiq-cluster-terminate-hung-workers.yml b/changelogs/unreleased/sidekiq-cluster-terminate-hung-workers.yml
new file mode 100644
index 00000000000..294c739839f
--- /dev/null
+++ b/changelogs/unreleased/sidekiq-cluster-terminate-hung-workers.yml
@@ -0,0 +1,5 @@
+---
+title: When sidekiq-cluster is asked to shutdown, actively terminate any sidekiq processes that don't finish cleanly in short order
+merge_request: 21796
+author:
+type: fixed
diff --git a/changelogs/unreleased/smaller-prom-redis-keys.yml b/changelogs/unreleased/smaller-prom-redis-keys.yml
new file mode 100644
index 00000000000..a1a9e40d0c3
--- /dev/null
+++ b/changelogs/unreleased/smaller-prom-redis-keys.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce redis key size for the Prometheus proxy and the amount of queries by half
+merge_request: 20006
+author:
+type: performance
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
index 59752a81f60..50776d92a30 100644
--- a/config/feature_categories.yml
+++ b/config/feature_categories.yml
@@ -8,10 +8,10 @@
#
---
- accessibility_testing
-- account-management
-- agile_portfolio_management
- analysis
-- audit_management
+- attack_emulation
+- audit_events
+- audit_reports
- authentication_and_authorization
- auto_devops
- backup_restore
@@ -25,25 +25,29 @@
- code_quality
- code_review
- collection
+- compliance_controls
+- compliance_frameworks
- container_network_security
- container_registry
- container_scanning
- continuous_delivery
- continuous_integration
- data_loss_prevention
+- ddos_protection
- dependency_proxy
- dependency_scanning
- design_management
- devops_score
- disaster_recovery
- dynamic_application_security_testing
+- epics
- error_tracking
- feature_flags
- fuzzing
- geo_replication
- gitaly
+- gitlab_handbook
- gitter
-- groups
- helm_chart_registry
- importers
- incident_management
@@ -55,12 +59,13 @@
- internationalization
- issue_tracking
- kanban_boards
-- kubernetes_configuration
+- kubernetes_management
- language_specific
- license_compliance
- live_coding
- load_testing
- logging
+- malware_scanning
- metrics
- omnibus_package
- package_registry
@@ -69,7 +74,9 @@
- release_governance
- release_orchestration
- requirements_management
+- responsible_disclosure
- review_apps
+- roadmaps
- runbooks
- runner
- runtime_application_self_protection
@@ -82,8 +89,9 @@
- snippets
- source_code_management
- static_application_security_testing
+- static_site_editor
- status_page
-- storage_security
+- subgroups
- synthetic_monitoring
- system_testing
- templates
@@ -100,4 +108,3 @@
- web_ide
- web_performance
- wiki
-- workflow_policies
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 22bb5f1764d..aa2601ea650 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -43,6 +43,9 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
defined?(::Prometheus::Client.reinitialize_on_pid_change) && Prometheus::Client.reinitialize_on_pid_change
Gitlab::Metrics::Samplers::RubySampler.initialize_instance(Settings.monitoring.ruby_sampler_interval).start
+ rescue IOError => e
+ Gitlab::ErrorTracking.track_exception(e)
+ Gitlab::Metrics.error_detected!
end
Gitlab::Cluster::LifecycleEvents.on_master_start do
@@ -55,6 +58,9 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
end
Gitlab::Metrics::RequestsRackMiddleware.initialize_http_request_duration_seconds
+ rescue IOError => e
+ Gitlab::ErrorTracking.track_exception(e)
+ Gitlab::Metrics.error_detected!
end
end
diff --git a/config/initializers/action_dispatch_journey_formatter.rb b/config/initializers/action_dispatch_journey_formatter.rb
new file mode 100644
index 00000000000..93cf407c73c
--- /dev/null
+++ b/config/initializers/action_dispatch_journey_formatter.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# TODO: Eliminate this file when https://github.com/rails/rails/pull/38184 is released.
+# Cleanup issue: https://gitlab.com/gitlab-org/gitlab/issues/195841
+ActionDispatch::Journey::Formatter.prepend(Gitlab::Patch::ActionDispatchJourneyFormatter)
+
+module ActionDispatch
+ module Journey
+ module Path
+ class Pattern
+ def requirements_for_missing_keys_check
+ @requirements_for_missing_keys_check ||= requirements.each_with_object({}) do |(key, regex), hash|
+ hash[key] = /\A#{regex}\Z/
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md
index e573699e856..6ef1a3ec607 100644
--- a/doc/administration/packages/container_registry.md
+++ b/doc/administration/packages/container_registry.md
@@ -98,7 +98,7 @@ There are two ways you can configure the Registry's external domain. Either:
for that domain.
Since the container Registry requires a TLS certificate, in the end it all boils
-down to how easy or pricey is to get a new one.
+down to how easy or pricey it is to get a new one.
Please take this into consideration before configuring the Container Registry
for the first time.
diff --git a/doc/security/asset_proxy.md b/doc/security/asset_proxy.md
index 6e615028e8b..5522a41ff01 100644
--- a/doc/security/asset_proxy.md
+++ b/doc/security/asset_proxy.md
@@ -30,22 +30,22 @@ To install a Camo server as an asset proxy:
1. Make sure your instance of GitLab is running, and that you have created a private API token.
Using the API, configure the asset proxy settings on your GitLab instance. For example:
- ```sh
- curl --request "PUT" "https://gitlab.example.com/api/v4/application/settings?\
- asset_proxy_enabled=true&\
- asset_proxy_url=https://proxy.gitlab.example.com&\
- asset_proxy_secret_key=<somekey>" \
- --header 'PRIVATE-TOKEN: <my_private_token>'
- ```
-
- The following settings are supported:
-
- | Attribute | Description |
- |:-------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|
- | `asset_proxy_enabled` | Enable proxying of assets. If enabled, requires: `asset_proxy_url`). |
- | `asset_proxy_secret_key` | Shared secret with the asset proxy server. |
- | `asset_proxy_url` | URL of the asset proxy server. |
- | `asset_proxy_whitelist` | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. |
+ ```sh
+ curl --request "PUT" "https://gitlab.example.com/api/v4/application/settings?\
+ asset_proxy_enabled=true&\
+ asset_proxy_url=https://proxy.gitlab.example.com&\
+ asset_proxy_secret_key=<somekey>" \
+ --header 'PRIVATE-TOKEN: <my_private_token>'
+ ```
+
+ The following settings are supported:
+
+ | Attribute | Description |
+ |:-------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|
+ | `asset_proxy_enabled` | Enable proxying of assets. If enabled, requires: `asset_proxy_url`). |
+ | `asset_proxy_secret_key` | Shared secret with the asset proxy server. |
+ | `asset_proxy_url` | URL of the asset proxy server. |
+ | `asset_proxy_whitelist` | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. |
1. Restart the server for the changes to take effect. Each time you change any values for the asset
proxy, you need to restart the server.
diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb
index 33cbb070c2f..fe61d9fe8ca 100644
--- a/lib/gitlab/auth/auth_finders.rb
+++ b/lib/gitlab/auth/auth_finders.rb
@@ -25,9 +25,10 @@ module Gitlab
PRIVATE_TOKEN_HEADER = 'HTTP_PRIVATE_TOKEN'
PRIVATE_TOKEN_PARAM = :private_token
- JOB_TOKEN_HEADER = "HTTP_JOB_TOKEN".freeze
+ JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
JOB_TOKEN_PARAM = :job_token
RUNNER_TOKEN_PARAM = :token
+ RUNNER_JOB_TOKEN_PARAM = :token
# Check the Rails session for valid authentication details
def find_user_from_warden
@@ -57,11 +58,13 @@ module Gitlab
def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed]
- token = (params[JOB_TOKEN_PARAM] || env[JOB_TOKEN_HEADER]).to_s
- return unless token.present?
+ token = current_request.params[JOB_TOKEN_PARAM].presence ||
+ current_request.params[RUNNER_JOB_TOKEN_PARAM].presence ||
+ current_request.env[JOB_TOKEN_HEADER].presence
+ return unless token
job = ::Ci::Build.find_by_token(token)
- raise ::Gitlab::Auth::UnauthorizedError unless job
+ raise UnauthorizedError unless job
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
diff --git a/lib/gitlab/auth/request_authenticator.rb b/lib/gitlab/auth/request_authenticator.rb
index 34ccff588f4..c6216fa9cad 100644
--- a/lib/gitlab/auth/request_authenticator.rb
+++ b/lib/gitlab/auth/request_authenticator.rb
@@ -33,7 +33,8 @@ module Gitlab
find_user_from_web_access_token(request_format) ||
find_user_from_feed_token(request_format) ||
find_user_from_static_object_token(request_format) ||
- find_user_from_basic_auth_job
+ find_user_from_basic_auth_job ||
+ find_user_from_job_token
rescue Gitlab::Auth::AuthenticationError
nil
end
@@ -45,6 +46,14 @@ module Gitlab
rescue Gitlab::Auth::AuthenticationError
false
end
+
+ private
+
+ def route_authentication_setting
+ @route_authentication_setting ||= {
+ job_token_allowed: api_request?
+ }
+ end
end
end
end
diff --git a/lib/gitlab/ci/snippets/review_app_default.yml b/lib/gitlab/ci/snippets/review_app_default.yml
new file mode 100644
index 00000000000..b6db08ef537
--- /dev/null
+++ b/lib/gitlab/ci/snippets/review_app_default.yml
@@ -0,0 +1,9 @@
+deploy_review:
+ stage: deploy
+ script:
+ - echo "Deploy a review app"
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ url: https://$CI_ENVIRONMENT_SLUG.example.com
+ only:
+ - branches
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 61ed20ad623..d759ae24051 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -5,8 +5,14 @@ module Gitlab
include Gitlab::Metrics::InfluxDb
include Gitlab::Metrics::Prometheus
+ @error = false
+
def self.enabled?
influx_metrics_enabled? || prometheus_metrics_enabled?
end
+
+ def self.error?
+ @error
+ end
end
end
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index cab1edab48f..f7480a8789e 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -61,6 +61,14 @@ module Gitlab
safe_provide_metric(:histogram, name, docstring, base_labels, buckets)
end
+ def error_detected!
+ clear_memoization(:prometheus_metrics_enabled)
+
+ PROVIDER_MUTEX.synchronize do
+ @error = true
+ end
+ end
+
private
def safe_provide_metric(method, name, *args)
@@ -81,7 +89,7 @@ module Gitlab
end
def prometheus_metrics_enabled_unmemoized
- metrics_folder_present? && Gitlab::CurrentSettings.prometheus_metrics_enabled || false
+ !error? && metrics_folder_present? && Gitlab::CurrentSettings.prometheus_metrics_enabled || false
end
end
end
diff --git a/lib/gitlab/patch/action_dispatch_journey_formatter.rb b/lib/gitlab/patch/action_dispatch_journey_formatter.rb
new file mode 100644
index 00000000000..2d3b7bb9923
--- /dev/null
+++ b/lib/gitlab/patch/action_dispatch_journey_formatter.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Patch
+ module ActionDispatchJourneyFormatter
+ def self.prepended(mod)
+ mod.alias_method(:old_missing_keys, :missing_keys)
+ mod.remove_method(:missing_keys)
+ end
+
+ private
+
+ def missing_keys(route, parts)
+ missing_keys = nil
+ tests = route.path.requirements_for_missing_keys_check
+ route.required_parts.each do |key|
+ case tests[key]
+ when nil
+ unless parts[key]
+ missing_keys ||= []
+ missing_keys << key
+ end
+ else
+ unless tests[key].match?(parts[key])
+ missing_keys ||= []
+ missing_keys << key
+ end
+ end
+ end
+ missing_keys
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ef8e20ec830..8bee019d867 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1350,6 +1350,9 @@ msgstr ""
msgid "AdminUsers|External"
msgstr ""
+msgid "AdminUsers|Is using seat"
+msgstr ""
+
msgid "AdminUsers|It's you!"
msgstr ""
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 2b923df40c5..9143db16b87 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -318,6 +318,7 @@ describe 'Issue Boards', :js do
wait_for_requests
click_link bug.title
+ within('.dropdown-menu-labels') { expect(page).to have_selector('.is-active', count: 3) }
click_link regression.title
wait_for_requests
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
index a9b8ff9dc4d..4507d90576b 100644
--- a/spec/features/projects/releases/user_views_releases_spec.rb
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -57,4 +57,14 @@ describe 'User views releases', :js do
expect(page).to have_content('Upcoming Release')
end
end
+
+ context 'with a tag containing a slash' do
+ it 'sees the release' do
+ release = create :release, :with_evidence, project: project, tag: 'debian/2.4.0-1'
+ visit project_releases_path(project)
+
+ expect(page).to have_content(release.name)
+ expect(page).to have_content(release.tag)
+ end
+ end
end
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index 82ff8e7f76c..bffaaef4ed4 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -446,6 +446,93 @@ describe Gitlab::Auth::AuthFinders do
end
end
+ describe '#find_user_from_job_token' do
+ let(:job) { create(:ci_build, user: user) }
+ let(:route_authentication_setting) { { job_token_allowed: true } }
+
+ subject { find_user_from_job_token }
+
+ context 'when the job token is in the headers' do
+ it 'returns the user if valid job token' do
+ env[described_class::JOB_TOKEN_HEADER] = job.token
+
+ is_expected.to eq(user)
+ expect(@current_authenticated_job).to eq(job)
+ end
+
+ it 'returns nil without job token' do
+ env[described_class::JOB_TOKEN_HEADER] = ''
+
+ is_expected.to be_nil
+ end
+
+ it 'returns exception if invalid job token' do
+ env[described_class::JOB_TOKEN_HEADER] = 'invalid token'
+
+ expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+
+ context 'when route is not allowed to be authenticated' do
+ let(:route_authentication_setting) { { job_token_allowed: false } }
+
+ it 'sets current_user to nil' do
+ env[described_class::JOB_TOKEN_HEADER] = job.token
+
+ allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
+
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ context 'when the job token is in the params' do
+ shared_examples 'job token params' do |token_key_name|
+ before do
+ set_param(token_key_name, token)
+ end
+
+ context 'with valid job token' do
+ let(:token) { job.token }
+
+ it 'returns the user' do
+ is_expected.to eq(user)
+ expect(@current_authenticated_job).to eq(job)
+ end
+ end
+
+ context 'with empty job token' do
+ let(:token) { '' }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+
+ context 'with invalid job token' do
+ let(:token) { 'invalid token' }
+
+ it 'returns exception' do
+ expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+ end
+
+ context 'when route is not allowed to be authenticated' do
+ let(:route_authentication_setting) { { job_token_allowed: false } }
+ let(:token) { job.token }
+
+ it 'sets current_user to nil' do
+ allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
+
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ it_behaves_like 'job token params', described_class::JOB_TOKEN_PARAM
+ it_behaves_like 'job token params', described_class::RUNNER_JOB_TOKEN_PARAM
+ end
+ end
+
describe '#find_runner_from_token' do
let(:runner) { create(:ci_runner) }
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index 4dbcd0df302..87c96803c3a 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -42,6 +42,8 @@ describe Gitlab::Auth::RequestAuthenticator do
describe '#find_sessionless_user' do
let!(:access_token_user) { build(:user) }
let!(:feed_token_user) { build(:user) }
+ let!(:static_object_token_user) { build(:user) }
+ let!(:job_token_user) { build(:user) }
it 'returns access_token user first' do
allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_return(access_token_user)
@@ -56,6 +58,22 @@ describe Gitlab::Auth::RequestAuthenticator do
expect(subject.find_sessionless_user([:api])).to eq feed_token_user
end
+ it 'returns static_object_token user if no feed_token user found' do
+ allow_any_instance_of(described_class)
+ .to receive(:find_user_from_static_object_token)
+ .and_return(static_object_token_user)
+
+ expect(subject.find_sessionless_user([:api])).to eq static_object_token_user
+ end
+
+ it 'returns job_token user if no static_object_token user found' do
+ allow_any_instance_of(described_class)
+ .to receive(:find_user_from_job_token)
+ .and_return(job_token_user)
+
+ expect(subject.find_sessionless_user([:api])).to eq job_token_user
+ end
+
it 'returns nil if no user found' do
expect(subject.find_sessionless_user([:api])).to be_blank
end
@@ -67,6 +85,39 @@ describe Gitlab::Auth::RequestAuthenticator do
end
end
+ describe '#find_user_from_job_token' do
+ let!(:user) { build(:user) }
+ let!(:job) { build(:ci_build, user: user) }
+
+ before do
+ env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'token'
+ end
+
+ context 'with API requests' do
+ before do
+ env['SCRIPT_NAME'] = '/api/endpoint'
+ end
+
+ it 'tries to find the user' do
+ expect(::Ci::Build).to receive(:find_by_token).and_return(job)
+
+ expect(subject.find_sessionless_user([:api])).to eq user
+ end
+ end
+
+ context 'without API requests' do
+ before do
+ env['SCRIPT_NAME'] = '/web/endpoint'
+ end
+
+ it 'does not search for job users' do
+ expect(::Ci::Build).not_to receive(:find_by_token)
+
+ expect(subject.find_sessionless_user([:api])).to be_nil
+ end
+ end
+ end
+
describe '#runner' do
let!(:runner) { build(:ci_runner) }
diff --git a/spec/lib/gitlab/metrics/prometheus_spec.rb b/spec/lib/gitlab/metrics/prometheus_spec.rb
index b37624982e2..d4aa96a5b20 100644
--- a/spec/lib/gitlab/metrics/prometheus_spec.rb
+++ b/spec/lib/gitlab/metrics/prometheus_spec.rb
@@ -17,4 +17,21 @@ describe Gitlab::Metrics::Prometheus, :prometheus do
expect(all_metrics.registry.metrics.count).to eq(0)
end
end
+
+ describe '#error_detected!' do
+ before do
+ allow(all_metrics).to receive(:metrics_folder_present?).and_return(true)
+ stub_application_setting(prometheus_metrics_enabled: true)
+ end
+
+ it 'disables Prometheus metrics' do
+ expect(all_metrics.error?).to be_falsey
+ expect(all_metrics.prometheus_metrics_enabled?).to be_truthy
+
+ all_metrics.error_detected!
+
+ expect(all_metrics.prometheus_metrics_enabled?).to be_falsey
+ expect(all_metrics.error?).to be_truthy
+ end
+ end
end
diff --git a/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb b/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb
new file mode 100644
index 00000000000..5f0e1f40231
--- /dev/null
+++ b/spec/lib/gitlab/patch/action_dispatch_journey_formatter_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Patch::ActionDispatchJourneyFormatter do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ let(:url) { Gitlab::Routing.url_helpers.project_pipeline_url(project, pipeline) }
+ let(:expected_path) { "#{project.full_path}/pipelines/#{pipeline.id}" }
+
+ context 'custom implementation of #missing_keys' do
+ before do
+ expect_any_instance_of(Gitlab::Patch::ActionDispatchJourneyFormatter).to receive(:missing_keys)
+ end
+
+ it 'generates correct url' do
+ expect(url).to end_with(expected_path)
+ end
+ end
+
+ context 'original implementation of #missing_keys' do
+ before do
+ allow_any_instance_of(Gitlab::Patch::ActionDispatchJourneyFormatter).to receive(:missing_keys) do |instance, route, parts|
+ instance.send(:old_missing_keys, route, parts) # test the old implementation
+ end
+ end
+
+ it 'generates correct url' do
+ expect(url).to end_with(expected_path)
+ end
+ end
+end
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 318024bacd6..09ca106c54c 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -4,11 +4,10 @@ require 'spec_helper'
describe ProjectPresenter do
let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:presenter) { described_class.new(project, current_user: user) }
describe '#license_short_name' do
- let(:project) { create(:project) }
- let(:presenter) { described_class.new(project, current_user: user) }
-
context 'when project.repository has a license_key' do
it 'returns the nickname of the license if present' do
allow(project.repository).to receive(:license_key).and_return('agpl-3.0')
@@ -33,8 +32,6 @@ describe ProjectPresenter do
end
describe '#default_view' do
- let(:presenter) { described_class.new(project, current_user: user) }
-
context 'user not signed in' do
let(:user) { nil }
@@ -125,7 +122,6 @@ describe ProjectPresenter do
describe '#can_current_user_push_code?' do
let(:project) { create(:project, :repository) }
- let(:presenter) { described_class.new(project, current_user: user) }
context 'empty repo' do
let(:project) { create(:project) }
@@ -163,7 +159,6 @@ describe ProjectPresenter do
context 'statistics anchors (empty repo)' do
let(:project) { create(:project, :empty_repo) }
- let(:presenter) { described_class.new(project, current_user: user) }
describe '#files_anchor_data' do
it 'returns files data' do
@@ -200,7 +195,6 @@ describe ProjectPresenter do
context 'statistics anchors' do
let(:project) { create(:project, :repository) }
- let(:presenter) { described_class.new(project, current_user: user) }
describe '#files_anchor_data' do
it 'returns files data' do
@@ -416,7 +410,6 @@ describe ProjectPresenter do
describe '#statistics_buttons' do
let(:project) { build(:project) }
- let(:presenter) { described_class.new(project, current_user: user) }
it 'orders the items correctly' do
allow(project.repository).to receive(:readme).and_return(double(name: 'readme'))
@@ -435,8 +428,6 @@ describe ProjectPresenter do
end
describe '#repo_statistics_buttons' do
- let(:presenter) { described_class.new(project, current_user: user) }
-
subject(:empty_repo_statistics_buttons) { presenter.empty_repo_statistics_buttons }
before do
@@ -485,4 +476,73 @@ describe ProjectPresenter do
end
end
end
+
+ describe '#can_setup_review_app?' do
+ subject { presenter.can_setup_review_app? }
+
+ context 'when the ci/cd file is missing' do
+ before do
+ allow(presenter).to receive(:cicd_missing?).and_return(true)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the ci/cd file is not missing' do
+ before do
+ allow(presenter).to receive(:cicd_missing?).and_return(false)
+ end
+
+ context 'and the user can create a cluster' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :create_cluster, project).and_return(true)
+ end
+
+ context 'and there is no cluster associated to this project' do
+ let(:project) { create(:project, clusters: []) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'and there is already a cluster associated to this project' do
+ let(:project) { create(:project, clusters: [build(:cluster, :providing_by_gcp)]) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when a group cluster is instantiated' do
+ let_it_be(:cluster) { create(:cluster, :group) }
+ let_it_be(:group) { cluster.group }
+
+ context 'and the project belongs to this group' do
+ let!(:project) { create(:project, group: group) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'and the project does not belong to this group' do
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'and there is already an instance cluster' do
+ it 'is false' do
+ create(:cluster, :instance)
+
+ is_expected.to be_falsey
+ end
+ end
+ end
+
+ context 'and the user cannot create a cluster' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :create_cluster, project).and_return(false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
end
diff --git a/spec/presenters/release_presenter_spec.rb b/spec/presenters/release_presenter_spec.rb
index 2f978b0a036..4c6142f2edb 100644
--- a/spec/presenters/release_presenter_spec.rb
+++ b/spec/presenters/release_presenter_spec.rb
@@ -96,4 +96,28 @@ describe ReleasePresenter do
it { is_expected.to be_nil }
end
end
+
+ describe '#evidence_file_path' do
+ subject { presenter.evidence_file_path }
+
+ context 'without evidence' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with evidence' do
+ let(:release) { create :release, :with_evidence, project: project }
+
+ specify do
+ is_expected.to match /#{evidence_project_release_url(project, release.tag, format: :json)}/
+ end
+ end
+
+ context 'when a tag contains a slash' do
+ let(:release) { create :release, :with_evidence, project: project, tag: 'debian/2.4.0-1' }
+
+ specify do
+ is_expected.to match /#{evidence_project_release_url(project, CGI.escape(release.tag), format: :json)}/
+ end
+ end
+ end
end
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 233f0497b7f..d3fe4c22b1d 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -115,6 +115,16 @@ describe API::Releases do
end
end
+ context 'when tag contains a slash' do
+ let!(:release) { create(:release, project: project, tag: 'debian/2.4.0-1', description: "debian/2.4.0-1") }
+
+ it 'returns 200 HTTP status' do
+ get api("/projects/#{project.id}/releases", maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
context 'when user is a guest' do
let!(:release) do
create(:release,
diff --git a/spec/serializers/review_app_setup_entity_spec.rb b/spec/serializers/review_app_setup_entity_spec.rb
new file mode 100644
index 00000000000..19949fa9282
--- /dev/null
+++ b/spec/serializers/review_app_setup_entity_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ReviewAppSetupEntity do
+ let_it_be(:user) { create(:admin) }
+ let(:project) { create(:project) }
+ let(:presenter) { ProjectPresenter.new(project, current_user: user) }
+ let(:entity) { described_class.new(presenter) }
+ let(:request) { double('request') }
+
+ before do
+ allow(request).to receive(:current_user).and_return(user)
+ allow(request).to receive(:project).and_return(project)
+ end
+
+ subject { entity.as_json }
+
+ describe '#as_json' do
+ it 'contains can_setup_review_app' do
+ expect(subject).to include(:can_setup_review_app)
+ end
+
+ context 'when the user can setup a review app' do
+ before do
+ allow(presenter).to receive(:can_setup_review_app?).and_return(true)
+ end
+
+ it 'contains relevant fields' do
+ expect(subject.keys).to include(:all_clusters_empty, :review_snippet)
+ end
+
+ it 'exposes the relevant review snippet' do
+ review_app_snippet = YAML.safe_load(File.read(Rails.root.join('lib', 'gitlab', 'ci', 'snippets', 'review_app_default.yml'))).to_s
+
+ expect(subject[:review_snippet]).to eq(review_app_snippet)
+ end
+
+ it 'exposes whether the project has associated clusters' do
+ expect(subject[:all_clusters_empty]).to be_truthy
+ end
+ end
+
+ context 'when the user cannot setup a review app' do
+ before do
+ allow(presenter).to receive(:can_setup_review_app?).and_return(false)
+ end
+
+ it 'does not expose certain fields' do
+ expect(subject.keys).not_to include(:all_clusters_empty, :review_snippet)
+ end
+ end
+ end
+end
diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb
index 03bda94e9c6..0ce08252425 100644
--- a/spec/services/prometheus/proxy_service_spec.rb
+++ b/spec/services/prometheus/proxy_service_spec.rb
@@ -8,6 +8,12 @@ describe Prometheus::ProxyService do
set(:project) { create(:project) }
set(:environment) { create(:environment, project: project) }
+ describe 'configuration' do
+ it 'ReactiveCaching refresh is not needed' do
+ expect(described_class.reactive_cache_refresh_interval).to be > described_class.reactive_cache_lifetime
+ end
+ end
+
describe '#initialize' do
let(:params) { ActionController::Parameters.new(query: '1').permit! }