Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
43c3400c67
commit
c3524d16b2
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GraphqlController < ApplicationController
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
# Unauthenticated users have access to the API for public data
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
|
@ -35,6 +37,7 @@ class GraphqlController < ApplicationController
|
|||
# callback execution order here
|
||||
around_action :sessionless_bypass_admin_mode!, if: :sessionless_user?
|
||||
|
||||
# The default feature category is overridden to read from request
|
||||
feature_category :not_owned
|
||||
|
||||
def execute
|
||||
|
@ -64,6 +67,11 @@ class GraphqlController < ApplicationController
|
|||
render_error(exception.message, status: :unprocessable_entity)
|
||||
end
|
||||
|
||||
override :feature_category
|
||||
def feature_category
|
||||
::Gitlab::FeatureCategories.default.from_request(request) || super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def disallow_mutations_for_get
|
||||
|
|
|
@ -18,11 +18,8 @@ module Resolvers
|
|||
|
||||
filter_params = filters.merge(board_id: list.board.id, id: list.id)
|
||||
service = ::Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params)
|
||||
pagination_connections = Gitlab::Graphql::Pagination::Keyset::Connection.new(service.execute)
|
||||
|
||||
::Boards::Issues::ListService.initialize_relative_positions(list.board, current_user, pagination_connections.items)
|
||||
|
||||
pagination_connections
|
||||
service.execute
|
||||
end
|
||||
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/235681
|
||||
|
|
|
@ -21,6 +21,7 @@ module Types
|
|||
@feature_flag = kwargs[:feature_flag]
|
||||
kwargs = check_feature_flag(kwargs)
|
||||
@deprecation = gitlab_deprecation(kwargs)
|
||||
after_connection_extensions = kwargs.delete(:late_extensions) || []
|
||||
|
||||
super(**kwargs, &block)
|
||||
|
||||
|
@ -28,6 +29,8 @@ module Types
|
|||
extension ::Gitlab::Graphql::CallsGitaly::FieldExtension if Gitlab.dev_or_test_env?
|
||||
extension ::Gitlab::Graphql::Present::FieldExtension
|
||||
extension ::Gitlab::Graphql::Authorize::ConnectionFilterExtension
|
||||
|
||||
after_connection_extensions.each { extension _1 } if after_connection_extensions.any?
|
||||
end
|
||||
|
||||
def may_call_gitaly?
|
||||
|
|
|
@ -27,6 +27,7 @@ module Types
|
|||
|
||||
field :issues, ::Types::IssueType.connection_type, null: true,
|
||||
description: 'Board issues.',
|
||||
late_extensions: [Gitlab::Graphql::Board::IssuesConnectionExtension],
|
||||
resolver: ::Resolvers::BoardListIssuesResolver
|
||||
|
||||
def issues_count
|
||||
|
|
|
@ -3,7 +3,13 @@
|
|||
class UserHighestRole < ApplicationRecord
|
||||
belongs_to :user, optional: false
|
||||
|
||||
validates :highest_access_level, allow_nil: true, inclusion: { in: Gitlab::Access.all_values }
|
||||
validates :highest_access_level, allow_nil: true, inclusion: { in: ->(_) { self.allowed_values } }
|
||||
|
||||
scope :with_highest_access_level, -> (highest_access_level) { where(highest_access_level: highest_access_level) }
|
||||
|
||||
def self.allowed_values
|
||||
Gitlab::Access.all_values
|
||||
end
|
||||
end
|
||||
|
||||
UserHighestRole.prepend_mod
|
||||
|
|
|
@ -103,7 +103,6 @@ module Ci
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def each_build(params, &blk)
|
||||
::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339429') do
|
||||
queue = ::Ci::Queue::BuildQueueService.new(runner)
|
||||
|
||||
builds = begin
|
||||
|
@ -139,7 +138,6 @@ module Ci
|
|||
|
||||
build_ids.each { |build_id| yield Ci::Build.find(build_id) }
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def retrieve_queue(queue_query_proc)
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/297382
|
|||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::runner
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
Gitlab::Database::Partitioning.register_models([
|
||||
AuditEvent,
|
||||
WebHookLog,
|
||||
LooseForeignKeys::DeletedRecord
|
||||
WebHookLog
|
||||
])
|
||||
|
||||
if Gitlab.ee?
|
||||
|
|
|
@ -65,9 +65,10 @@ because the expansion is done in GitLab before any runner gets the job.
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48627) in GitLab 13.10. [Deployed behind the `variable_inside_variable` feature flag](../../user/feature_flags.md), disabled by default.
|
||||
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/297382) in GitLab 14.3.
|
||||
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/297382) in GitLab 14.4.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is disabled. To enable the feature per project or for your entire instance, ask an administrator to [enable the `variable_inside_variable` flag](../../administration/feature_flags.md).
|
||||
On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `variable_inside_variable`. On GitLab.com, this feature is available.
|
||||
|
||||
GitLab expands job variable values recursively before sending them to the runner. For example:
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ Consider the following scope:
|
|||
scope = Issue.where(project_id: 10).order(Gitlab::Database.nulls_last_order('relative_position', 'DESC'))
|
||||
# SELECT "issues".* FROM "issues" WHERE "issues"."project_id" = 10 ORDER BY relative_position DESC NULLS LAST
|
||||
|
||||
scope.keyset_paginate # raises: Gitlab::Pagination::Keyset::Paginator::UnsupportedScopeOrder: The order on the scope does not support keyset pagination
|
||||
scope.keyset_paginate # raises: Gitlab::Pagination::Keyset::UnsupportedScopeOrder: The order on the scope does not support keyset pagination
|
||||
```
|
||||
|
||||
The `keyset_paginate` method raises an error because the order value on the query is a custom SQL string and not an [`Arel`](https://www.rubydoc.info/gems/arel) AST node. The keyset library cannot automatically infer configuration values from these kinds of queries.
|
||||
|
|
|
@ -425,6 +425,92 @@ to fix the cross-join. If the cross-join is being used in a migration, we do not
|
|||
need to fix the code. See <https://gitlab.com/gitlab-org/gitlab/-/issues/340017>
|
||||
for more details.
|
||||
|
||||
### Removing cross-database transactions
|
||||
|
||||
When dealing with multiple databases, it's important to pay close attention to data modification
|
||||
that affects more than one database.
|
||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339811) GitLab 14.4, an automated check
|
||||
prevents cross-database modifications.
|
||||
|
||||
When at least two different databases are modified during a transaction initiated on any database
|
||||
server, the application triggers a cross-database modification error (only in test environment).
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
# Open transaction on Main DB
|
||||
ApplicationRecord.transaction do
|
||||
ci_build.update!(updated_at: Time.current) # UPDATE on CI DB
|
||||
ci_build.project.update!(updated_at: Time.current) # UPDATE on Main DB
|
||||
end
|
||||
# raises error: Cross-database data modification of 'main, ci' were detected within
|
||||
# a transaction modifying the 'ci_build, projects' tables
|
||||
```
|
||||
|
||||
The code example above updates the timestamp for two records within a transaction. With the
|
||||
ongoing work on the CI database decomposition, we cannot ensure the schematics of a database
|
||||
transaction.
|
||||
If the second update query fails, the first update query will not be
|
||||
rolled back because the `ci_build` record is located on a different database server. For
|
||||
more information, look at the
|
||||
[transaction guidelines](transaction_guidelines.md#dangerous-example-third-party-api-calls)
|
||||
page.
|
||||
|
||||
#### Fixing cross-database errors
|
||||
|
||||
##### Removing the transaction block
|
||||
|
||||
Without an open transaction, the cross-database modification check cannot raise an error.
|
||||
By making this change, we sacrifice consistency. In case of an application failure after the
|
||||
first `UPDATE` query, the second `UPDATE` query will never execute.
|
||||
|
||||
The same code without the `transaction` block:
|
||||
|
||||
```ruby
|
||||
ci_build.update!(updated_at: Time.current) # CI DB
|
||||
ci_build.project.update!(updated_at: Time.current) # Main DB
|
||||
```
|
||||
|
||||
##### Async processing
|
||||
|
||||
If we need more guarantee that an operation finishes the work consistently we can execute it
|
||||
within a background job. A background job is scheduled asynchronously and retried several times
|
||||
in case of an error. There is still a very small chance of introducing inconsistency.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
current_time = Time.current
|
||||
|
||||
MyAsyncConsistencyJob.perform_async(cu_build.id)
|
||||
|
||||
ci_build.update!(updated_at: current_time)
|
||||
ci_build.project.update!(updated_at: current_time)
|
||||
```
|
||||
|
||||
The `MyAsyncConsistencyJob` would also attempt to update the timestamp if they differ.
|
||||
|
||||
##### Aiming for perfect consistency
|
||||
|
||||
At this point, we don't have the tooling (we might not even need it) to ensure similar consistency
|
||||
characteristics as we had with one database. If you think that the code you're working on requires
|
||||
these properties, then you can disable the cross-database modification check by wrapping to
|
||||
offending database queries with a block and create a follow-up issue mentioning the sharding group
|
||||
(`gitlab-org/sharding-group`).
|
||||
|
||||
```ruby
|
||||
Gitlab::Database.allow_cross_joins_across_databases(url: 'gitlab issue URL') do
|
||||
ApplicationRecord.transaction do
|
||||
ci_build.update!(updated_at: Time.current) # UPDATE on CI DB
|
||||
ci_build.project.update!(updated_at: Time.current) # UPDATE on Main DB
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Don't hesitate to reach out to the
|
||||
[sharding group](https://about.gitlab.com/handbook/engineering/development/enablement/sharding)
|
||||
for advice.
|
||||
|
||||
## `config/database.yml`
|
||||
|
||||
GitLab will support running multiple databases in the future, for example to [separate tables for the continuous integration features](https://gitlab.com/groups/gitlab-org/-/epics/6167) from the main database. In order to prepare for this change, we [validate the structure of the configuration](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67877) in `database.yml` to ensure that only known databases are used.
|
||||
|
|
|
@ -6,11 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Award emoji **(FREE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/1825) in GitLab 8.2.
|
||||
> - GitLab 9.0 [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9570) the usage of native emoji if the platform
|
||||
> supports them and falls back to images or CSS sprites. This change greatly
|
||||
> improved award emoji performance overall.
|
||||
|
||||
When you're collaborating online, you get fewer opportunities for high-fives
|
||||
and thumbs-ups. Emoji can be awarded to [issues](project/issues/index.md), [merge requests](project/merge_requests/index.md),
|
||||
[snippets](snippets.md), and anywhere you can have a thread.
|
||||
|
@ -24,8 +19,6 @@ For information on the relevant API, see [Award Emoji API](../api/award_emoji.md
|
|||
|
||||
## Sort issues and merge requests on vote count
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2781) in GitLab 8.5.
|
||||
|
||||
You can quickly sort issues and merge requests by the number of votes they
|
||||
have received. The sort options can be found in the dropdown menu as "Most
|
||||
popular" and "Least popular".
|
||||
|
@ -38,8 +31,6 @@ downvotes.
|
|||
|
||||
## Award emoji for comments
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/4291) in GitLab 8.9.
|
||||
|
||||
Award emoji can also be applied to individual comments when you want to
|
||||
celebrate an accomplishment or agree with an opinion.
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ the related documentation.
|
|||
| [Max pipelines per schedule](../../administration/instance_limits.md#limit-the-number-of-pipelines-created-by-a-pipeline-schedule-per-day) | `24` for Free tier, `288` for all paid tiers | Unlimited |
|
||||
| [Scheduled Job Archival](../../user/admin_area/settings/continuous_integration.md#archive-jobs) | 3 months | Never |
|
||||
| Max test cases per [unit test report](../../ci/unit_test_reports.md) | `500_000` | Unlimited |
|
||||
| [Max registered runners](../../administration/instance_limits.md#number-of-registered-runners-per-scope) | `50` per-project and per-group for Free tier,<br/>`1_000` per-group for all paid tiers / `1_000` per-project for all paid tiers | `1_000` per-group / `1_000` per-project |
|
||||
| [Max registered runners](../../administration/instance_limits.md#number-of-registered-runners-per-scope) | Free tier: `50` per-group / `50` per-project <br/> All paid tiers: `1_000` per-group / `1_000` per-project | `1_000` per-group / `1_000` per-project |
|
||||
|
||||
## Account and limit settings
|
||||
|
||||
|
|
|
@ -156,8 +156,6 @@ to the project:
|
|||
|
||||
## Scoped labels **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9175) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.10.
|
||||
|
||||
Scoped labels allow teams to use the label feature to annotate issues, merge requests
|
||||
and epics with mutually exclusive labels. This can enable more complicated workflows
|
||||
by preventing certain labels from being used together.
|
||||
|
@ -241,14 +239,14 @@ to label notifications for the project only, or the whole group.
|
|||
|
||||
## Label priority
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/14189) in GitLab 8.9.
|
||||
> - Priority sorting is based on the highest priority label only. [This discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/14523) considers changing this.
|
||||
|
||||
Labels can have relative priorities, which are used in the **Label priority** and
|
||||
**Priority** sort orders of issues and merge request list pages. Prioritization
|
||||
for both group and project labels happens at the project level, and cannot be done
|
||||
from the group label list.
|
||||
|
||||
NOTE:
|
||||
Priority sorting is based on the highest priority label only. [This discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/14523) considers changing this.
|
||||
|
||||
From the project label list page, star a label to indicate that it has a priority.
|
||||
|
||||
![Labels prioritized](img/labels_prioritized_v13_5.png)
|
||||
|
|
|
@ -7,8 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# To-Do List **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2817) in GitLab 8.5.
|
||||
|
||||
Your *To-Do List* is a chronological list of items waiting for your input.
|
||||
The items are known as *to-do items*.
|
||||
|
||||
|
@ -67,8 +65,6 @@ You can manually add an item to your To-Do List.
|
|||
|
||||
## Create a to-do item by directly addressing someone
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/7926) in GitLab 9.0.
|
||||
|
||||
You can create a to-do item by directly addressing someone at the start of a line.
|
||||
For example, in the following comment:
|
||||
|
||||
|
|
|
@ -124,7 +124,10 @@ module Gitlab
|
|||
strong_memoize(:runner_project) do
|
||||
next unless runner&.project_type?
|
||||
|
||||
projects = runner.projects.take(2) # rubocop: disable CodeReuse/ActiveRecord
|
||||
projects = ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/342147') do
|
||||
runner.projects.take(2) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
projects.first if projects.one?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class FeatureCategories
|
||||
FEATURE_CATEGORY_DEFAULT = 'unknown'
|
||||
|
||||
attr_reader :categories
|
||||
|
||||
def self.default
|
||||
@default ||= self.load_from_yaml
|
||||
end
|
||||
|
||||
def self.load_from_yaml
|
||||
categories = YAML.load_file(Rails.root.join('config', 'feature_categories.yml'))
|
||||
|
||||
new(categories)
|
||||
end
|
||||
|
||||
def initialize(categories)
|
||||
@categories = categories.to_set
|
||||
end
|
||||
|
||||
# If valid, returns a feature category from the given request.
|
||||
def from_request(request)
|
||||
category = request.headers["HTTP_X_GITLAB_FEATURE_CATEGORY"].presence
|
||||
|
||||
return unless category && valid?(category)
|
||||
|
||||
return unless ::Gitlab::RequestForgeryProtection.verified?(request.env)
|
||||
|
||||
category
|
||||
end
|
||||
|
||||
def valid?(category)
|
||||
categories.include?(category.to_s)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
module Gitlab
|
||||
module Graphql
|
||||
module Board
|
||||
class IssuesConnectionExtension < GraphQL::Schema::Field::ConnectionExtension
|
||||
def after_resolve(value:, object:, context:, **rest)
|
||||
::Boards::Issues::ListService
|
||||
.initialize_relative_positions(object.list.board, context[:current_user], value.nodes)
|
||||
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
delegate :to_a, :size, :include?, :empty?, to: :nodes
|
||||
delegate :to_a, :size, :map, :include?, :empty?, to: :nodes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,6 @@ tree:
|
|||
- labels:
|
||||
- :priorities
|
||||
- boards:
|
||||
- :milestone
|
||||
- lists:
|
||||
- label:
|
||||
- :priorities
|
||||
|
|
|
@ -15,7 +15,7 @@ module Gitlab
|
|||
|
||||
HEALTH_ENDPOINT = %r{^/-/(liveness|readiness|health|metrics)/?$}.freeze
|
||||
|
||||
FEATURE_CATEGORY_DEFAULT = 'unknown'
|
||||
FEATURE_CATEGORY_DEFAULT = ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT
|
||||
ENDPOINT_MISSING = 'unknown'
|
||||
|
||||
# These were the top 5 categories at a point in time, chosen as a
|
||||
|
|
|
@ -57,10 +57,6 @@ module Gitlab
|
|||
|
||||
action = "#{controller.action_name}"
|
||||
|
||||
# Try to get the feature category, but don't fail when the controller is
|
||||
# not an ApplicationController.
|
||||
feature_category = controller.class.try(:feature_category_for_action, action).to_s
|
||||
|
||||
# Devise exposes a method called "request_format" that does the below.
|
||||
# However, this method is not available to all controllers (e.g. certain
|
||||
# Doorkeeper controllers). As such we use the underlying code directly.
|
||||
|
@ -91,9 +87,6 @@ module Gitlab
|
|||
if route
|
||||
path = endpoint_paths_cache[route.request_method][route.path]
|
||||
|
||||
grape_class = endpoint.options[:for]
|
||||
feature_category = grape_class.try(:feature_category_for_app, endpoint).to_s
|
||||
|
||||
{ controller: 'Grape', action: "#{route.request_method} #{path}", feature_category: feature_category }
|
||||
end
|
||||
end
|
||||
|
@ -109,6 +102,10 @@ module Gitlab
|
|||
def endpoint_instrumentable_path(raw_path)
|
||||
raw_path.sub('(.:format)', '').sub('/:version', '')
|
||||
end
|
||||
|
||||
def feature_category
|
||||
::Gitlab::ApplicationContext.current_context_attribute(:feature_category) || ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,6 @@ module Gitlab
|
|||
module InOperatorOptimization
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
class QueryBuilder
|
||||
UnsupportedScopeOrder = Class.new(StandardError)
|
||||
|
||||
RECURSIVE_CTE_NAME = 'recursive_keyset_cte'
|
||||
|
||||
# This class optimizes slow database queries (PostgreSQL specific) where the
|
||||
|
@ -44,14 +42,7 @@ module Gitlab
|
|||
def initialize(scope:, array_scope:, array_mapping_scope:, finder_query: nil, values: {})
|
||||
@scope, success = Gitlab::Pagination::Keyset::SimpleOrderBuilder.build(scope)
|
||||
|
||||
unless success
|
||||
error_message = <<~MSG
|
||||
The order on the scope does not support keyset pagination. You might need to define a custom Order object.\n
|
||||
See https://docs.gitlab.com/ee/development/database/keyset_pagination.html#complex-order-configuration\n
|
||||
Or the Gitlab::Pagination::Keyset::Order class for examples
|
||||
MSG
|
||||
raise(UnsupportedScopeOrder, error_message)
|
||||
end
|
||||
raise(UnsupportedScopeOrder) unless success
|
||||
|
||||
@order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(scope)
|
||||
@array_scope = array_scope
|
||||
|
|
|
@ -4,11 +4,9 @@ module Gitlab
|
|||
module Pagination
|
||||
module Keyset
|
||||
class Iterator
|
||||
UnsupportedScopeOrder = Class.new(StandardError)
|
||||
|
||||
def initialize(scope:, cursor: {}, use_union_optimization: true, in_operator_optimization_options: nil)
|
||||
@scope, success = Gitlab::Pagination::Keyset::SimpleOrderBuilder.build(scope)
|
||||
raise(UnsupportedScopeOrder, 'The order on the scope does not support keyset pagination') unless success
|
||||
raise(UnsupportedScopeOrder) unless success
|
||||
|
||||
@cursor = cursor
|
||||
@order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(scope)
|
||||
|
|
|
@ -19,8 +19,6 @@ module Gitlab
|
|||
FORWARD_DIRECTION = 'n'
|
||||
BACKWARD_DIRECTION = 'p'
|
||||
|
||||
UnsupportedScopeOrder = Class.new(StandardError)
|
||||
|
||||
# scope - ActiveRecord::Relation object with order by clause
|
||||
# cursor - Encoded cursor attributes as String. Empty value will requests the first page.
|
||||
# per_page - Number of items per page.
|
||||
|
@ -167,7 +165,7 @@ module Gitlab
|
|||
def build_scope(scope)
|
||||
keyset_aware_scope, success = Gitlab::Pagination::Keyset::SimpleOrderBuilder.build(scope)
|
||||
|
||||
raise(UnsupportedScopeOrder, 'The order on the scope does not support keyset pagination') unless success
|
||||
raise(UnsupportedScopeOrder) unless success
|
||||
|
||||
keyset_aware_scope
|
||||
end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Pagination
|
||||
module Keyset
|
||||
class UnsupportedScopeOrder < StandardError
|
||||
DEFAULT_ERROR_MESSAGE = <<~MSG
|
||||
The order on the scope does not support keyset pagination. You might need to define a custom Order object.\n
|
||||
See https://docs.gitlab.com/ee/development/database/keyset_pagination.html#complex-order-configuration\n
|
||||
Or the Gitlab::Pagination::Keyset::Order class for examples
|
||||
MSG
|
||||
|
||||
def message
|
||||
DEFAULT_ERROR_MESSAGE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Configure', except: { job: 'review-qa-*' } do
|
||||
describe 'Kubernetes Cluster Integration', :requires_admin, :skip_live_env, :smoke do
|
||||
describe 'Kubernetes Cluster Integration', :orchestrated, :requires_admin, :skip_live_env do
|
||||
context 'Project Clusters' do
|
||||
let!(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! }
|
||||
let(:project) do
|
||||
|
|
|
@ -38,6 +38,14 @@ RSpec.describe GraphqlController do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'sets feature category in ApplicationContext from request' do
|
||||
request.headers["HTTP_X_GITLAB_FEATURE_CATEGORY"] = "web_ide"
|
||||
|
||||
post :execute
|
||||
|
||||
expect(::Gitlab::ApplicationContext.current_context_attribute(:feature_category)).to eq('web_ide')
|
||||
end
|
||||
|
||||
it 'returns 200 when user can access API' do
|
||||
post :execute
|
||||
|
||||
|
|
|
@ -18,6 +18,10 @@ FactoryBot.define do
|
|||
transient { child_of { nil } }
|
||||
transient { upstream_of { nil } }
|
||||
|
||||
before(:create) do |pipeline, evaluator|
|
||||
pipeline.ensure_project_iid!
|
||||
end
|
||||
|
||||
after(:build) do |pipeline, evaluator|
|
||||
if evaluator.child_of
|
||||
pipeline.project = evaluator.child_of.project
|
||||
|
@ -48,7 +52,6 @@ FactoryBot.define do
|
|||
transient { ci_ref_presence { true } }
|
||||
|
||||
before(:create) do |pipeline, evaluator|
|
||||
pipeline.ensure_project_iid!
|
||||
pipeline.ensure_ci_ref! if evaluator.ci_ref_presence && pipeline.ci_ref_id.nil?
|
||||
end
|
||||
|
||||
|
|
|
@ -31,12 +31,11 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
|
|||
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
|
||||
end
|
||||
|
||||
it 'returns issues in the correct order with non-nil relative positions', :aggregate_failures do
|
||||
it 'returns the issues in the correct order' do
|
||||
# by relative_position and then ID
|
||||
result = resolve_board_list_issues
|
||||
|
||||
expect(result.map(&:id)).to eq [issue1.id, issue3.id, issue2.id, issue4.id]
|
||||
expect(result.map(&:relative_position)).not_to include(nil)
|
||||
end
|
||||
|
||||
it 'finds only issues matching filters' do
|
||||
|
@ -119,6 +118,6 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
|
|||
end
|
||||
|
||||
def resolve_board_list_issues(args: {}, current_user: user)
|
||||
resolve(described_class, obj: list, args: args, ctx: { current_user: current_user }).items
|
||||
resolve(described_class, obj: list, args: args, ctx: { current_user: current_user })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -154,6 +154,17 @@ RSpec.describe Types::BaseField do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
context "late_extensions is given" do
|
||||
it 'registers the late extensions after the regular extensions' do
|
||||
extension_class = Class.new(GraphQL::Schema::Field::ConnectionExtension)
|
||||
field = described_class.new(name: 'test', type: GraphQL::Types::String.connection_type, null: true, late_extensions: [extension_class])
|
||||
|
||||
expect(field.extensions.last.class).to be(extension_class)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
context 'feature flag given' do
|
||||
let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, feature_flag: flag, null: false, description: 'Test description.') }
|
||||
|
|
|
@ -10,4 +10,12 @@ RSpec.describe GitlabSchema.types['BoardList'] do
|
|||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
||||
describe 'issues field' do
|
||||
subject { described_class.fields['issues'] }
|
||||
|
||||
it 'has a correct extension' do
|
||||
is_expected.to have_graphql_extension(Gitlab::Graphql::Board::IssuesConnectionExtension)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,4 +45,10 @@ RSpec.describe Gitlab::Database::Partitioning do
|
|||
described_class.drop_detached_partitions
|
||||
end
|
||||
end
|
||||
|
||||
context 'ensure that the registered models have partitioning strategy' do
|
||||
it 'fails when partitioning_strategy is not specified for the model' do
|
||||
expect(described_class.registered_models).to all(respond_to(:partitioning_strategy))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::FeatureCategories do
|
||||
let(:fake_categories) { %w(foo bar) }
|
||||
|
||||
subject { described_class.new(fake_categories) }
|
||||
|
||||
describe "#valid?" do
|
||||
it "returns true if category is known", :aggregate_failures do
|
||||
expect(subject.valid?('foo')).to be(true)
|
||||
expect(subject.valid?('zzz')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#from_request" do
|
||||
let(:request_env) { {} }
|
||||
let(:verified) { true }
|
||||
|
||||
def fake_request(request_feature_category)
|
||||
double('request', env: request_env, headers: { "HTTP_X_GITLAB_FEATURE_CATEGORY" => request_feature_category })
|
||||
end
|
||||
|
||||
before do
|
||||
allow(::Gitlab::RequestForgeryProtection).to receive(:verified?).with(request_env).and_return(verified)
|
||||
end
|
||||
|
||||
it "returns category from request when valid, otherwise returns nil", :aggregate_failures do
|
||||
expect(subject.from_request(fake_request("foo"))).to be("foo")
|
||||
expect(subject.from_request(fake_request("zzz"))).to be_nil
|
||||
end
|
||||
|
||||
context "when request is not verified" do
|
||||
let(:verified) { false }
|
||||
|
||||
it "returns nil" do
|
||||
expect(subject.from_request(fake_request("foo"))).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#categories" do
|
||||
it "returns a set of the given categories" do
|
||||
expect(subject.categories).to be_a(Set)
|
||||
expect(subject.categories).to contain_exactly(*fake_categories)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".load_from_yaml" do
|
||||
subject { described_class.load_from_yaml }
|
||||
|
||||
it "creates FeatureCategories from feature_categories.yml file" do
|
||||
contents = YAML.load_file(Rails.root.join('config', 'feature_categories.yml'))
|
||||
|
||||
expect(subject.categories).to contain_exactly(*contents)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".default" do
|
||||
it "returns a memoization of load_from_yaml", :aggregate_failures do
|
||||
# FeatureCategories.default could have been referenced in another spec, so we need to clean it up here
|
||||
described_class.instance_variable_set(:@default, nil)
|
||||
|
||||
expect(described_class).to receive(:load_from_yaml).once.and_call_original
|
||||
|
||||
2.times { described_class.default }
|
||||
|
||||
# Uses reference equality to verify memoization
|
||||
expect(described_class.default).to equal(described_class.default)
|
||||
expect(described_class.default).to be_a(described_class)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -348,7 +348,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
|
|||
end
|
||||
|
||||
it 'has every label in config/feature_categories.yml' do
|
||||
defaults = [described_class::FEATURE_CATEGORY_DEFAULT, 'not_owned']
|
||||
defaults = [::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT, 'not_owned']
|
||||
feature_categories = YAML.load_file(Rails.root.join('config', 'feature_categories.yml')).map(&:strip) + defaults
|
||||
|
||||
expect(described_class::FEATURE_CATEGORIES_TO_INITIALIZE).to all(be_in(feature_categories))
|
||||
|
|
|
@ -32,7 +32,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
|
|||
|
||||
it 'measures with correct labels and value' do
|
||||
value = 1
|
||||
expect(prometheus_metric).to receive(metric_method).with({ controller: 'TestController', action: 'show', feature_category: '' }, value)
|
||||
expect(prometheus_metric).to receive(metric_method).with({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT }, value)
|
||||
|
||||
transaction.send(metric_method, :bau, value)
|
||||
end
|
||||
|
@ -105,6 +105,9 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
|
|||
namespace: "/projects")
|
||||
|
||||
env['api.endpoint'] = endpoint
|
||||
|
||||
# This is needed since we're not actually making a request, which would trigger the controller pushing to the context
|
||||
::Gitlab::ApplicationContext.push(feature_category: 'projects')
|
||||
end
|
||||
|
||||
it 'provides labels with the method and path of the route in the grape endpoint' do
|
||||
|
@ -129,7 +132,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
|
|||
include_context 'ActionController request'
|
||||
|
||||
it 'tags a transaction with the name and action of a controller' do
|
||||
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
|
||||
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
|
||||
end
|
||||
|
||||
it 'contains only the labels defined for transactions' do
|
||||
|
@ -140,7 +143,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
|
|||
let(:request) { double(:request, format: double(:format, ref: :json)) }
|
||||
|
||||
it 'appends the mime type to the transaction action' do
|
||||
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: '' })
|
||||
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show.json', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -148,13 +151,15 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
|
|||
let(:request) { double(:request, format: double(:format, ref: 'http://example.com')) }
|
||||
|
||||
it 'does not append the MIME type to the transaction action' do
|
||||
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: '' })
|
||||
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: ::Gitlab::FeatureCategories::FEATURE_CATEGORY_DEFAULT })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the feature category is known' do
|
||||
it 'includes it in the feature category label' do
|
||||
expect(controller_class).to receive(:feature_category_for_action).with('show').and_return(:source_code_management)
|
||||
# This is needed since we're not actually making a request, which would trigger the controller pushing to the context
|
||||
::Gitlab::ApplicationContext.push(feature_category: 'source_code_management')
|
||||
|
||||
expect(transaction.labels).to eq({ controller: 'TestController', action: 'show', feature_category: "source_code_management" })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,7 +30,7 @@ RSpec.describe 'get board lists' do
|
|||
nodes {
|
||||
lists {
|
||||
nodes {
|
||||
issues(filters: {labelName: "#{label2.title}"}) {
|
||||
issues(filters: {labelName: "#{label2.title}"}, first: 3) {
|
||||
count
|
||||
nodes {
|
||||
#{all_graphql_fields_for('issues'.classify)}
|
||||
|
@ -44,6 +44,10 @@ RSpec.describe 'get board lists' do
|
|||
)
|
||||
end
|
||||
|
||||
def issue_id
|
||||
issues_data.map { |i| i['id'] }
|
||||
end
|
||||
|
||||
def issue_titles
|
||||
issues_data.map { |i| i['title'] }
|
||||
end
|
||||
|
@ -60,6 +64,7 @@ RSpec.describe 'get board lists' do
|
|||
let!(:issue3) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
|
||||
let!(:issue4) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
|
||||
let!(:issue5) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) }
|
||||
let!(:issue6) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
|
||||
|
||||
context 'when the user does not have access to the board' do
|
||||
it 'returns nil' do
|
||||
|
@ -72,14 +77,19 @@ RSpec.describe 'get board lists' do
|
|||
context 'when user can read the board' do
|
||||
before do
|
||||
board_parent.add_reporter(user)
|
||||
post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user)
|
||||
end
|
||||
|
||||
it 'can access the issues', :aggregate_failures do
|
||||
post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user)
|
||||
|
||||
# ties for relative positions are broken by id in ascending order by default
|
||||
expect(issue_titles).to eq([issue2.title, issue1.title, issue3.title])
|
||||
expect(issue_relative_positions).not_to include(nil)
|
||||
end
|
||||
|
||||
it 'does not set the relative positions of the issues not being returned', :aggregate_failures do
|
||||
expect(issue_id).not_to include(issue6.id)
|
||||
expect(issue3.relative_position).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -87,19 +87,25 @@ module Ci
|
|||
end
|
||||
|
||||
context 'for specific runner' do
|
||||
context 'with FF disabled' do
|
||||
context 'with tables decoupling disabled' do
|
||||
before do
|
||||
stub_feature_flags(
|
||||
ci_pending_builds_project_runners_decoupling: false,
|
||||
ci_queueing_builds_enabled_checks: false)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332952') do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not pick a build' do
|
||||
expect(execute(specific_runner)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with FF enabled' do
|
||||
context 'with tables decoupling enabled' do
|
||||
before do
|
||||
stub_feature_flags(
|
||||
ci_pending_builds_project_runners_decoupling: true,
|
||||
|
@ -266,17 +272,23 @@ module Ci
|
|||
context 'and uses project runner' do
|
||||
let(:build) { execute(specific_runner) }
|
||||
|
||||
context 'with FF disabled' do
|
||||
context 'with tables decoupling disabled' do
|
||||
before do
|
||||
stub_feature_flags(
|
||||
ci_pending_builds_project_runners_decoupling: false,
|
||||
ci_queueing_builds_enabled_checks: false)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332952') do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it { expect(build).to be_nil }
|
||||
end
|
||||
|
||||
context 'with FF enabled' do
|
||||
context 'with tables decoupling enabled' do
|
||||
before do
|
||||
stub_feature_flags(
|
||||
ci_pending_builds_project_runners_decoupling: true,
|
||||
|
@ -791,6 +803,12 @@ module Ci
|
|||
stub_feature_flags(ci_queueing_denormalize_shared_runners_information: false)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332952') do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'handles runner assignment'
|
||||
end
|
||||
|
||||
|
@ -807,6 +825,12 @@ module Ci
|
|||
stub_feature_flags(ci_queueing_denormalize_tags_information: false)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332952') do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'handles runner assignment'
|
||||
end
|
||||
|
||||
|
@ -815,6 +839,12 @@ module Ci
|
|||
stub_feature_flags(ci_queueing_denormalize_namespace_traversal_ids: false)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332952') do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'handles runner assignment'
|
||||
end
|
||||
end
|
||||
|
@ -824,6 +854,12 @@ module Ci
|
|||
stub_feature_flags(ci_pending_builds_queue_source: false)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332952') do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'handles runner assignment'
|
||||
end
|
||||
end
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -81,7 +81,6 @@
|
|||
- "./spec/presenters/ci/build_runner_presenter_spec.rb"
|
||||
- "./spec/presenters/ci/pipeline_presenter_spec.rb"
|
||||
- "./spec/presenters/packages/detail/package_presenter_spec.rb"
|
||||
- "./spec/requests/api/ci/runner/jobs_request_post_spec.rb"
|
||||
- "./spec/requests/api/ci/runner/runners_post_spec.rb"
|
||||
- "./spec/requests/api/ci/runners_spec.rb"
|
||||
- "./spec/requests/api/commit_statuses_spec.rb"
|
||||
|
@ -105,7 +104,6 @@
|
|||
- "./spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb"
|
||||
- "./spec/services/ci/job_artifacts/destroy_associations_service_spec.rb"
|
||||
- "./spec/services/ci/job_artifacts/destroy_batch_service_spec.rb"
|
||||
- "./spec/services/ci/register_job_service_spec.rb"
|
||||
- "./spec/services/deployments/older_deployments_drop_service_spec.rb"
|
||||
- "./spec/services/environments/stop_service_spec.rb"
|
||||
- "./spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb"
|
||||
|
|
|
@ -33,10 +33,12 @@ module Database
|
|||
end
|
||||
|
||||
def cleanup_with_cross_database_modification_prevented
|
||||
if PreventCrossDatabaseModification.cross_database_context
|
||||
ActiveSupport::Notifications.unsubscribe(PreventCrossDatabaseModification.cross_database_context[:subscriber])
|
||||
PreventCrossDatabaseModification.cross_database_context[:enabled] = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.cross_database_context
|
||||
Thread.current[:transaction_tracker]
|
||||
|
@ -55,8 +57,11 @@ module Database
|
|||
end
|
||||
|
||||
def self.prevent_cross_database_modification!(connection, sql)
|
||||
return unless cross_database_context
|
||||
return unless cross_database_context[:enabled]
|
||||
|
||||
return if connection.pool.instance_of?(ActiveRecord::ConnectionAdapters::NullPool)
|
||||
|
||||
database = connection.pool.db_config.name
|
||||
|
||||
if sql.start_with?('SAVEPOINT')
|
||||
|
@ -87,7 +92,7 @@ module Database
|
|||
if schemas.many?
|
||||
raise Database::PreventCrossDatabaseModification::CrossDatabaseModificationAcrossUnsupportedTablesError,
|
||||
"Cross-database data modification of '#{schemas.to_a.join(", ")}' were detected within " \
|
||||
"a transaction modifying the '#{all_tables.to_a.join(", ")}'"
|
||||
"a transaction modifying the '#{all_tables.to_a.join(", ")}' tables"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -96,16 +101,20 @@ end
|
|||
Gitlab::Database.singleton_class.prepend(
|
||||
Database::PreventCrossDatabaseModification::GitlabDatabaseMixin)
|
||||
|
||||
CROSS_DB_MODIFICATION_ALLOW_LIST = Set.new(YAML.load_file(File.join(__dir__, 'cross-database-modification-allowlist.yml'))).freeze
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include(::Database::PreventCrossDatabaseModification::SpecHelpers)
|
||||
|
||||
# Using before and after blocks because the around block causes problems with the let_it_be
|
||||
# record creations. It makes an extra savepoint which breaks the transaction count logic.
|
||||
config.before(:each, :prevent_cross_database_modification) do
|
||||
config.before do |example_file|
|
||||
if CROSS_DB_MODIFICATION_ALLOW_LIST.exclude?(example_file.file_path)
|
||||
with_cross_database_modification_prevented
|
||||
end
|
||||
end
|
||||
|
||||
config.after(:each, :prevent_cross_database_modification) do
|
||||
config.after do |example_file|
|
||||
cleanup_with_cross_database_modification_prevented
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,6 +64,10 @@ module Database
|
|||
ensure
|
||||
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
|
||||
end
|
||||
|
||||
def allow_cross_joins_across_databases(url:, &block)
|
||||
::Gitlab::Database.allow_cross_joins_across_databases(url: url, &block)
|
||||
end
|
||||
end
|
||||
|
||||
module GitlabDatabaseMixin
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'a connection with collection methods' do
|
||||
%i[to_a size include? empty?].each do |method_name|
|
||||
%i[to_a size map include? empty?].each do |method_name|
|
||||
it "responds to #{method_name}" do
|
||||
expect(connection).to respond_to(method_name)
|
||||
end
|
||||
|
|
|
@ -56,6 +56,7 @@ const (
|
|||
apiPattern = `^/api/`
|
||||
ciAPIPattern = `^/ci/api/`
|
||||
gitProjectPattern = `^/.+\.git/`
|
||||
geoGitProjectPattern = `^/[^-].+\.git/` // Prevent matching routes like /-/push_from_secondary
|
||||
projectPattern = `^/([^/]+/){1,}[^/]+/`
|
||||
apiProjectPattern = apiPattern + `v4/projects/[^/]+/` // API: Projects can be encoded via group%2Fsubgroup%2Fproject
|
||||
snippetUploadPattern = `^/uploads/personal_snippet`
|
||||
|
@ -348,10 +349,9 @@ func configureRoutes(u *upstream) {
|
|||
// pulls are performed against a different source of truth. Ideally, we'd
|
||||
// proxy/redirect pulls as well, when the secondary is not up-to-date.
|
||||
//
|
||||
u.route("GET", gitProjectPattern+`info/refs\z`, git.GetInfoRefsHandler(api)),
|
||||
u.route("POST", gitProjectPattern+`git-upload-pack\z`, contentEncodingHandler(git.UploadPack(api)), withMatcher(isContentType("application/x-git-upload-pack-request"))),
|
||||
u.route("POST", gitProjectPattern+`git-receive-pack\z`, contentEncodingHandler(git.ReceivePack(api)), withMatcher(isContentType("application/x-git-receive-pack-request"))),
|
||||
u.route("PUT", gitProjectPattern+`gitlab-lfs/objects/([0-9a-f]{64})/([0-9]+)\z`, lfs.PutStore(api, signingProxy, preparers.lfs), withMatcher(isContentType("application/octet-stream"))),
|
||||
u.route("GET", geoGitProjectPattern+`info/refs\z`, git.GetInfoRefsHandler(api)),
|
||||
u.route("POST", geoGitProjectPattern+`git-upload-pack\z`, contentEncodingHandler(git.UploadPack(api)), withMatcher(isContentType("application/x-git-upload-pack-request"))),
|
||||
u.route("GET", geoGitProjectPattern+`gitlab-lfs/objects/([0-9a-f]{64})\z`, defaultUpstream),
|
||||
|
||||
// Serve health checks from this Geo secondary
|
||||
u.route("", "^/-/(readiness|liveness)$", static.DeployPage(probeUpstream)),
|
||||
|
|
|
@ -99,6 +99,8 @@ func TestGeoProxyFeatureEnabledOnGeoSecondarySite(t *testing.T) {
|
|||
defer wsDeferredClose()
|
||||
|
||||
testCases := []testCase{
|
||||
{"push from secondary is forwarded", "/-/push_from_secondary/foo/bar.git/info/refs", "Geo primary received request to path /-/push_from_secondary/foo/bar.git/info/refs"},
|
||||
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
|
||||
{"jobs request is forwarded", "/api/v4/jobs/request", "Geo primary received request to path /api/v4/jobs/request"},
|
||||
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
|
||||
{"unknown route is forwarded", "/anything", "Geo primary received request to path /anything"},
|
||||
|
@ -117,6 +119,7 @@ func TestGeoProxyFeatureDisabledOnNonGeoSecondarySite(t *testing.T) {
|
|||
defer wsDeferredClose()
|
||||
|
||||
testCases := []testCase{
|
||||
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
|
||||
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
|
||||
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
|
||||
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
|
||||
|
@ -134,6 +137,7 @@ func TestGeoProxyFeatureEnabledOnNonGeoSecondarySite(t *testing.T) {
|
|||
defer wsDeferredClose()
|
||||
|
||||
testCases := []testCase{
|
||||
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
|
||||
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
|
||||
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
|
||||
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
|
||||
|
@ -151,6 +155,7 @@ func TestGeoProxyFeatureEnabledButWithAPIError(t *testing.T) {
|
|||
defer wsDeferredClose()
|
||||
|
||||
testCases := []testCase{
|
||||
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
|
||||
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
|
||||
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
|
||||
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
|
||||
|
@ -174,12 +179,15 @@ func TestGeoProxyFeatureEnablingAndDisabling(t *testing.T) {
|
|||
defer wsDeferredClose()
|
||||
|
||||
testCasesLocal := []testCase{
|
||||
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
|
||||
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
|
||||
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
|
||||
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
|
||||
}
|
||||
|
||||
testCasesProxied := []testCase{
|
||||
{"push from secondary is forwarded", "/-/push_from_secondary/foo/bar.git/info/refs", "Geo primary received request to path /-/push_from_secondary/foo/bar.git/info/refs"},
|
||||
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
|
||||
{"jobs request is forwarded", "/api/v4/jobs/request", "Geo primary received request to path /api/v4/jobs/request"},
|
||||
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
|
||||
{"unknown route is forwarded", "/anything", "Geo primary received request to path /anything"},
|
||||
|
|
Loading…
Reference in New Issue