Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-20 12:10:29 +00:00
parent 063d4b1caa
commit 62c78157be
27 changed files with 798 additions and 433 deletions

View file

@ -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({

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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?

View file

@ -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.

View 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:

View file

@ -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": []
}
}
}
```

View file

@ -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).

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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', () => {

View file

@ -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

View file

@ -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) }

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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) { {} }

View file

@ -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

View file

@ -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