Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
063d4b1caa
commit
62c78157be
27 changed files with 798 additions and 433 deletions
|
@ -53,9 +53,15 @@ export const receiveLatestPipelineSuccess = ({ rootGetters, commit }, { pipeline
|
|||
commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, lastCommitPipeline);
|
||||
};
|
||||
|
||||
export const fetchLatestPipeline = ({ dispatch, rootGetters }) => {
|
||||
export const fetchLatestPipeline = ({ commit, dispatch, rootGetters }) => {
|
||||
if (eTagPoll) return;
|
||||
|
||||
if (!rootGetters.lastCommit) {
|
||||
commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, null);
|
||||
dispatch('stopPipelinePolling');
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch('requestLatestPipeline');
|
||||
|
||||
eTagPoll = new Poll({
|
||||
|
|
|
@ -246,7 +246,7 @@ class NotifyPreview < ActionMailer::Preview
|
|||
def cleanup
|
||||
email = nil
|
||||
|
||||
ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
|
||||
ApplicationRecord.transaction do
|
||||
email = yield
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Clusters
|
||||
module Applications
|
||||
class Runner < ApplicationRecord
|
||||
VERSION = '0.35.0'
|
||||
VERSION = '0.36.0'
|
||||
|
||||
self.table_name = 'clusters_applications_runners'
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ class InternalId < ApplicationRecord
|
|||
self.internal_id_transactions_total.increment(
|
||||
operation: operation,
|
||||
usage: usage.to_s,
|
||||
in_transaction: ActiveRecord::Base.connection.transaction_open?.to_s # rubocop: disable Database/MultipleDatabases
|
||||
in_transaction: InternalId.connection.transaction_open?.to_s
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -25,15 +25,20 @@ module Namespaces
|
|||
def self_and_ancestors(include_self: true, hierarchy_order: nil)
|
||||
return super unless use_traversal_ids_for_ancestor_scopes?
|
||||
|
||||
ancestors_cte, base_cte = ancestor_ctes
|
||||
namespaces = Arel::Table.new(:namespaces)
|
||||
|
||||
records = unscoped
|
||||
.where(id: select('unnest(traversal_ids)'))
|
||||
.with(base_cte.to_arel, ancestors_cte.to_arel)
|
||||
.distinct
|
||||
.from([ancestors_cte.table, namespaces])
|
||||
.where(namespaces[:id].eq(ancestors_cte.table[:ancestor_id]))
|
||||
.order_by_depth(hierarchy_order)
|
||||
.normal_select
|
||||
|
||||
if include_self
|
||||
records
|
||||
else
|
||||
records.where.not(id: all.as_ids)
|
||||
records.where(ancestors_cte.table[:base_id].not_eq(ancestors_cte.table[:ancestor_id]))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -150,6 +155,20 @@ module Namespaces
|
|||
records.where('namespaces.id <> base.id')
|
||||
end
|
||||
end
|
||||
|
||||
def ancestor_ctes
|
||||
base_scope = all.select('namespaces.id', 'namespaces.traversal_ids')
|
||||
base_cte = Gitlab::SQL::CTE.new(:base_ancestors_cte, base_scope)
|
||||
|
||||
# We have to alias id with 'AS' to avoid ambiguous column references by calling methods.
|
||||
ancestors_scope = unscoped
|
||||
.unscope(where: [:type])
|
||||
.select('id as base_id', 'unnest(traversal_ids) as ancestor_id')
|
||||
.from(base_cte.table)
|
||||
ancestors_cte = Gitlab::SQL::CTE.new(:ancestors_cte, ancestors_scope)
|
||||
|
||||
[ancestors_cte, base_cte]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ module AutoMerge
|
|||
include MergeRequests::AssignsMergeParams
|
||||
|
||||
def execute(merge_request)
|
||||
ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
|
||||
ApplicationRecord.transaction do
|
||||
register_auto_merge_parameters!(merge_request)
|
||||
yield if block_given?
|
||||
end
|
||||
|
@ -29,7 +29,7 @@ module AutoMerge
|
|||
end
|
||||
|
||||
def cancel(merge_request)
|
||||
ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
|
||||
ApplicationRecord.transaction do
|
||||
clear_auto_merge_parameters!(merge_request)
|
||||
yield if block_given?
|
||||
end
|
||||
|
@ -41,7 +41,7 @@ module AutoMerge
|
|||
end
|
||||
|
||||
def abort(merge_request, reason)
|
||||
ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
|
||||
ApplicationRecord.transaction do
|
||||
clear_auto_merge_parameters!(merge_request)
|
||||
yield if block_given?
|
||||
end
|
||||
|
|
36
app/services/ci/register_runner_service.rb
Normal file
36
app/services/ci/register_runner_service.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
class RegisterRunnerService
|
||||
def execute(registration_token, attributes)
|
||||
runner_type_attrs = check_token_and_extract_attrs(registration_token)
|
||||
|
||||
return unless runner_type_attrs
|
||||
|
||||
::Ci::Runner.create(attributes.merge(runner_type_attrs))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_token_and_extract_attrs(registration_token)
|
||||
if runner_registration_token_valid?(registration_token)
|
||||
# Create shared runner. Requires admin access
|
||||
{ runner_type: :instance_type }
|
||||
elsif runner_registrar_valid?('project') && project = ::Project.find_by_runners_token(registration_token)
|
||||
# Create a specific runner for the project
|
||||
{ runner_type: :project_type, projects: [project] }
|
||||
elsif runner_registrar_valid?('group') && group = ::Group.find_by_runners_token(registration_token)
|
||||
# Create a specific runner for the group
|
||||
{ runner_type: :group_type, groups: [group] }
|
||||
end
|
||||
end
|
||||
|
||||
def runner_registration_token_valid?(registration_token)
|
||||
ActiveSupport::SecurityUtils.secure_compare(registration_token, Gitlab::CurrentSettings.runners_registration_token)
|
||||
end
|
||||
|
||||
def runner_registrar_valid?(type)
|
||||
Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -26,7 +26,7 @@ module Deployments
|
|||
end
|
||||
|
||||
def update_environment(deployment)
|
||||
ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
|
||||
ApplicationRecord.transaction do
|
||||
# Renew attributes at update
|
||||
renew_external_url
|
||||
renew_auto_stop_in
|
||||
|
|
|
@ -4,7 +4,10 @@ module Import
|
|||
module GitlabProjects
|
||||
class CreateProjectFromRemoteFileService < CreateProjectFromUploadedFileService
|
||||
FILE_SIZE_LIMIT = 10.gigabytes
|
||||
ALLOWED_CONTENT_TYPES = ['application/gzip'].freeze
|
||||
ALLOWED_CONTENT_TYPES = [
|
||||
'application/gzip', # most common content-type when fetching a tar.gz
|
||||
'application/x-tar' # aws-s3 uses x-tar for tar.gz files
|
||||
].freeze
|
||||
|
||||
validate :valid_remote_import_url?
|
||||
validate :validate_file_size
|
||||
|
@ -44,17 +47,27 @@ module Import
|
|||
end
|
||||
|
||||
def validate_content_type
|
||||
# AWS-S3 presigned URLs don't respond to HTTP HEAD requests,
|
||||
# so file type cannot be validated
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75170#note_748059103
|
||||
return if amazon_s3?
|
||||
|
||||
if headers['content-type'].blank?
|
||||
errors.add(:base, "Missing 'ContentType' header")
|
||||
elsif !ALLOWED_CONTENT_TYPES.include?(headers['content-type'])
|
||||
errors.add(:base, "Remote file content type '%{content_type}' not allowed. (Allowed content types: %{allowed})" % {
|
||||
content_type: headers['content-type'],
|
||||
allowed: ALLOWED_CONTENT_TYPES.join(',')
|
||||
allowed: ALLOWED_CONTENT_TYPES.join(', ')
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def validate_file_size
|
||||
# AWS-S3 presigned URLs don't respond to HTTP HEAD requests,
|
||||
# so file size cannot be validated
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75170#note_748059103
|
||||
return if amazon_s3?
|
||||
|
||||
if headers['content-length'].to_i == 0
|
||||
errors.add(:base, "Missing 'ContentLength' header")
|
||||
elsif headers['content-length'].to_i > FILE_SIZE_LIMIT
|
||||
|
@ -64,6 +77,10 @@ module Import
|
|||
end
|
||||
end
|
||||
|
||||
def amazon_s3?
|
||||
headers['Server'] == 'AmazonS3' && headers['x-amz-request-id'].present?
|
||||
end
|
||||
|
||||
def headers
|
||||
return {} if params[:remote_import_url].blank? || !valid_remote_import_url?
|
||||
|
||||
|
|
|
@ -7439,6 +7439,29 @@ The edge type for [`ScanExecutionPolicy`](#scanexecutionpolicy).
|
|||
| <a id="scanexecutionpolicyedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="scanexecutionpolicyedgenode"></a>`node` | [`ScanExecutionPolicy`](#scanexecutionpolicy) | The item at the end of the edge. |
|
||||
|
||||
#### `ScanResultPolicyConnection`
|
||||
|
||||
The connection type for [`ScanResultPolicy`](#scanresultpolicy).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="scanresultpolicyconnectionedges"></a>`edges` | [`[ScanResultPolicyEdge]`](#scanresultpolicyedge) | A list of edges. |
|
||||
| <a id="scanresultpolicyconnectionnodes"></a>`nodes` | [`[ScanResultPolicy]`](#scanresultpolicy) | A list of nodes. |
|
||||
| <a id="scanresultpolicyconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `ScanResultPolicyEdge`
|
||||
|
||||
The edge type for [`ScanResultPolicy`](#scanresultpolicy).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="scanresultpolicyedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="scanresultpolicyedgenode"></a>`node` | [`ScanResultPolicy`](#scanresultpolicy) | The item at the end of the edge. |
|
||||
|
||||
#### `ScannedResourceConnection`
|
||||
|
||||
The connection type for [`ScannedResource`](#scannedresource).
|
||||
|
@ -7741,6 +7764,29 @@ The edge type for [`TestSuiteSummary`](#testsuitesummary).
|
|||
| <a id="testsuitesummaryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="testsuitesummaryedgenode"></a>`node` | [`TestSuiteSummary`](#testsuitesummary) | The item at the end of the edge. |
|
||||
|
||||
#### `TimelineEventTypeConnection`
|
||||
|
||||
The connection type for [`TimelineEventType`](#timelineeventtype).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="timelineeventtypeconnectionedges"></a>`edges` | [`[TimelineEventTypeEdge]`](#timelineeventtypeedge) | A list of edges. |
|
||||
| <a id="timelineeventtypeconnectionnodes"></a>`nodes` | [`[TimelineEventType]`](#timelineeventtype) | A list of nodes. |
|
||||
| <a id="timelineeventtypeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `TimelineEventTypeEdge`
|
||||
|
||||
The edge type for [`TimelineEventType`](#timelineeventtype).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="timelineeventtypeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="timelineeventtypeedgenode"></a>`node` | [`TimelineEventType`](#timelineeventtype) | The item at the end of the edge. |
|
||||
|
||||
#### `TimelogConnection`
|
||||
|
||||
The connection type for [`Timelog`](#timelog).
|
||||
|
@ -12989,6 +13035,7 @@ Represents vulnerability finding of a security report on the pipeline.
|
|||
| <a id="projectrequestaccessenabled"></a>`requestAccessEnabled` | [`Boolean`](#boolean) | Indicates if users can request member access to the project. |
|
||||
| <a id="projectrequirementstatescount"></a>`requirementStatesCount` | [`RequirementStatesCount`](#requirementstatescount) | Number of requirements for the project by their state. |
|
||||
| <a id="projectsastciconfiguration"></a>`sastCiConfiguration` | [`SastCiConfiguration`](#sastciconfiguration) | SAST CI configuration for the project. |
|
||||
| <a id="projectscanresultpolicies"></a>`scanResultPolicies` | [`ScanResultPolicyConnection`](#scanresultpolicyconnection) | Scan Result Policies of the project. (see [Connections](#connections)) |
|
||||
| <a id="projectsecuritydashboardpath"></a>`securityDashboardPath` | [`String`](#string) | Path to project's security dashboard. |
|
||||
| <a id="projectsecurityscanners"></a>`securityScanners` | [`SecurityScanners`](#securityscanners) | Information about security analyzers used in the project. |
|
||||
| <a id="projectsentryerrors"></a>`sentryErrors` | [`SentryErrorCollection`](#sentryerrorcollection) | Paginated collection of Sentry errors on the project. |
|
||||
|
@ -13295,6 +13342,35 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="projectincidentmanagementoncallschedulesiids"></a>`iids` | [`[ID!]`](#id) | IIDs of on-call schedules. |
|
||||
|
||||
##### `Project.incidentManagementTimelineEvent`
|
||||
|
||||
Incident Management Timeline event associated with the incident.
|
||||
|
||||
Returns [`TimelineEventType`](#timelineeventtype).
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectincidentmanagementtimelineeventid"></a>`id` | [`IncidentManagementTimelineEventID!`](#incidentmanagementtimelineeventid) | ID of the timeline event. |
|
||||
| <a id="projectincidentmanagementtimelineeventincidentid"></a>`incidentId` | [`IssueID!`](#issueid) | ID of the incident. |
|
||||
|
||||
##### `Project.incidentManagementTimelineEvents`
|
||||
|
||||
Incident Management Timeline events associated with the incident.
|
||||
|
||||
Returns [`TimelineEventTypeConnection`](#timelineeventtypeconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectincidentmanagementtimelineeventsincidentid"></a>`incidentId` | [`IssueID!`](#issueid) | ID of the incident. |
|
||||
|
||||
##### `Project.issue`
|
||||
|
||||
A single issue of the project.
|
||||
|
@ -14429,6 +14505,20 @@ Represents the scan execution policy.
|
|||
| <a id="scanexecutionpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
|
||||
| <a id="scanexecutionpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
### `ScanResultPolicy`
|
||||
|
||||
Represents the scan result policy.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="scanresultpolicydescription"></a>`description` | [`String!`](#string) | Description of the policy. |
|
||||
| <a id="scanresultpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
|
||||
| <a id="scanresultpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. |
|
||||
| <a id="scanresultpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
|
||||
| <a id="scanresultpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
### `ScannedResource`
|
||||
|
||||
Represents a resource scanned by a security scan.
|
||||
|
@ -14999,6 +15089,27 @@ Represents a historically accurate report about the timebox.
|
|||
| <a id="timeboxreportburnuptimeseries"></a>`burnupTimeSeries` | [`[BurnupChartDailyTotals!]`](#burnupchartdailytotals) | Daily scope and completed totals for burnup charts. |
|
||||
| <a id="timeboxreportstats"></a>`stats` | [`TimeReportStats`](#timereportstats) | Represents the time report stats for the timebox. |
|
||||
|
||||
### `TimelineEventType`
|
||||
|
||||
Describes an incident management timeline event.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="timelineeventtypeaction"></a>`action` | [`String!`](#string) | Indicates the timeline event icon. |
|
||||
| <a id="timelineeventtypeauthor"></a>`author` | [`UserCore`](#usercore) | User that created the timeline event. |
|
||||
| <a id="timelineeventtypecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp when the event created. |
|
||||
| <a id="timelineeventtypeeditable"></a>`editable` | [`Boolean!`](#boolean) | Indicates the timeline event is editable. |
|
||||
| <a id="timelineeventtypeid"></a>`id` | [`IncidentManagementTimelineEventID!`](#incidentmanagementtimelineeventid) | ID of the timeline event. |
|
||||
| <a id="timelineeventtypeincident"></a>`incident` | [`Issue!`](#issue) | Incident of the timeline event. |
|
||||
| <a id="timelineeventtypenote"></a>`note` | [`String`](#string) | Text note of the timeline event. |
|
||||
| <a id="timelineeventtypenotehtml"></a>`noteHtml` | [`String`](#string) | HTML note of the timeline event. |
|
||||
| <a id="timelineeventtypeoccurredat"></a>`occurredAt` | [`Time!`](#time) | Timestamp when the event occurred. |
|
||||
| <a id="timelineeventtypepromotedfromnote"></a>`promotedFromNote` | [`Note`](#note) | Note from which the timeline event was created. |
|
||||
| <a id="timelineeventtypeupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp when the event updated. |
|
||||
| <a id="timelineeventtypeupdatedbyuser"></a>`updatedByUser` | [`UserCore`](#usercore) | User that updated the timeline event. |
|
||||
|
||||
### `Timelog`
|
||||
|
||||
#### Fields
|
||||
|
@ -17691,6 +17802,12 @@ A `IncidentManagementOncallRotationID` is a global ID. It is encoded as a string
|
|||
|
||||
An example `IncidentManagementOncallRotationID` is: `"gid://gitlab/IncidentManagement::OncallRotation/1"`.
|
||||
|
||||
### `IncidentManagementTimelineEventID`
|
||||
|
||||
A `IncidentManagementTimelineEventID` is a global ID. It is encoded as a string.
|
||||
|
||||
An example `IncidentManagementTimelineEventID` is: `"gid://gitlab/IncidentManagement::TimelineEvent/1"`.
|
||||
|
||||
### `Int`
|
||||
|
||||
Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.
|
||||
|
@ -18180,6 +18297,23 @@ Implementations:
|
|||
| <a id="noteableinterfacediscussions"></a>`discussions` | [`DiscussionConnection!`](#discussionconnection) | All discussions on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="noteableinterfacenotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
|
||||
|
||||
#### `OrchestrationPolicy`
|
||||
|
||||
Implementations:
|
||||
|
||||
- [`ScanExecutionPolicy`](#scanexecutionpolicy)
|
||||
- [`ScanResultPolicy`](#scanresultpolicy)
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="orchestrationpolicydescription"></a>`description` | [`String!`](#string) | Description of the policy. |
|
||||
| <a id="orchestrationpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. |
|
||||
| <a id="orchestrationpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. |
|
||||
| <a id="orchestrationpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. |
|
||||
| <a id="orchestrationpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. |
|
||||
|
||||
#### `PackageFileMetadata`
|
||||
|
||||
Represents metadata associated with a Package file.
|
||||
|
|
|
@ -201,13 +201,13 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
|
|||
| -------------------------------------------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `name` | string | yes | The name of the branch or wildcard |
|
||||
| `push_access_level` | string | no | Access levels allowed to push (defaults: `40`, Maintainer role) |
|
||||
| `merge_access_level` | string | no | Access levels allowed to merge (defaults: `40`, Maintainer role) |
|
||||
| `unprotect_access_level` | string | no | Access levels allowed to unprotect (defaults: `40`, Maintainer role) |
|
||||
| `push_access_level` | integer | no | Access levels allowed to push (defaults: `40`, Maintainer role) |
|
||||
| `merge_access_level` | integer | no | Access levels allowed to merge (defaults: `40`, Maintainer role) |
|
||||
| `unprotect_access_level` | integer | no | Access levels allowed to unprotect (defaults: `40`, Maintainer role) |
|
||||
| `allow_force_push` | boolean | no | Allow all users with push access to force push. (default: `false`) |
|
||||
| `allowed_to_push` **(PREMIUM)** | array | no | Array of access levels allowed to push, with each described by a hash |
|
||||
| `allowed_to_merge` **(PREMIUM)** | array | no | Array of access levels allowed to merge, with each described by a hash |
|
||||
| `allowed_to_unprotect` **(PREMIUM)** | array | no | Array of access levels allowed to unprotect, with each described by a hash |
|
||||
| `allowed_to_push` **(PREMIUM)** | array | no | Array of access levels allowed to push, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}` |
|
||||
| `allowed_to_merge` **(PREMIUM)** | array | no | Array of access levels allowed to merge, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}` |
|
||||
| `allowed_to_unprotect` **(PREMIUM)** | array | no | Array of access levels allowed to unprotect, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}` |
|
||||
| `code_owner_approval_required` **(PREMIUM)** | boolean | no | Prevent pushes to this branch if it matches an item in the [`CODEOWNERS` file](../user/project/code_owners.md). (defaults: false) |
|
||||
|
||||
Example response:
|
||||
|
|
|
@ -19,7 +19,7 @@ This API is in the process of being deprecated and considered unstable.
|
|||
The response payload may be subject to change or breakage
|
||||
across GitLab releases. Please use the
|
||||
[GraphQL API](graphql/reference/index.md#queryvulnerabilities)
|
||||
instead.
|
||||
instead. See the [GraphQL examples](#replace-rest-with-graphql) to get started.
|
||||
|
||||
Every API call to vulnerabilities must be [authenticated](index.md#authentication).
|
||||
|
||||
|
@ -272,3 +272,185 @@ Example response:
|
|||
"closed_at": null
|
||||
}
|
||||
```
|
||||
|
||||
## Replace REST with GraphQL
|
||||
|
||||
To prepare for the [upcoming deprecation](https://gitlab.com/groups/gitlab-org/-/epics/5118) of
|
||||
this REST API endpoint, use the examples below to learn how to perform the equivalent operations
|
||||
using the GraphQL API.
|
||||
|
||||
### GraphQL - Single vulnerability
|
||||
|
||||
Use [`Query.vulnerability`](graphql/reference/#queryvulnerability).
|
||||
|
||||
```graphql
|
||||
{
|
||||
vulnerability(id: "gid://gitlab/Vulnerability/20345379") {
|
||||
title
|
||||
description
|
||||
state
|
||||
severity
|
||||
reportType
|
||||
project {
|
||||
id
|
||||
name
|
||||
fullPath
|
||||
}
|
||||
detectedAt
|
||||
confirmedAt
|
||||
resolvedAt
|
||||
resolvedBy {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"vulnerability": {
|
||||
"title": "Improper Input Validation in railties",
|
||||
"description": "A remote code execution vulnerability in development mode Rails beta3 can allow an attacker to guess the automatically generated development mode secret token. This secret token can be used in combination with other Rails internals to escalate to a remote code execution exploit.",
|
||||
"state": "RESOLVED",
|
||||
"severity": "CRITICAL",
|
||||
"reportType": "DEPENDENCY_SCANNING",
|
||||
"project": {
|
||||
"id": "gid://gitlab/Project/6102100",
|
||||
"name": "security-reports",
|
||||
"fullPath": "gitlab-examples/security/security-reports"
|
||||
},
|
||||
"detectedAt": "2021-10-14T03:13:41Z",
|
||||
"confirmedAt": "2021-12-14T01:45:56Z",
|
||||
"resolvedAt": "2021-12-14T01:45:59Z",
|
||||
"resolvedBy": {
|
||||
"id": "gid://gitlab/User/480804",
|
||||
"username": "thiagocsf"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GraphQL - Confirm vulnerability
|
||||
|
||||
Use [`Mutation.vulnerabilityConfirm`](graphql/reference/#mutationvulnerabilityconfirm).
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
vulnerabilityConfirm(input: { id: "gid://gitlab/Vulnerability/23577695"}) {
|
||||
vulnerability {
|
||||
state
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"vulnerabilityConfirm": {
|
||||
"vulnerability": {
|
||||
"state": "CONFIRMED"
|
||||
},
|
||||
"errors": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GraphQL - Resolve vulnerability
|
||||
|
||||
Use [`Mutation.vulnerabilityResolve`](graphql/reference/#mutationvulnerabilityresolve).
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
vulnerabilityResolve(input: { id: "gid://gitlab/Vulnerability/23577695"}) {
|
||||
vulnerability {
|
||||
state
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"vulnerabilityConfirm": {
|
||||
"vulnerability": {
|
||||
"state": "RESOLVED"
|
||||
},
|
||||
"errors": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GraphQL - Dismiss vulnerability
|
||||
|
||||
Use [`Mutation.vulnerabilityDismiss`](graphql/reference/#mutationvulnerabilitydismiss).
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
vulnerabilityDismiss(input: { id: "gid://gitlab/Vulnerability/23577695"}) {
|
||||
vulnerability {
|
||||
state
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"vulnerabilityConfirm": {
|
||||
"vulnerability": {
|
||||
"state": "DISMISSED"
|
||||
},
|
||||
"errors": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GraphQL - Revert vulnerability to detected state
|
||||
|
||||
Use [`Mutation.vulnerabilityRevertToDetected`](graphql/reference/#mutationvulnerabilityreverttodetected).
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
vulnerabilityRevertToDetected(input: { id: "gid://gitlab/Vulnerability/20345379"}) {
|
||||
vulnerability {
|
||||
state
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"vulnerabilityConfirm": {
|
||||
"vulnerability": {
|
||||
"state": "DETECTED"
|
||||
},
|
||||
"errors": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -3066,8 +3066,10 @@ job:
|
|||
|
||||
- If a rule matches and has no `when` defined, the rule uses the `when`
|
||||
defined for the job, which defaults to `on_success` if not defined.
|
||||
- You can define `when` once per rule, or once at the job-level, which applies to
|
||||
all rules. You can't mix `when` at the job-level with `when` in rules.
|
||||
- In GitLab 14.5 and earlier, you can define `when` once per rule, or once at the job-level,
|
||||
which applies to all rules. You can't mix `when` at the job-level with `when` in rules.
|
||||
- In GitLab 14.6 and later, you can [mix `when` at the job-level with `when` in rules](https://gitlab.com/gitlab-org/gitlab/-/issues/219437).
|
||||
`when` configuration in `rules` takes precedence over `when` at the job-level.
|
||||
- Unlike variables in [`script`](../variables/index.md#use-cicd-variables-in-job-scripts)
|
||||
sections, variables in rules expressions are always formatted as `$VARIABLE`.
|
||||
- You can use `rules:if` with `include` to [conditionally include other configuration files](includes.md#use-rules-with-include).
|
||||
|
|
|
@ -11,14 +11,6 @@ module API
|
|||
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'
|
||||
JOB_TOKEN_PARAM = :token
|
||||
|
||||
def runner_registration_token_valid?
|
||||
ActiveSupport::SecurityUtils.secure_compare(params[:token], Gitlab::CurrentSettings.runners_registration_token)
|
||||
end
|
||||
|
||||
def runner_registrar_valid?(type)
|
||||
Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
|
||||
end
|
||||
|
||||
def authenticate_runner!
|
||||
forbidden! unless current_runner
|
||||
|
||||
|
|
|
@ -28,21 +28,8 @@ module API
|
|||
attributes = attributes_for_keys([:description, :active, :locked, :run_untagged, :tag_list, :access_level, :maximum_timeout])
|
||||
.merge(get_runner_details_from_request)
|
||||
|
||||
attributes =
|
||||
if runner_registration_token_valid?
|
||||
# Create shared runner. Requires admin access
|
||||
attributes.merge(runner_type: :instance_type)
|
||||
elsif runner_registrar_valid?('project') && @project = Project.find_by_runners_token(params[:token])
|
||||
# Create a specific runner for the project
|
||||
attributes.merge(runner_type: :project_type, projects: [@project])
|
||||
elsif runner_registrar_valid?('group') && @group = Group.find_by_runners_token(params[:token])
|
||||
# Create a specific runner for the group
|
||||
attributes.merge(runner_type: :group_type, groups: [@group])
|
||||
else
|
||||
forbidden!
|
||||
end
|
||||
|
||||
@runner = ::Ci::Runner.create(attributes)
|
||||
@runner = ::Ci::RegisterRunnerService.new.execute(params[:token], attributes)
|
||||
forbidden! unless @runner
|
||||
|
||||
if @runner.persisted?
|
||||
present @runner, with: Entities::Ci::RunnerRegistrationDetails
|
||||
|
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
# We _must not_ use quote_table_name as this will produce double
|
||||
# quotes on PostgreSQL and for "has_table_privilege" we need single
|
||||
# quotes.
|
||||
connection = ActiveRecord::Base.connection # rubocop: disable Database/MultipleDatabases
|
||||
connection = ApplicationRecord.connection
|
||||
quoted_table = connection.quote(table)
|
||||
|
||||
begin
|
||||
|
|
|
@ -104,11 +104,9 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Database/MultipleDatabases
|
||||
def connection
|
||||
use_model_load_balancing? ? super : ActiveRecord::Base.connection
|
||||
use_model_load_balancing? ? super : ApplicationRecord.connection
|
||||
end
|
||||
# rubocop:enable Database/MultipleDatabases
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -188,6 +188,24 @@ describe('IDE pipelines actions', () => {
|
|||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets latest pipeline to `null` and stops polling on empty project', (done) => {
|
||||
mockedState = {
|
||||
...mockedState,
|
||||
rootGetters: {
|
||||
lastCommit: null,
|
||||
},
|
||||
};
|
||||
|
||||
testAction(
|
||||
fetchLatestPipeline,
|
||||
{},
|
||||
mockedState,
|
||||
[{ type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: null }],
|
||||
[{ type: 'stopPipelinePolling' }],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestJobs', () => {
|
||||
|
|
|
@ -58,6 +58,7 @@ issues:
|
|||
- test_reports
|
||||
- requirement
|
||||
- incident_management_issuable_escalation_status
|
||||
- incident_management_timeline_events
|
||||
- pending_escalations
|
||||
- customer_relations_contacts
|
||||
- issue_customer_relations_contacts
|
||||
|
|
|
@ -62,7 +62,7 @@ RSpec.describe Gitlab::Metrics::Samplers::DatabaseSampler do
|
|||
end
|
||||
|
||||
context 'when replica hosts are configured' do
|
||||
let(:main_load_balancer) { ActiveRecord::Base.load_balancer } # rubocop:disable Database/MultipleDatabases
|
||||
let(:main_load_balancer) { ApplicationRecord.load_balancer }
|
||||
let(:main_replica_host) { main_load_balancer.host }
|
||||
|
||||
let(:ci_load_balancer) { double(:load_balancer, host_list: ci_host_list, configuration: configuration) }
|
||||
|
@ -117,7 +117,7 @@ RSpec.describe Gitlab::Metrics::Samplers::DatabaseSampler do
|
|||
end
|
||||
|
||||
context 'when the base model has replica connections' do
|
||||
let(:main_load_balancer) { ActiveRecord::Base.load_balancer } # rubocop:disable Database/MultipleDatabases
|
||||
let(:main_load_balancer) { ApplicationRecord.load_balancer }
|
||||
let(:main_replica_host) { main_load_balancer.host }
|
||||
|
||||
let(:ci_load_balancer) { double(:load_balancer, host_list: ci_host_list, configuration: configuration) }
|
||||
|
|
|
@ -147,22 +147,20 @@ RSpec.describe ApplicationRecord do
|
|||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Database/MultipleDatabases
|
||||
it 'increments a counter when a transaction is created in ActiveRecord' do
|
||||
expect(described_class.connection.transaction_open?).to be false
|
||||
|
||||
expect(::Gitlab::Database::Metrics)
|
||||
.to receive(:subtransactions_increment)
|
||||
.with('ActiveRecord::Base')
|
||||
.with('ApplicationRecord')
|
||||
.once
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
ActiveRecord::Base.transaction(requires_new: true) do
|
||||
expect(ActiveRecord::Base.connection.transaction_open?).to be true
|
||||
ApplicationRecord.transaction do
|
||||
ApplicationRecord.transaction(requires_new: true) do
|
||||
expect(ApplicationRecord.connection.transaction_open?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Database/MultipleDatabases
|
||||
end
|
||||
|
||||
describe '.with_fast_read_statement_timeout' do
|
||||
|
|
|
@ -87,7 +87,7 @@ RSpec.describe InternalId do
|
|||
|
||||
context 'when executed outside of transaction' do
|
||||
it 'increments counter with in_transaction: "false"' do
|
||||
allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } # rubocop: disable Database/MultipleDatabases
|
||||
allow(ApplicationRecord.connection).to receive(:transaction_open?) { false }
|
||||
|
||||
expect(InternalId.internal_id_transactions_total).to receive(:increment)
|
||||
.with(operation: :generate, usage: 'issues', in_transaction: 'false').and_call_original
|
||||
|
@ -146,7 +146,7 @@ RSpec.describe InternalId do
|
|||
let(:value) { 2 }
|
||||
|
||||
it 'increments counter with in_transaction: "false"' do
|
||||
allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } # rubocop: disable Database/MultipleDatabases
|
||||
allow(ApplicationRecord.connection).to receive(:transaction_open?) { false }
|
||||
|
||||
expect(InternalId.internal_id_transactions_total).to receive(:increment)
|
||||
.with(operation: :reset, usage: 'issues', in_transaction: 'false').and_call_original
|
||||
|
@ -217,7 +217,7 @@ RSpec.describe InternalId do
|
|||
|
||||
context 'when executed outside of transaction' do
|
||||
it 'increments counter with in_transaction: "false"' do
|
||||
allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } # rubocop: disable Database/MultipleDatabases
|
||||
allow(ApplicationRecord.connection).to receive(:transaction_open?) { false }
|
||||
|
||||
expect(InternalId.internal_id_transactions_total).to receive(:increment)
|
||||
.with(operation: :track_greatest, usage: 'issues', in_transaction: 'false').and_call_original
|
||||
|
|
|
@ -3,21 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
||||
include StubGitlabCalls
|
||||
include RedisHelpers
|
||||
include WorkhorseHelpers
|
||||
|
||||
let(:registration_token) { 'abcdefg123456' }
|
||||
|
||||
before do
|
||||
stub_feature_flags(ci_enable_live_trace: true)
|
||||
stub_feature_flags(runner_registration_control: false)
|
||||
stub_gitlab_calls
|
||||
stub_application_setting(runners_registration_token: registration_token)
|
||||
stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES)
|
||||
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
|
||||
end
|
||||
|
||||
describe '/api/v4/runners' do
|
||||
describe 'POST /api/v4/runners' do
|
||||
context 'when no token is provided' do
|
||||
|
@ -30,380 +15,106 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
|||
|
||||
context 'when invalid token is provided' do
|
||||
it 'returns 403 error' do
|
||||
allow_next_instance_of(::Ci::RegisterRunnerService) do |service|
|
||||
allow(service).to receive(:execute).and_return(nil)
|
||||
end
|
||||
|
||||
post api('/runners'), params: { token: 'invalid' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid token is provided' do
|
||||
context 'when valid parameters are provided' do
|
||||
def request
|
||||
post api('/runners'), params: { token: token }
|
||||
post api('/runners'), params: {
|
||||
token: 'valid token',
|
||||
description: 'server.hostname',
|
||||
run_untagged: false,
|
||||
tag_list: 'tag1, tag2',
|
||||
locked: true,
|
||||
active: true,
|
||||
access_level: 'ref_protected',
|
||||
maximum_timeout: 9000
|
||||
}
|
||||
end
|
||||
|
||||
context 'with a registration token' do
|
||||
let(:token) { registration_token }
|
||||
let_it_be(:new_runner) { create(:ci_runner) }
|
||||
|
||||
it 'creates runner with default values' do
|
||||
request
|
||||
before do
|
||||
allow_next_instance_of(::Ci::RegisterRunnerService) do |service|
|
||||
expected_params = {
|
||||
description: 'server.hostname',
|
||||
run_untagged: false,
|
||||
tag_list: %w(tag1 tag2),
|
||||
locked: true,
|
||||
active: true,
|
||||
access_level: 'ref_protected',
|
||||
maximum_timeout: 9000
|
||||
}.stringify_keys
|
||||
|
||||
runner = ::Ci::Runner.first
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['id']).to eq(runner.id)
|
||||
expect(json_response['token']).to eq(runner.token)
|
||||
expect(runner.run_untagged).to be true
|
||||
expect(runner.active).to be true
|
||||
expect(runner.token).not_to eq(registration_token)
|
||||
expect(runner).to be_instance_type
|
||||
end
|
||||
|
||||
it_behaves_like 'storing arguments in the application context for the API' do
|
||||
subject { request }
|
||||
|
||||
let(:expected_params) { { client_id: "runner/#{::Ci::Runner.first.id}" } }
|
||||
end
|
||||
|
||||
it_behaves_like 'not executing any extra queries for the application context' do
|
||||
let(:subject_proc) { proc { request } }
|
||||
allow(service).to receive(:execute)
|
||||
.once
|
||||
.with('valid token', a_hash_including(expected_params))
|
||||
.and_return(new_runner)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project token is used' do
|
||||
let(:project) { create(:project) }
|
||||
let(:token) { project.runners_token }
|
||||
it 'creates runner' do
|
||||
request
|
||||
|
||||
it 'creates project runner' do
|
||||
request
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['id']).to eq(new_runner.id)
|
||||
expect(json_response['token']).to eq(new_runner.token)
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(project.runners.size).to eq(1)
|
||||
runner = ::Ci::Runner.first
|
||||
expect(runner.token).not_to eq(registration_token)
|
||||
expect(runner.token).not_to eq(project.runners_token)
|
||||
expect(runner).to be_project_type
|
||||
end
|
||||
it_behaves_like 'storing arguments in the application context for the API' do
|
||||
subject { request }
|
||||
|
||||
it_behaves_like 'storing arguments in the application context for the API' do
|
||||
subject { request }
|
||||
let(:expected_params) { { client_id: "runner/#{new_runner.id}" } }
|
||||
end
|
||||
|
||||
let(:expected_params) { { project: project.full_path, client_id: "runner/#{::Ci::Runner.first.id}" } }
|
||||
end
|
||||
it_behaves_like 'not executing any extra queries for the application context' do
|
||||
let(:subject_proc) { proc { request } }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'not executing any extra queries for the application context' do
|
||||
let(:subject_proc) { proc { request } }
|
||||
end
|
||||
context 'calling actual register service' do
|
||||
include StubGitlabCalls
|
||||
|
||||
context 'when it exceeds the application limits' do
|
||||
before do
|
||||
create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago)
|
||||
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
|
||||
end
|
||||
let(:registration_token) { 'abcdefg123456' }
|
||||
|
||||
it 'does not create runner' do
|
||||
request
|
||||
before do
|
||||
stub_gitlab_calls
|
||||
stub_application_setting(runners_registration_token: registration_token)
|
||||
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']).to include('runner_projects.base' => ['Maximum number of ci registered project runners (1) exceeded'])
|
||||
expect(project.runners.reload.size).to eq(1)
|
||||
end
|
||||
end
|
||||
%w(name version revision platform architecture).each do |param|
|
||||
context "when info parameter '#{param}' info is present" do
|
||||
let(:value) { "#{param}_value" }
|
||||
|
||||
context 'when abandoned runners cause application limits to not be exceeded' do
|
||||
before do
|
||||
create(:ci_runner, runner_type: :project_type, projects: [project], created_at: 14.months.ago, contacted_at: 13.months.ago)
|
||||
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
|
||||
end
|
||||
|
||||
it 'creates runner' do
|
||||
request
|
||||
it "updates provided Runner's parameter" do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
info: { param => value }
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['message']).to be_nil
|
||||
expect(project.runners.reload.size).to eq(2)
|
||||
expect(project.runners.recent.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid runner registrars do not include project' do
|
||||
before do
|
||||
stub_application_setting(valid_runner_registrars: ['group'])
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(runner_registration_control: true)
|
||||
end
|
||||
|
||||
it 'returns 403 error' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
it 'registers the runner' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.active).to be true
|
||||
end
|
||||
expect(::Ci::Runner.last.read_attribute(param.to_sym)).to eq(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when group token is used' do
|
||||
let(:group) { create(:group) }
|
||||
let(:token) { group.runners_token }
|
||||
|
||||
it 'creates a group runner' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(group.runners.reload.size).to eq(1)
|
||||
runner = ::Ci::Runner.first
|
||||
expect(runner.token).not_to eq(registration_token)
|
||||
expect(runner.token).not_to eq(group.runners_token)
|
||||
expect(runner).to be_group_type
|
||||
end
|
||||
|
||||
it_behaves_like 'storing arguments in the application context for the API' do
|
||||
subject { request }
|
||||
|
||||
let(:expected_params) { { root_namespace: group.full_path_components.first, client_id: "runner/#{::Ci::Runner.first.id}" } }
|
||||
end
|
||||
|
||||
it_behaves_like 'not executing any extra queries for the application context' do
|
||||
let(:subject_proc) { proc { request } }
|
||||
end
|
||||
|
||||
context 'when it exceeds the application limits' do
|
||||
before do
|
||||
create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 1.month.ago)
|
||||
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
|
||||
end
|
||||
|
||||
it 'does not create runner' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']).to include('runner_namespaces.base' => ['Maximum number of ci registered group runners (1) exceeded'])
|
||||
expect(group.runners.reload.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when abandoned runners cause application limits to not be exceeded' do
|
||||
before do
|
||||
create(:ci_runner, runner_type: :group_type, groups: [group], created_at: 4.months.ago, contacted_at: 3.months.ago)
|
||||
create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 4.months.ago)
|
||||
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
|
||||
end
|
||||
|
||||
it 'creates runner' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['message']).to be_nil
|
||||
expect(group.runners.reload.size).to eq(3)
|
||||
expect(group.runners.recent.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid runner registrars do not include group' do
|
||||
before do
|
||||
stub_application_setting(valid_runner_registrars: ['project'])
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(runner_registration_control: true)
|
||||
end
|
||||
|
||||
it 'returns 403 error' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
it 'registers the runner' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.active).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runner description is provided' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
description: 'server.hostname'
|
||||
}
|
||||
it "sets the runner's ip_address" do
|
||||
post api('/runners'),
|
||||
params: { token: registration_token },
|
||||
headers: { 'X-Forwarded-For' => '123.111.123.111' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.description).to eq('server.hostname')
|
||||
expect(::Ci::Runner.last.ip_address).to eq('123.111.123.111')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when runner tags are provided' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
tag_list: 'tag1, tag2'
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when option for running untagged jobs is provided' do
|
||||
context 'when tags are provided' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
run_untagged: false,
|
||||
tag_list: ['tag']
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.run_untagged).to be false
|
||||
expect(::Ci::Runner.first.tag_list.sort).to eq(['tag'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tags are not provided' do
|
||||
it 'returns 400 error' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
run_untagged: false
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']).to include(
|
||||
'tags_list' => ['can not be empty when runner is not allowed to pick untagged jobs'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when option for locking Runner is provided' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
locked: true
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.locked).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when option for activating a Runner is provided' do
|
||||
context 'when active is set to true' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
active: true
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.active).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when active is set to false' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
active: false
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.active).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when access_level is provided for Runner' do
|
||||
context 'when access_level is set to ref_protected' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
access_level: 'ref_protected'
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.ref_protected?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when access_level is set to not_protected' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
access_level: 'not_protected'
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.ref_protected?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when maximum job timeout is specified' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
maximum_timeout: 9000
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.maximum_timeout).to eq(9000)
|
||||
end
|
||||
|
||||
context 'when maximum job timeout is empty' do
|
||||
it 'creates runner' do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
maximum_timeout: ''
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.maximum_timeout).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%w(name version revision platform architecture).each do |param|
|
||||
context "when info parameter '#{param}' info is present" do
|
||||
let(:value) { "#{param}_value" }
|
||||
|
||||
it "updates provided Runner's parameter" do
|
||||
post api('/runners'), params: {
|
||||
token: registration_token,
|
||||
info: { param => value }
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "sets the runner's ip_address" do
|
||||
post api('/runners'),
|
||||
params: { token: registration_token },
|
||||
headers: { 'X-Forwarded-For' => '123.111.123.111' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(::Ci::Runner.first.ip_address).to eq('123.111.123.111')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
226
spec/services/ci/register_runner_service_spec.rb
Normal file
226
spec/services/ci/register_runner_service_spec.rb
Normal file
|
@ -0,0 +1,226 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ::Ci::RegisterRunnerService do
|
||||
let(:registration_token) { 'abcdefg123456' }
|
||||
|
||||
before do
|
||||
stub_feature_flags(runner_registration_control: false)
|
||||
stub_application_setting(runners_registration_token: registration_token)
|
||||
stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let(:token) { }
|
||||
let(:args) { {} }
|
||||
|
||||
subject { described_class.new.execute(token, args) }
|
||||
|
||||
context 'when no token is provided' do
|
||||
let(:token) { '' }
|
||||
|
||||
it 'returns nil' do
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid token is provided' do
|
||||
let(:token) { 'invalid' }
|
||||
|
||||
it 'returns nil' do
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid token is provided' do
|
||||
context 'with a registration token' do
|
||||
let(:token) { registration_token }
|
||||
|
||||
it 'creates runner with default values' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.persisted?).to be_truthy
|
||||
expect(subject.run_untagged).to be true
|
||||
expect(subject.active).to be true
|
||||
expect(subject.token).not_to eq(registration_token)
|
||||
expect(subject).to be_instance_type
|
||||
end
|
||||
|
||||
context 'with non-default arguments' do
|
||||
let(:args) do
|
||||
{
|
||||
description: 'some description',
|
||||
active: false,
|
||||
locked: true,
|
||||
run_untagged: false,
|
||||
tag_list: %w(tag1 tag2),
|
||||
access_level: 'ref_protected',
|
||||
maximum_timeout: 600,
|
||||
name: 'some name',
|
||||
version: 'some version',
|
||||
revision: 'some revision',
|
||||
platform: 'some platform',
|
||||
architecture: 'some architecture',
|
||||
ip_address: '10.0.0.1',
|
||||
config: {
|
||||
gpus: 'some gpu config'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates runner with specified values', :aggregate_failures do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.active).to eq args[:active]
|
||||
expect(subject.locked).to eq args[:locked]
|
||||
expect(subject.run_untagged).to eq args[:run_untagged]
|
||||
expect(subject.tags).to contain_exactly(
|
||||
an_object_having_attributes(name: 'tag1'),
|
||||
an_object_having_attributes(name: 'tag2')
|
||||
)
|
||||
expect(subject.access_level).to eq args[:access_level]
|
||||
expect(subject.maximum_timeout).to eq args[:maximum_timeout]
|
||||
expect(subject.name).to eq args[:name]
|
||||
expect(subject.version).to eq args[:version]
|
||||
expect(subject.revision).to eq args[:revision]
|
||||
expect(subject.platform).to eq args[:platform]
|
||||
expect(subject.architecture).to eq args[:architecture]
|
||||
expect(subject.ip_address).to eq args[:ip_address]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project token is used' do
|
||||
let(:project) { create(:project) }
|
||||
let(:token) { project.runners_token }
|
||||
|
||||
it 'creates project runner' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(project.runners.size).to eq(1)
|
||||
is_expected.to eq(project.runners.first)
|
||||
expect(subject.token).not_to eq(registration_token)
|
||||
expect(subject.token).not_to eq(project.runners_token)
|
||||
expect(subject).to be_project_type
|
||||
end
|
||||
|
||||
context 'when it exceeds the application limits' do
|
||||
before do
|
||||
create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago)
|
||||
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
|
||||
end
|
||||
|
||||
it 'does not create runner' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.persisted?).to be_falsey
|
||||
expect(subject.errors.messages).to eq('runner_projects.base': ['Maximum number of ci registered project runners (1) exceeded'])
|
||||
expect(project.runners.reload.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when abandoned runners cause application limits to not be exceeded' do
|
||||
before do
|
||||
create(:ci_runner, runner_type: :project_type, projects: [project], created_at: 14.months.ago, contacted_at: 13.months.ago)
|
||||
create(:plan_limits, :default_plan, ci_registered_project_runners: 1)
|
||||
end
|
||||
|
||||
it 'creates runner' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.errors).to be_empty
|
||||
expect(project.runners.reload.size).to eq(2)
|
||||
expect(project.runners.recent.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid runner registrars do not include project' do
|
||||
before do
|
||||
stub_application_setting(valid_runner_registrars: ['group'])
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(runner_registration_control: true)
|
||||
end
|
||||
|
||||
it 'returns 403 error' do
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
it 'registers the runner' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.errors).to be_empty
|
||||
expect(subject.active).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when group token is used' do
|
||||
let(:group) { create(:group) }
|
||||
let(:token) { group.runners_token }
|
||||
|
||||
it 'creates a group runner' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.errors).to be_empty
|
||||
expect(group.runners.reload.size).to eq(1)
|
||||
expect(subject.token).not_to eq(registration_token)
|
||||
expect(subject.token).not_to eq(group.runners_token)
|
||||
expect(subject).to be_group_type
|
||||
end
|
||||
|
||||
context 'when it exceeds the application limits' do
|
||||
before do
|
||||
create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 1.month.ago)
|
||||
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
|
||||
end
|
||||
|
||||
it 'does not create runner' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.persisted?).to be_falsey
|
||||
expect(subject.errors.messages).to eq('runner_namespaces.base': ['Maximum number of ci registered group runners (1) exceeded'])
|
||||
expect(group.runners.reload.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when abandoned runners cause application limits to not be exceeded' do
|
||||
before do
|
||||
create(:ci_runner, runner_type: :group_type, groups: [group], created_at: 4.months.ago, contacted_at: 3.months.ago)
|
||||
create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 4.months.ago)
|
||||
create(:plan_limits, :default_plan, ci_registered_group_runners: 1)
|
||||
end
|
||||
|
||||
it 'creates runner' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.errors).to be_empty
|
||||
expect(group.runners.reload.size).to eq(3)
|
||||
expect(group.runners.recent.size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid runner registrars do not include group' do
|
||||
before do
|
||||
stub_application_setting(valid_runner_registrars: ['project'])
|
||||
end
|
||||
|
||||
context 'when feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(runner_registration_control: true)
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
it 'registers the runner' do
|
||||
is_expected.to be_an_instance_of(::Ci::Runner)
|
||||
expect(subject.errors).to be_empty
|
||||
expect(subject.active).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,24 +18,29 @@ RSpec.describe ::Import::GitlabProjects::CreateProjectFromRemoteFileService do
|
|||
|
||||
subject { described_class.new(user, params) }
|
||||
|
||||
it 'creates a project and returns a successful response' do
|
||||
stub_headers_for(remote_url, {
|
||||
'content-type' => 'application/gzip',
|
||||
'content-length' => '10'
|
||||
})
|
||||
shared_examples 'successfully import' do |content_type|
|
||||
it 'creates a project and returns a successful response' do
|
||||
stub_headers_for(remote_url, {
|
||||
'content-type' => content_type,
|
||||
'content-length' => '10'
|
||||
})
|
||||
|
||||
response = nil
|
||||
expect { response = subject.execute }
|
||||
.to change(Project, :count).by(1)
|
||||
response = nil
|
||||
expect { response = subject.execute }
|
||||
.to change(Project, :count).by(1)
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.http_status).to eq(:ok)
|
||||
expect(response.payload).to be_instance_of(Project)
|
||||
expect(response.payload.name).to eq('name')
|
||||
expect(response.payload.path).to eq('path')
|
||||
expect(response.payload.namespace).to eq(user.namespace)
|
||||
expect(response).to be_success
|
||||
expect(response.http_status).to eq(:ok)
|
||||
expect(response.payload).to be_instance_of(Project)
|
||||
expect(response.payload.name).to eq('name')
|
||||
expect(response.payload.path).to eq('path')
|
||||
expect(response.payload.namespace).to eq(user.namespace)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'successfully import', 'application/gzip'
|
||||
it_behaves_like 'successfully import', 'application/x-tar'
|
||||
|
||||
context 'when the file url is invalid' do
|
||||
it 'returns an erred response with the reason of the failure' do
|
||||
stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
|
||||
|
@ -79,7 +84,7 @@ RSpec.describe ::Import::GitlabProjects::CreateProjectFromRemoteFileService do
|
|||
expect(response).not_to be_success
|
||||
expect(response.http_status).to eq(:bad_request)
|
||||
expect(response.message)
|
||||
.to eq("Remote file content type 'application/js' not allowed. (Allowed content types: application/gzip)")
|
||||
.to eq("Remote file content type 'application/js' not allowed. (Allowed content types: application/gzip, application/x-tar)")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -130,6 +135,20 @@ RSpec.describe ::Import::GitlabProjects::CreateProjectFromRemoteFileService do
|
|||
end
|
||||
end
|
||||
|
||||
it 'does not validate content-type or content-length when the file is stored in AWS-S3' do
|
||||
stub_headers_for(remote_url, {
|
||||
'Server' => 'AmazonS3',
|
||||
'x-amz-request-id' => 'Something'
|
||||
})
|
||||
|
||||
response = nil
|
||||
expect { response = subject.execute }
|
||||
.to change(Project, :count)
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.http_status).to eq(:ok)
|
||||
end
|
||||
|
||||
context 'when required parameters are not provided' do
|
||||
let(:params) { {} }
|
||||
|
||||
|
|
|
@ -124,6 +124,12 @@ RSpec.shared_examples 'namespace traversal scopes' do
|
|||
it { expect(subject[0, 2]).to contain_exactly(group_1, group_2) }
|
||||
it { expect(subject[2, 2]).to contain_exactly(nested_group_1, nested_group_2) }
|
||||
end
|
||||
|
||||
context 'with offset and limit' do
|
||||
subject { described_class.where(id: [deep_nested_group_1, deep_nested_group_2]).offset(1).limit(1).self_and_ancestors }
|
||||
|
||||
it { is_expected.to contain_exactly(group_2, nested_group_2, deep_nested_group_2) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.self_and_ancestors' do
|
||||
|
@ -168,6 +174,19 @@ RSpec.shared_examples 'namespace traversal scopes' do
|
|||
|
||||
it { is_expected.to contain_exactly(group_1.id, group_2.id) }
|
||||
end
|
||||
|
||||
context 'with offset and limit' do
|
||||
subject do
|
||||
described_class
|
||||
.where(id: [deep_nested_group_1, deep_nested_group_2])
|
||||
.limit(1)
|
||||
.offset(1)
|
||||
.self_and_ancestor_ids
|
||||
.pluck(:id)
|
||||
end
|
||||
|
||||
it { is_expected.to contain_exactly(group_2.id, nested_group_2.id, deep_nested_group_2.id) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.self_and_ancestor_ids' do
|
||||
|
|
|
@ -42,7 +42,7 @@ RSpec.describe 'Database::MultipleDatabases' do
|
|||
context 'when reconnect is true' do
|
||||
it 'does not raise exception' do
|
||||
with_reestablished_active_record_base(reconnect: true) do
|
||||
expect { ActiveRecord::Base.connection.execute("SELECT 1") }.not_to raise_error # rubocop:disable Database/MultipleDatabases
|
||||
expect { ApplicationRecord.connection.execute("SELECT 1") }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -50,7 +50,7 @@ RSpec.describe 'Database::MultipleDatabases' do
|
|||
context 'when reconnect is false' do
|
||||
it 'does raise exception' do
|
||||
with_reestablished_active_record_base(reconnect: false) do
|
||||
expect { ActiveRecord::Base.connection.execute("SELECT 1") }.to raise_error(ActiveRecord::ConnectionNotEstablished) # rubocop:disable Database/MultipleDatabases
|
||||
expect { ApplicationRecord.connection.execute("SELECT 1") }.to raise_error(ActiveRecord::ConnectionNotEstablished)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue