Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-28 12:17:26 +00:00
parent fa4a0663e8
commit d6ce16a407
50 changed files with 603 additions and 143 deletions

View file

@ -1 +1 @@
b7f0c0462a8f689c8ee9e654f0875157b238158b 69baea2ed2c593ed570b614f1c311d8ee80a1c33

View file

@ -3,6 +3,7 @@ import {
GlTooltipDirective, GlTooltipDirective,
GlSafeHtmlDirective, GlSafeHtmlDirective,
GlIcon, GlIcon,
GlBadge,
GlButton, GlButton,
GlButtonGroup, GlButtonGroup,
GlDropdown, GlDropdown,
@ -34,6 +35,7 @@ export default {
GlIcon, GlIcon,
FileIcon, FileIcon,
DiffStats, DiffStats,
GlBadge,
GlButton, GlButton,
GlButtonGroup, GlButtonGroup,
GlDropdown, GlDropdown,
@ -349,7 +351,9 @@ export default {
{{ diffFile.a_mode }} {{ diffFile.b_mode }} {{ diffFile.a_mode }} {{ diffFile.b_mode }}
</small> </small>
<span v-if="isUsingLfs" class="badge label label-lfs gl-mr-2"> {{ __('LFS') }} </span> <gl-badge v-if="isUsingLfs" variant="neutral" class="gl-mr-2" data-testid="label-lfs">{{
__('LFS')
}}</gl-badge>
</div> </div>
<div <div

View file

@ -267,6 +267,8 @@
.nav-item-name { .nav-item-name {
flex: 1; flex: 1;
overflow: hidden;
text-overflow: ellipsis;
} }
> a, > a,

View file

@ -411,11 +411,6 @@ span.idiff {
margin-right: 1.5em; margin-right: 1.5em;
} }
.label-lfs {
color: $common-gray-light;
border: 1px solid $common-gray-light;
}
.preview-container { .preview-container {
overflow: auto; overflow: auto;

View file

@ -1095,6 +1095,8 @@ input {
} }
.nav-sidebar li .nav-item-name { .nav-sidebar li .nav-item-name {
flex: 1; flex: 1;
overflow: hidden;
text-overflow: ellipsis;
} }
.nav-sidebar li > a, .nav-sidebar li > a,
.nav-sidebar li > .fly-out-top-item-container { .nav-sidebar li > .fly-out-top-item-container {

View file

@ -1076,6 +1076,8 @@ input {
} }
.nav-sidebar li .nav-item-name { .nav-sidebar li .nav-item-name {
flex: 1; flex: 1;
overflow: hidden;
text-overflow: ellipsis;
} }
.nav-sidebar li > a, .nav-sidebar li > a,
.nav-sidebar li > .fly-out-top-item-container { .nav-sidebar li > .fly-out-top-item-container {

View file

@ -28,7 +28,12 @@ module Mutations
argument :active, GraphQL::Types::Boolean, argument :active, GraphQL::Types::Boolean,
required: false, required: false,
description: 'Indicates the runner is allowed to receive jobs.' description: 'Indicates the runner is allowed to receive jobs.',
deprecated: { reason: :renamed, replacement: 'paused', milestone: '14.8' }
argument :paused, GraphQL::Types::Boolean,
required: false,
description: 'Indicates the runner is not allowed to receive jobs.'
argument :locked, GraphQL::Types::Boolean, required: false, argument :locked, GraphQL::Types::Boolean, required: false,
description: 'Indicates the runner is locked.' description: 'Indicates the runner is locked.'

View file

@ -3,6 +3,8 @@
module Ci module Ci
class PipelineScheduleService < BaseService class PipelineScheduleService < BaseService
def execute(schedule) def execute(schedule)
return unless project.persisted?
# Ensure `next_run_at` is set properly before creating a pipeline. # Ensure `next_run_at` is set properly before creating a pipeline.
# Otherwise, multiple pipelines could be created in a short interval. # Otherwise, multiple pipelines could be created in a short interval.
schedule.schedule_next_run! schedule.schedule_next_run!

View file

@ -9,6 +9,8 @@ module Ci
end end
def update(params) def update(params)
params[:active] = !params.delete(:paused) if params.include?(:paused)
runner.update(params).tap do |updated| runner.update(params).tap do |updated|
runner.tick_runner_queue if updated runner.tick_runner_queue if updated
end end

View file

@ -5,6 +5,7 @@ module GoogleCloud
def execute def execute
service_account = google_api_client.create_service_account(gcp_project_id, service_account_name, service_account_desc) service_account = google_api_client.create_service_account(gcp_project_id, service_account_name, service_account_desc)
service_account_key = google_api_client.create_service_account_key(gcp_project_id, service_account.unique_id) service_account_key = google_api_client.create_service_account_key(gcp_project_id, service_account.unique_id)
google_api_client.grant_service_account_roles(gcp_project_id, service_account.email)
service_accounts_service.add_for_project( service_accounts_service.add_for_project(
environment_name, environment_name,
@ -35,7 +36,7 @@ module GoogleCloud
end end
def google_api_client def google_api_client
GoogleApi::CloudPlatform::Client.new(google_oauth2_token, nil) @google_api_client_instance ||= GoogleApi::CloudPlatform::Client.new(google_oauth2_token, nil)
end end
def service_accounts_service def service_accounts_service
@ -50,7 +51,7 @@ module GoogleCloud
"GitLab generated service account for project '#{project.name}' and environment '#{environment_name}'" "GitLab generated service account for project '#{project.name}' and environment '#{environment_name}'"
end end
# Overriden in EE # Overridden in EE
def environment_protected? def environment_protected?
false false
end end

View file

@ -14,4 +14,4 @@
= number_to_human_size(blob.raw_size) = number_to_human_size(blob.raw_size)
- if blob.stored_externally? && blob.external_storage == :lfs - if blob.stored_externally? && blob.external_storage == :lfs
%span.badge.label-lfs.gl-mr-2 LFS = gl_badge_tag(_('LFS'), variant: :neutral)

View file

@ -22,13 +22,7 @@ module Ci
end end
def max_running_jobs def max_running_jobs
if ::Feature.enabled?(:ci_delete_objects_medium_concurrency) 20
20
elsif ::Feature.enabled?(:ci_delete_objects_high_concurrency)
50
else
2
end
end end
private private

View file

@ -13,6 +13,8 @@ class PipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker
def perform def perform
Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules| Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules|
schedules.each do |schedule| schedules.each do |schedule|
next unless schedule.project
with_context(project: schedule.project, user: schedule.owner) do with_context(project: schedule.project, user: schedule.owner) do
Ci::PipelineScheduleService.new(schedule.project, schedule.owner).execute(schedule) Ci::PipelineScheduleService.new(schedule.project, schedule.owner).execute(schedule)
end end

View file

@ -15,7 +15,7 @@ class RunPipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker
schedule = Ci::PipelineSchedule.find_by_id(schedule_id) schedule = Ci::PipelineSchedule.find_by_id(schedule_id)
user = User.find_by_id(user_id) user = User.find_by_id(user_id)
return unless schedule && user return unless schedule && schedule.project && user
run_pipeline_schedule(schedule, user) run_pipeline_schedule(schedule, user)
end end

View file

@ -1,8 +1,8 @@
--- ---
name: ci_delete_objects_medium_concurrency name: project_import_schedule_worker_job_tracker
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39464 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79097
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247103 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351408
milestone: '13.5' milestone: '14.8'
type: development type: development
group: group::pipeline execution group: group::scalability
default_enabled: false default_enabled: false

View file

@ -1,8 +1,8 @@
--- ---
name: ci_delete_objects_high_concurrency name: update_all_mirrors_job_tracker
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39464 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79097
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247103 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351420
milestone: '13.5' milestone: '14.8'
type: development type: development
group: group::pipeline execution group: group::scalability
default_enabled: false default_enabled: false

View file

@ -1,4 +1,4 @@
- name: "REST API Runner will not contain `paused`" - name: "REST and GraphQL API Runner status will not return `paused`"
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated. announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-11-22" announcement_date: "2021-11-22"
removal_milestone: "15.0" # the milestone when this feature is planned to be removed removal_milestone: "15.0" # the milestone when this feature is planned to be removed
@ -11,7 +11,7 @@
`online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear. `online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear.
When checking if a runner is `paused`, API users are advised to check the boolean attribute When checking if a runner is `paused`, API users are advised to check the boolean attribute
`active` to be `false` instead. When checking if a runner is `active`, check if `active` is `true`. `paused` to be `true` instead. When checking if a runner is `active`, check if `paused` is `false`.
stage: Verify stage: Verify
tiers: [Core, Premium, Ultimate] tiers: [Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648 issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648

View file

@ -0,0 +1,33 @@
- name: "REST and GraphQL API Runner usage of `active` replaced by `paused`"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-05-22"
breaking_change: true
reporter: pedropombeiro
body: |
Occurrences of the `active` identifier in the GitLab Runner REST and GraphQL API endpoints will be
renamed to `paused` in GitLab 15.0, namely:
- GraphQL API:
- the `CiRunner` property;
- the `RunnerUpdateInput` input type for the `runnerUpdate` mutation;
- the `runners` and `Group.runners` queries.
- REST API:
- endpoints taking or returning `active` properties, such as:
- `GET /runners`
- `GET /runners/all`
- `GET /runners/:id` / `PUT /runners/:id`
- `PUT --form "active=false" /runners/:runner_id`
- `GET /projects/:id/runners` / `POST /projects/:id/runners`
- `GET /groups/:id/runners`
The 15.0 release of the GitLab Runner will start using the `paused` property when registering runners, and therefore
will only be compatible with GitLab 15.0 and later. Until 15.0, GitLab will accept the deprecated `active` flag from
existing runners.
stage: Verify
tiers: [Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347211
documentation_url: https://docs.gitlab.com/ee/api/runners.html
image_url: # (optional) This is a link to a thumbnail image depicting the feature
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg

View file

@ -0,0 +1,71 @@
# frozen_string_literal: true
class DedupRunnerProjects < Gitlab::Database::Migration[1.0]
TABLE_NAME = :ci_runner_projects
TMP_INDEX_NAME = 'tmp_unique_ci_runner_projects_by_runner_id_and_project_id'
OLD_INDEX_NAME = 'index_ci_runner_projects_on_runner_id_and_project_id'
INDEX_NAME = 'index_unique_ci_runner_projects_on_runner_id_and_project_id'
BATCH_SIZE = 5000
disable_ddl_transaction!
module Ci
class RunnerProject < ActiveRecord::Base
include EachBatch
self.table_name = 'ci_runner_projects'
end
end
def up
last_runner_project_record_id = Ci::RunnerProject.maximum(:id) || 0
# This index will disallow further duplicates while we're deduplicating the data.
add_concurrent_index(TABLE_NAME, [:runner_id, :project_id], where: "id > #{Integer(last_runner_project_record_id)}", unique: true, name: TMP_INDEX_NAME)
Ci::RunnerProject.each_batch(of: BATCH_SIZE) do |relation|
duplicated_runner_projects = Ci::RunnerProject
.select('COUNT(*)', :runner_id, :project_id)
.where('(runner_id, project_id) IN (?)', relation.select(:runner_id, :project_id))
.group(:runner_id, :project_id)
.having('COUNT(*) > 1')
duplicated_runner_projects.each do |runner_project|
deduplicate_item(runner_project)
end
end
add_concurrent_index(TABLE_NAME, [:runner_id, :project_id], unique: true, name: INDEX_NAME)
remove_concurrent_index_by_name(TABLE_NAME, TMP_INDEX_NAME)
remove_concurrent_index_by_name(TABLE_NAME, OLD_INDEX_NAME)
end
def down
add_concurrent_index(TABLE_NAME, [:runner_id, :project_id], name: OLD_INDEX_NAME)
remove_concurrent_index_by_name(TABLE_NAME, TMP_INDEX_NAME)
remove_concurrent_index_by_name(TABLE_NAME, INDEX_NAME)
end
private
def deduplicate_item(runner_project)
runner_projects_records = Ci::RunnerProject
.where(project_id: runner_project.project_id, runner_id: runner_project.runner_id)
.order(updated_at: :asc)
.to_a
attributes = {}
runner_projects_records.each do |runner_projects_record|
params = runner_projects_record.attributes.except('id')
attributes.merge!(params.compact)
end
ApplicationRecord.transaction do
record_to_keep = runner_projects_records.pop
records_to_delete = runner_projects_records
Ci::RunnerProject.where(id: records_to_delete.map(&:id)).delete_all
record_to_keep.update!(attributes)
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class RemoveProjectsCiSourcesProjectsSourceProjectIdFk < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
return unless foreign_key_exists?(:ci_sources_projects, :projects, name: "fk_rails_64b6855cbc")
with_lock_retries do
execute('LOCK projects, ci_sources_projects IN ACCESS EXCLUSIVE MODE') if transaction_open?
remove_foreign_key_if_exists(:ci_sources_projects, :projects, name: "fk_rails_64b6855cbc")
end
end
def down
add_concurrent_foreign_key(:ci_sources_projects, :projects, name: "fk_rails_64b6855cbc", column: :source_project_id, target_column: :id, on_delete: :cascade)
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class RemoveProjectsCiPipelineSchedulesProjectIdFk < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
return unless foreign_key_exists?(:ci_pipeline_schedules, :projects, name: "fk_8ead60fcc4")
with_lock_retries do
execute('LOCK projects, ci_pipeline_schedules IN ACCESS EXCLUSIVE MODE') if transaction_open?
remove_foreign_key_if_exists(:ci_pipeline_schedules, :projects, name: "fk_8ead60fcc4")
end
end
def down
add_concurrent_foreign_key(:ci_pipeline_schedules, :projects, name: "fk_8ead60fcc4", column: :project_id, target_column: :id, on_delete: :cascade)
end
end

View file

@ -0,0 +1 @@
7f2b3e70e33273d75f68bd1fa33103f24a4e4cfc3f2e5777dfd258b5a2e7bf4e

View file

@ -0,0 +1 @@
6067e4e22e49548496454b48171f8168f7d5bf626fedab4351e2a37a3f85731b

View file

@ -0,0 +1 @@
07f837ddde21e36d1ca6a471dd96350d3020bd30204fca0e093983810c94e54d

View file

@ -25713,8 +25713,6 @@ CREATE UNIQUE INDEX index_ci_runner_namespaces_on_runner_id_and_namespace_id ON
CREATE INDEX index_ci_runner_projects_on_project_id ON ci_runner_projects USING btree (project_id); CREATE INDEX index_ci_runner_projects_on_project_id ON ci_runner_projects USING btree (project_id);
CREATE INDEX index_ci_runner_projects_on_runner_id_and_project_id ON ci_runner_projects USING btree (runner_id, project_id);
CREATE INDEX index_ci_runners_on_active ON ci_runners USING btree (active, id); CREATE INDEX index_ci_runners_on_active ON ci_runners USING btree (active, id);
CREATE INDEX index_ci_runners_on_contacted_at_and_id_desc ON ci_runners USING btree (contacted_at, id DESC); CREATE INDEX index_ci_runners_on_contacted_at_and_id_desc ON ci_runners USING btree (contacted_at, id DESC);
@ -27797,6 +27795,8 @@ CREATE INDEX index_u2f_registrations_on_user_id ON u2f_registrations USING btree
CREATE UNIQUE INDEX index_uniq_im_issuable_escalation_statuses_on_issue_id ON incident_management_issuable_escalation_statuses USING btree (issue_id); CREATE UNIQUE INDEX index_uniq_im_issuable_escalation_statuses_on_issue_id ON incident_management_issuable_escalation_statuses USING btree (issue_id);
CREATE UNIQUE INDEX index_unique_ci_runner_projects_on_runner_id_and_project_id ON ci_runner_projects USING btree (runner_id, project_id);
CREATE UNIQUE INDEX index_unique_issue_metrics_issue_id ON issue_metrics USING btree (issue_id); CREATE UNIQUE INDEX index_unique_issue_metrics_issue_id ON issue_metrics USING btree (issue_id);
CREATE INDEX index_unit_test_failures_failed_at ON ci_unit_test_failures USING btree (failed_at DESC); CREATE INDEX index_unit_test_failures_failed_at ON ci_unit_test_failures USING btree (failed_at DESC);
@ -29580,9 +29580,6 @@ ALTER TABLE ONLY releases
ALTER TABLE ONLY protected_tags ALTER TABLE ONLY protected_tags
ADD CONSTRAINT fk_8e4af87648 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_8e4af87648 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_pipeline_schedules
ADD CONSTRAINT fk_8ead60fcc4 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY todos ALTER TABLE ONLY todos
ADD CONSTRAINT fk_91d1f47b13 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE; ADD CONSTRAINT fk_91d1f47b13 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
@ -30606,9 +30603,6 @@ ALTER TABLE ONLY reviews
ALTER TABLE ONLY operations_feature_flags ALTER TABLE ONLY operations_feature_flags
ADD CONSTRAINT fk_rails_648e241be7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_648e241be7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_sources_projects
ADD CONSTRAINT fk_rails_64b6855cbc FOREIGN KEY (source_project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY board_group_recent_visits ALTER TABLE ONLY board_group_recent_visits
ADD CONSTRAINT fk_rails_64bfc19bc5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_64bfc19bc5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;

View file

@ -4158,12 +4158,13 @@ Input type: `RunnerUpdateInput`
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="mutationrunnerupdateaccesslevel"></a>`accessLevel` | [`CiRunnerAccessLevel`](#cirunneraccesslevel) | Access level of the runner. | | <a id="mutationrunnerupdateaccesslevel"></a>`accessLevel` | [`CiRunnerAccessLevel`](#cirunneraccesslevel) | Access level of the runner. |
| <a id="mutationrunnerupdateactive"></a>`active` | [`Boolean`](#boolean) | Indicates the runner is allowed to receive jobs. | | <a id="mutationrunnerupdateactive"></a>`active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated:** This was renamed. Please use `paused`. Deprecated in 14.8. |
| <a id="mutationrunnerupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | <a id="mutationrunnerupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationrunnerupdatedescription"></a>`description` | [`String`](#string) | Description of the runner. | | <a id="mutationrunnerupdatedescription"></a>`description` | [`String`](#string) | Description of the runner. |
| <a id="mutationrunnerupdateid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner to update. | | <a id="mutationrunnerupdateid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner to update. |
| <a id="mutationrunnerupdatelocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. | | <a id="mutationrunnerupdatelocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. |
| <a id="mutationrunnerupdatemaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. | | <a id="mutationrunnerupdatemaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. |
| <a id="mutationrunnerupdatepaused"></a>`paused` | [`Boolean`](#boolean) | Indicates the runner is not allowed to receive jobs. |
| <a id="mutationrunnerupdateprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). | | <a id="mutationrunnerupdateprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). |
| <a id="mutationrunnerupdatepublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "minutes cost factor" associated with the runner (GitLab.com only). | | <a id="mutationrunnerupdatepublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "minutes cost factor" associated with the runner (GitLab.com only). |
| <a id="mutationrunnerupdaterununtagged"></a>`runUntagged` | [`Boolean`](#boolean) | Indicates the runner is able to run untagged jobs. | | <a id="mutationrunnerupdaterununtagged"></a>`runUntagged` | [`Boolean`](#boolean) | Indicates the runner is able to run untagged jobs. |

View file

@ -54,12 +54,17 @@ GET /runners?tag_list=tag1,tag2
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners"
``` ```
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
Example response: Example response:
```json ```json
[ [
{ {
"active": true, "active": true,
"paused": false,
"description": "test-1-20150125", "description": "test-1-20150125",
"id": 6, "id": 6,
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
@ -71,6 +76,7 @@ Example response:
}, },
{ {
"active": true, "active": true,
"paused": false,
"description": "test-2-20150125", "description": "test-2-20150125",
"id": 8, "id": 8,
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
@ -107,12 +113,17 @@ GET /runners/all?tag_list=tag1,tag2
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all"
``` ```
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
Example response: Example response:
```json ```json
[ [
{ {
"active": true, "active": true,
"paused": false,
"description": "shared-runner-1", "description": "shared-runner-1",
"id": 1, "id": 1,
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
@ -124,6 +135,7 @@ Example response:
}, },
{ {
"active": true, "active": true,
"paused": false,
"description": "shared-runner-2", "description": "shared-runner-2",
"id": 3, "id": 3,
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
@ -135,6 +147,7 @@ Example response:
}, },
{ {
"active": true, "active": true,
"paused": false,
"description": "test-1-20150125", "description": "test-1-20150125",
"id": 6, "id": 6,
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
@ -146,6 +159,7 @@ Example response:
}, },
{ {
"active": true, "active": true,
"paused": false,
"description": "test-2-20150125", "description": "test-2-20150125",
"id": 8, "id": 8,
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
@ -185,11 +199,16 @@ NOTE:
The `token` attribute in the response was deprecated [in GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320). The `token` attribute in the response was deprecated [in GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320).
and removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322). and removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322).
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
Example response: Example response:
```json ```json
{ {
"active": true, "active": true,
"paused": false,
"architecture": null, "architecture": null,
"description": "test-1-20150125", "description": "test-1-20150125",
"id": 6, "id": 6,
@ -229,16 +248,17 @@ Update details of a runner.
PUT /runners/:id PUT /runners/:id
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
|---------------|---------|----------|---------------------| |-------------------|---------|----------|--------------------------------------------------------------------------------------------------|
| `id` | integer | yes | The ID of a runner | | `id` | integer | yes | The ID of a runner |
| `description` | string | no | The description of a runner | | `description` | string | no | The description of a runner |
| `active` | boolean | no | The state of a runner; can be set to `true` or `false` | | `active` | boolean | no | Deprecated: Use `:paused` instead. Flag indicating whether the runner is allowed to receive jobs |
| `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner | | `paused` | boolean | no | Flag indicating whether the runner should ignore new jobs |
| `run_untagged`| boolean | no | Flag indicating the runner can execute untagged jobs | | `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner |
| `locked` | boolean | no | Flag indicating the runner is locked | | `run_untagged` | boolean | no | Flag indicating the runner can execute untagged jobs |
| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` | | `locked` | boolean | no | Flag indicating the runner is locked |
| `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job | | `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
| `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job |
```shell ```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6" \ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6" \
@ -249,6 +269,10 @@ NOTE:
The `token` attribute in the response was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/214320) in GitLab 12.10. The `token` attribute in the response was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/214320) in GitLab 12.10.
and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/214322) in GitLab 13.0. and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/214322) in GitLab 13.0.
NOTE:
The `active` query parameter was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
Example response: Example response:
```json ```json
@ -292,7 +316,12 @@ Example response:
Pause a specific runner. Pause a specific runner.
```plaintext ```plaintext
PUT --form "active=false" /runners/:runner_id PUT --form "paused=true" /runners/:runner_id
# --or--
# Deprecated: removal planned in 15.0
PUT --form "active=false" /runners/:runner_id
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
@ -300,10 +329,20 @@ PUT --form "active=false" /runners/:runner_id
| `runner_id` | integer | yes | The ID of a runner | | `runner_id` | integer | yes | The ID of a runner |
```shell ```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--form "paused=true" "https://gitlab.example.com/api/v4/runners/6"
# --or--
# Deprecated: removal planned in 15.0
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" \
--form "active=false" "https://gitlab.example.com/api/v4/runners/6" --form "active=false" "https://gitlab.example.com/api/v4/runners/6"
``` ```
NOTE:
The `active` form attribute was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
## List runner's jobs ## List runner's jobs
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
@ -420,12 +459,17 @@ GET /projects/:id/runners?tag_list=tag1,tag2
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners"
``` ```
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
Example response: Example response:
```json ```json
[ [
{ {
"active": true, "active": true,
"paused": false,
"description": "test-2-20150125", "description": "test-2-20150125",
"id": 8, "id": 8,
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
@ -437,6 +481,7 @@ Example response:
}, },
{ {
"active": true, "active": true,
"paused": false,
"description": "development_runner", "description": "development_runner",
"id": 5, "id": 5,
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
@ -444,7 +489,7 @@ Example response:
"runner_type": "instance_type", "runner_type": "instance_type",
"name": null, "name": null,
"online": true, "online": true,
"status": "paused" "status": "online"
} }
] ]
``` ```
@ -525,6 +570,10 @@ GET /groups/:id/runners?tag_list=tag1,tag2
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/9/runners" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/9/runners"
``` ```
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
Example response: Example response:
```json ```json
@ -534,6 +583,7 @@ Example response:
"description": "Shared", "description": "Shared",
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
"active": true, "active": true,
"paused": false,
"is_shared": true, "is_shared": true,
"runner_type": "instance_type", "runner_type": "instance_type",
"name": "gitlab-runner", "name": "gitlab-runner",
@ -545,6 +595,7 @@ Example response:
"description": "Test", "description": "Test",
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
"active": true, "active": true,
"paused": false,
"is_shared": true, "is_shared": true,
"runner_type": "instance_type", "runner_type": "instance_type",
"name": "gitlab-runner", "name": "gitlab-runner",
@ -556,6 +607,7 @@ Example response:
"description": "Test 2", "description": "Test 2",
"ip_address": "127.0.0.1", "ip_address": "127.0.0.1",
"active": true, "active": true,
"paused": false,
"is_shared": false, "is_shared": false,
"runner_type": "group_type", "runner_type": "group_type",
"name": "gitlab-runner", "name": "gitlab-runner",
@ -573,19 +625,20 @@ Register a new runner for the instance.
POST /runners POST /runners
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
|--------------------|--------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| |--------------------|--------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `token` | string | yes | [Registration token](#registration-and-authentication-tokens). | | `token` | string | yes | [Registration token](#registration-and-authentication-tokens) |
| `description` | string | no | Runner's description | | `description` | string | no | Runner's description |
| `info` | hash | no | Runner's metadata. You can include `name`, `version`, `revision`, `platform`, and `architecture`, but only `version` is displayed in the Admin area of the UI. | | `info` | hash | no | Runner's metadata. You can include `name`, `version`, `revision`, `platform`, and `architecture`, but only `version` is displayed in the Admin area of the UI |
| `active` | boolean | no | Whether the runner is active | | `active` | boolean | no | Deprecated: Use `:paused` instead. Whether the runner is allowed to receive jobs |
| `locked` | boolean | no | Whether the runner should be locked for current project | | `paused` | boolean | no | Whether the runner should ignore new jobs |
| `run_untagged` | boolean | no | Whether the runner should handle untagged jobs | | `locked` | boolean | no | Whether the runner should be locked for current project |
| `tag_list` | string array | no | List of runner's tags | | `run_untagged` | boolean | no | Whether the runner should handle untagged jobs |
| `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` | | `tag_list` | string array | no | List of runner's tags |
| `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job | | `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` |
| `maintainer_note` | string | no | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/350730), see `maintenance_note`. | | `maximum_timeout` | integer | no | Maximum timeout set when this runner handles the job |
| `maintenance_note` | string | no | Free-form maintenance notes for the runner (255 characters) | | `maintainer_note` | string | no | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/350730), see `maintenance_note` |
| `maintenance_note` | string | no | Free-form maintenance notes for the runner (255 characters) |
```shell ```shell
curl --request POST "https://gitlab.example.com/api/v4/runners" \ curl --request POST "https://gitlab.example.com/api/v4/runners" \

View file

@ -226,7 +226,7 @@ In milestone 15.0, we will remove the `pipelines` attribute from the API respons
**Planned removal milestone: 15.0 (2022-05-22)** **Planned removal milestone: 15.0 (2022-05-22)**
### REST API Runner will not contain `paused` ### REST and GraphQL API Runner status will not return `paused`
WARNING: WARNING:
This feature will be changed or removed in 15.0 This feature will be changed or removed in 15.0
@ -240,7 +240,7 @@ A runner's status will only relate to runner contact status, such as:
`online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear. `online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear.
When checking if a runner is `paused`, API users are advised to check the boolean attribute When checking if a runner is `paused`, API users are advised to check the boolean attribute
`active` to be `false` instead. When checking if a runner is `active`, check if `active` is `true`. `paused` to be `true` instead. When checking if a runner is `active`, check if `paused` is `false`.
**Planned removal milestone: 15.0 (2022-05-22)** **Planned removal milestone: 15.0 (2022-05-22)**
@ -717,6 +717,36 @@ The `merged_by` field in the [merge request API](https://docs.gitlab.com/ee/api/
## 14.8 ## 14.8
### REST and GraphQL API Runner usage of `active` replaced by `paused`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
Occurrences of the `active` identifier in the GitLab Runner REST and GraphQL API endpoints will be
renamed to `paused` in GitLab 15.0, namely:
- GraphQL API:
- the `CiRunner` property;
- the `RunnerUpdateInput` input type for the `runnerUpdate` mutation;
- the `runners` and `Group.runners` queries.
- REST API:
- endpoints taking or returning `active` properties, such as:
- `GET /runners`
- `GET /runners/all`
- `GET /runners/:id` / `PUT /runners/:id`
- `PUT --form "active=false" /runners/:runner_id`
- `GET /projects/:id/runners` / `POST /projects/:id/runners`
- `GET /groups/:id/runners`
The 15.0 release of the GitLab Runner will start using the `paused` property when registering runners, and therefore
will only be compatible with GitLab 15.0 and later. Until 15.0, GitLab will accept the deprecated `active` flag from
existing runners.
**Planned removal milestone: 15.0 (2022-05-22)**
### Vulnerability Check ### Vulnerability Check
WARNING: WARNING:

View file

@ -18,21 +18,25 @@ module API
optional :maintainer_note, type: String, desc: %q(Deprecated: Use :maintenance_note instead. Runner's maintenance notes) optional :maintainer_note, type: String, desc: %q(Deprecated: Use :maintenance_note instead. Runner's maintenance notes)
optional :maintenance_note, type: String, desc: %q(Runner's maintenance notes) optional :maintenance_note, type: String, desc: %q(Runner's maintenance notes)
optional :info, type: Hash, desc: %q(Runner's metadata) optional :info, type: Hash, desc: %q(Runner's metadata)
optional :active, type: Boolean, desc: 'Should Runner be active' optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Should runner be active'
optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' optional :paused, type: Boolean, desc: 'Whether the runner should ignore new jobs'
optional :locked, type: Boolean, desc: 'Whether the runner should be locked for current project'
optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
desc: 'The access_level of the runner' desc: 'The access_level of the runner; `not_protected` or `ref_protected`'
optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' optional :run_untagged, type: Boolean, desc: 'Whether the runner should handle untagged jobs'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: %q(List of Runner's tags) optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: %q(List of Runner's tags)
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this runner handles the job'
mutually_exclusive :maintainer_note, :maintainer_note
mutually_exclusive :active, :paused
end end
post '/', feature_category: :runner do post '/', feature_category: :runner do
attributes = attributes_for_keys(%i[description maintainer_note maintenance_note active locked run_untagged tag_list access_level maximum_timeout]) attributes = attributes_for_keys(%i[description maintainer_note maintenance_note active paused locked run_untagged tag_list access_level maximum_timeout])
.merge(get_runner_details_from_request) .merge(get_runner_details_from_request)
# Pull in deprecated maintainer_note if that's the only note value available # Pull in deprecated maintainer_note if that's the only note value available
deprecated_note = attributes.delete(:maintainer_note) deprecated_note = attributes.delete(:maintainer_note)
attributes[:maintenance_note] ||= deprecated_note if deprecated_note attributes[:maintenance_note] ||= deprecated_note if deprecated_note
attributes[:active] = !attributes.delete(:paused) if attributes.include?(:paused)
@runner = ::Ci::RegisterRunnerService.new.execute(params[:token], attributes) @runner = ::Ci::RegisterRunnerService.new.execute(params[:token], attributes)
forbidden! unless @runner forbidden! unless @runner

View file

@ -77,18 +77,21 @@ module API
params do params do
requires :id, type: Integer, desc: 'The ID of the runner' requires :id, type: Integer, desc: 'The ID of the runner'
optional :description, type: String, desc: 'The description of the runner' optional :description, type: String, desc: 'The description of the runner'
optional :active, type: Boolean, desc: 'The state of a runner' optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Flag indicating whether the runner is allowed to receive jobs'
optional :paused, type: Boolean, desc: 'Flag indicating whether the runner should ignore new jobs'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a runner' optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a runner'
optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs' optional :run_untagged, type: Boolean, desc: 'Flag indicating whether the runner can execute untagged jobs'
optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys,
desc: 'The access_level of the runner' desc: 'The access_level of the runner'
optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job'
at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout at_least_one_of :description, :active, :paused, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout
mutually_exclusive :active, :paused
end end
put ':id' do put ':id' do
runner = get_runner(params.delete(:id)) runner = get_runner(params.delete(:id))
authenticate_update_runner!(runner) authenticate_update_runner!(runner)
params[:active] = !params.delete(:paused) if params.include?(:paused)
update_service = ::Ci::UpdateRunnerService.new(runner) update_service = ::Ci::UpdateRunnerService.new(runner)
if update_service.update(declared_params(include_missing: false)) if update_service.update(declared_params(include_missing: false))

View file

@ -7,7 +7,10 @@ module API
expose :id expose :id
expose :description expose :description
expose :ip_address expose :ip_address
expose :active expose :active # TODO Remove in %15.0 in favor of `paused` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/351109
expose :paused do |runner|
!runner.active
end
expose :instance_type?, as: :is_shared expose :instance_type?, as: :is_shared
expose :runner_type expose :runner_type
expose :name expose :name

View file

@ -83,6 +83,10 @@ ci_namespace_mirrors:
- table: namespaces - table: namespaces
column: namespace_id column: namespace_id
on_delete: async_delete on_delete: async_delete
ci_sources_projects:
- table: projects
column: source_project_id
on_delete: async_delete
ci_build_report_results: ci_build_report_results:
- table: projects - table: projects
column: project_id column: project_id
@ -167,6 +171,9 @@ ci_pipeline_schedules:
- table: users - table: users
column: owner_id column: owner_id
on_delete: async_nullify on_delete: async_nullify
- table: projects
column: project_id
on_delete: async_delete
merge_trains: merge_trains:
- table: ci_pipelines - table: ci_pipelines
column: pipeline_id column: pipeline_id

View file

@ -18,7 +18,7 @@ module Gitlab
end end
def self.subdomain_regex def self.subdomain_regex
%r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze %r{\Ahttps://[a-z0-9-]+\.gitlab\.com\z}.freeze
end end
def self.dev_url def self.dev_url

View file

@ -20,6 +20,7 @@ module GoogleApi
"https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring" "https://www.googleapis.com/auth/monitoring"
].freeze ].freeze
ROLES_LIST = %w[roles/iam.serviceAccountUser roles/artifactregistry.admin roles/cloudbuild.builds.builder roles/run.admin roles/storage.admin roles/cloudsql.admin roles/browser].freeze
class << self class << self
def session_key_for_token def session_key_for_token
@ -88,11 +89,8 @@ module GoogleApi
def list_projects def list_projects
result = [] result = []
service = Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new response = cloud_resource_manager_service.fetch_all(items: :projects) do |token|
service.authorization = access_token cloud_resource_manager_service.list_projects
response = service.fetch_all(items: :projects) do |token|
service.list_projects
end end
# Google API results are paged by default, so we need to iterate through # Google API results are paged by default, so we need to iterate through
@ -130,6 +128,11 @@ module GoogleApi
service.create_service_account_key(name, request_body) service.create_service_account_key(name, request_body)
end end
def grant_service_account_roles(gcp_project_id, email)
body = policy_request_body(gcp_project_id, email)
cloud_resource_manager_service.set_project_iam_policy(gcp_project_id, body)
end
private private
def make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons) def make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons)
@ -173,6 +176,23 @@ module GoogleApi
options.header = { 'User-Agent': "GitLab/#{Gitlab::VERSION.match('(\d+\.\d+)').captures.first} (GPN:GitLab;)" } options.header = { 'User-Agent': "GitLab/#{Gitlab::VERSION.match('(\d+\.\d+)').captures.first} (GPN:GitLab;)" }
end end
end end
def policy_request_body(gcp_project_id, email)
policy = cloud_resource_manager_service.get_project_iam_policy(gcp_project_id)
policy.bindings = policy.bindings + additional_policy_bindings("serviceAccount:#{email}")
Google::Apis::CloudresourcemanagerV1::SetIamPolicyRequest.new(policy: policy)
end
def additional_policy_bindings(member)
ROLES_LIST.map do |role|
Google::Apis::CloudresourcemanagerV1::Binding.new(role: role, members: [member])
end
end
def cloud_resource_manager_service
@gpc_service ||= Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new.tap { |s| s. authorization = access_token }
end
end end
end end
end end

View file

@ -127,7 +127,7 @@ RSpec.describe 'Merge request > User creates image diff notes', :js do
visit diffs_project_merge_request_path(project, merge_request, view: view) visit diffs_project_merge_request_path(project, merge_request, view: view)
wait_for_requests wait_for_requests
expect(page.all('.diff-file span.label-lfs', visible: :all)).not_to be_empty expect(page.all('[data-testid="label-lfs"]', visible: :all)).not_to be_empty
end end
it_behaves_like 'creates image diff note' it_behaves_like 'creates image diff note'

View file

@ -82,7 +82,7 @@ describe('DiffFileHeader component', () => {
const findExpandButton = () => wrapper.find({ ref: 'expandDiffToFullFileButton' }); const findExpandButton = () => wrapper.find({ ref: 'expandDiffToFullFileButton' });
const findFileActions = () => wrapper.find('.file-actions'); const findFileActions = () => wrapper.find('.file-actions');
const findModeChangedLine = () => wrapper.find({ ref: 'fileMode' }); const findModeChangedLine = () => wrapper.find({ ref: 'fileMode' });
const findLfsLabel = () => wrapper.find('.label-lfs'); const findLfsLabel = () => wrapper.find('[data-testid="label-lfs"]');
const findToggleDiscussionsButton = () => wrapper.find({ ref: 'toggleDiscussionsButton' }); const findToggleDiscussionsButton = () => wrapper.find({ ref: 'toggleDiscussionsButton' });
const findExternalLink = () => wrapper.find({ ref: 'externalLink' }); const findExternalLink = () => wrapper.find({ ref: 'externalLink' });
const findReplacedFileButton = () => wrapper.find({ ref: 'replacedFileButton' }); const findReplacedFileButton = () => wrapper.find({ ref: 'replacedFileButton' });

View file

@ -19,12 +19,10 @@ RSpec.describe 'cross-database foreign keys' do
ci_pending_builds.namespace_id ci_pending_builds.namespace_id
ci_pending_builds.project_id ci_pending_builds.project_id
ci_pipeline_schedules.owner_id ci_pipeline_schedules.owner_id
ci_pipeline_schedules.project_id
ci_pipelines.project_id ci_pipelines.project_id
ci_resource_groups.project_id ci_resource_groups.project_id
ci_runner_namespaces.namespace_id ci_runner_namespaces.namespace_id
ci_running_builds.project_id ci_running_builds.project_id
ci_sources_projects.source_project_id
ci_stages.project_id ci_stages.project_id
ci_unit_tests.project_id ci_unit_tests.project_id
).freeze ).freeze

View file

@ -99,6 +99,13 @@ RSpec.describe Gitlab do
expect(described_class.com?).to eq true expect(described_class.com?).to eq true
end end
it 'is true when on other gitlab subdomain with hyphen' do
url_with_subdomain = Gitlab::Saas.com_url.gsub('https://', 'https://test-example.')
stub_config_setting(url: url_with_subdomain)
expect(described_class.com?).to eq true
end
it 'is false when not on GitLab.com' do it 'is false when not on GitLab.com' do
stub_config_setting(url: 'http://example.com') stub_config_setting(url: 'http://example.com')

View file

@ -60,7 +60,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
before do before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
.to receive(:get_zone_cluster).with(any_args, options: user_agent_options) .to receive(:get_zone_cluster).with(any_args, options: user_agent_options)
.and_return(gke_cluster) .and_return(gke_cluster)
end end
it { is_expected.to eq(gke_cluster) } it { is_expected.to eq(gke_cluster) }
@ -122,7 +122,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
before do before do
allow_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService) allow_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService)
.to receive(:create_cluster).with(any_args) .to receive(:create_cluster).with(any_args)
.and_return(operation) .and_return(operation)
end end
it 'sets corresponded parameters' do it 'sets corresponded parameters' do
@ -172,7 +172,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
before do before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
.to receive(:get_zone_operation).with(any_args, options: user_agent_options) .to receive(:get_zone_operation).with(any_args, options: user_agent_options)
.and_return(operation) .and_return(operation)
end end
it { is_expected.to eq(operation) } it { is_expected.to eq(operation) }
@ -244,7 +244,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
let(:operation) { double('Service Account Key') } let(:operation) { double('Service Account Key') }
it 'class Google Api IamService#create_service_account_key' do it 'calls Google Api IamService#create_service_account_key' do
expect_any_instance_of(Google::Apis::IamV1::IamService) expect_any_instance_of(Google::Apis::IamV1::IamService)
.to receive(:create_service_account_key) .to receive(:create_service_account_key)
.with(any_args) .with(any_args)
@ -252,4 +252,48 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
is_expected.to eq(operation) is_expected.to eq(operation)
end end
end end
describe 'grant_service_account_roles' do
subject { client.grant_service_account_roles(spy, spy) }
it 'calls Google Api CloudResourceManager#set_iam_policy' do
mock_gcp_id = 'mock-gcp-id'
mock_email = 'mock@email.com'
mock_policy = Struct.new(:bindings).new([])
mock_body = []
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/iam.serviceAccountUser', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/artifactregistry.admin', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/cloudbuild.builds.builder', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/run.admin', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/storage.admin', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/cloudsql.admin', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::Binding).to receive(:new)
.with({ 'role': 'roles/browser', 'members': ["serviceAccount:#{mock_email}"] })
expect(Google::Apis::CloudresourcemanagerV1::SetIamPolicyRequest).to receive(:new).and_return([])
expect_next_instance_of(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService) do |instance|
expect(instance).to receive(:get_project_iam_policy)
.with(mock_gcp_id)
.and_return(mock_policy)
expect(instance).to receive(:set_project_iam_policy)
.with(mock_gcp_id, mock_body)
end
client.grant_service_account_roles(mock_gcp_id, mock_email)
end
end
end end

View file

@ -0,0 +1,65 @@
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20220124130028_dedup_runner_projects.rb')
RSpec.describe DedupRunnerProjects, :migration, schema: 20220120085655 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:runners) { table(:ci_runners) }
let(:runner_projects) { table(:ci_runner_projects) }
let!(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
let!(:project) { projects.create!(namespace_id: namespace.id) }
let!(:project_2) { projects.create!(namespace_id: namespace.id) }
let!(:runner) { runners.create!(runner_type: 'project_type') }
let!(:runner_2) { runners.create!(runner_type: 'project_type') }
let!(:runner_3) { runners.create!(runner_type: 'project_type') }
let!(:duplicated_runner_project_1) { runner_projects.create!(runner_id: runner.id, project_id: project.id) }
let!(:duplicated_runner_project_2) { runner_projects.create!(runner_id: runner.id, project_id: project.id) }
let!(:duplicated_runner_project_3) { runner_projects.create!(runner_id: runner_2.id, project_id: project_2.id) }
let!(:duplicated_runner_project_4) { runner_projects.create!(runner_id: runner_2.id, project_id: project_2.id) }
let!(:non_duplicated_runner_project) { runner_projects.create!(runner_id: runner_3.id, project_id: project.id) }
it 'deduplicates ci_runner_projects table' do
expect { migrate! }.to change { runner_projects.count }.from(5).to(3)
end
it 'merges `duplicated_runner_project_1` with `duplicated_runner_project_2`', :aggregate_failures do
migrate!
expect(runner_projects.where(id: duplicated_runner_project_1.id)).not_to(exist)
merged_runner_projects = runner_projects.find_by(id: duplicated_runner_project_2.id)
expect(merged_runner_projects).to be_present
expect(merged_runner_projects.created_at).to be_like_time(duplicated_runner_project_1.created_at)
expect(merged_runner_projects.created_at).to be_like_time(duplicated_runner_project_2.created_at)
end
it 'merges `duplicated_runner_project_3` with `duplicated_runner_project_4`', :aggregate_failures do
migrate!
expect(runner_projects.where(id: duplicated_runner_project_3.id)).not_to(exist)
merged_runner_projects = runner_projects.find_by(id: duplicated_runner_project_4.id)
expect(merged_runner_projects).to be_present
expect(merged_runner_projects.created_at).to be_like_time(duplicated_runner_project_3.created_at)
expect(merged_runner_projects.created_at).to be_like_time(duplicated_runner_project_4.created_at)
end
it 'does not change non duplicated records' do
expect { migrate! }.not_to change { non_duplicated_runner_project.reload.attributes }
end
it 'does nothing when there are no runner projects' do
runner_projects.delete_all
migrate!
expect(runner_projects.count).to eq(0)
end
end

View file

@ -227,4 +227,11 @@ RSpec.describe Ci::PipelineSchedule do
it { is_expected.to eq(144) } it { is_expected.to eq(144) }
end end
end end
context 'loose foreign key on ci_pipeline_schedules.project_id' do
it_behaves_like 'cleanup by a loose foreign key' do
let!(:parent) { create(:project) }
let!(:model) { create(:ci_pipeline_schedule, project: parent) }
end
end
end end

View file

@ -34,7 +34,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
run_untagged: false, run_untagged: false,
tag_list: 'tag1, tag2', tag_list: 'tag1, tag2',
locked: true, locked: true,
active: true, paused: false,
access_level: 'ref_protected', access_level: 'ref_protected',
maximum_timeout: 9000 maximum_timeout: 9000
} }
@ -55,7 +55,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
maximum_timeout: 9000 maximum_timeout: 9000
}.stringify_keys }.stringify_keys
allow(service).to receive(:execute) expect(service).to receive(:execute)
.once .once
.with('valid token', a_hash_including(expected_params)) .with('valid token', a_hash_including(expected_params))
.and_return(new_runner) .and_return(new_runner)
@ -108,6 +108,32 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end end
end end
context 'when deprecated active parameter is provided' do
def request
post api('/runners'), params: {
token: 'valid token',
active: false
}
end
let_it_be(:new_runner) { create(:ci_runner) }
it 'uses active value in registration' do
expect_next_instance_of(::Ci::RegisterRunnerService) do |service|
expected_params = { active: false }.stringify_keys
expect(service).to receive(:execute)
.once
.with('valid token', a_hash_including(expected_params))
.and_return(new_runner)
end
request
expect(response).to have_gitlab_http_status(:created)
end
end
context 'calling actual register service' do context 'calling actual register service' do
include StubGitlabCalls include StubGitlabCalls

View file

@ -109,7 +109,7 @@ RSpec.describe API::Ci::Runners do
get api('/runners?tag_list=tag1,tag2', user) get api('/runners?tag_list=tag1,tag2', user)
expect(json_response).to match_array [ expect(json_response).to match_array [
a_hash_including('description' => 'Runner tagged with tag1 and tag2') a_hash_including('description' => 'Runner tagged with tag1 and tag2', 'active' => true, 'paused' => false)
] ]
end end
end end
@ -137,7 +137,7 @@ RSpec.describe API::Ci::Runners do
get api('/runners/all', admin) get api('/runners/all', admin)
expect(json_response).to match_array [ expect(json_response).to match_array [
a_hash_including('description' => 'Project runner', 'is_shared' => false, 'runner_type' => 'project_type'), a_hash_including('description' => 'Project runner', 'is_shared' => false, 'active' => true, 'paused' => false, 'runner_type' => 'project_type'),
a_hash_including('description' => 'Two projects runner', 'is_shared' => false, 'runner_type' => 'project_type'), a_hash_including('description' => 'Two projects runner', 'is_shared' => false, 'runner_type' => 'project_type'),
a_hash_including('description' => 'Group runner A', 'is_shared' => false, 'runner_type' => 'group_type'), a_hash_including('description' => 'Group runner A', 'is_shared' => false, 'runner_type' => 'group_type'),
a_hash_including('description' => 'Group runner B', 'is_shared' => false, 'runner_type' => 'group_type'), a_hash_including('description' => 'Group runner B', 'is_shared' => false, 'runner_type' => 'group_type'),
@ -255,6 +255,8 @@ RSpec.describe API::Ci::Runners do
expect(json_response['description']).to eq(shared_runner.description) expect(json_response['description']).to eq(shared_runner.description)
expect(json_response['maximum_timeout']).to be_nil expect(json_response['maximum_timeout']).to be_nil
expect(json_response['status']).to eq("not_connected") expect(json_response['status']).to eq("not_connected")
expect(json_response['active']).to eq(true)
expect(json_response['paused']).to eq(false)
end end
end end
@ -359,6 +361,14 @@ RSpec.describe API::Ci::Runners do
expect(shared_runner.reload.active).to eq(!active) expect(shared_runner.reload.active).to eq(!active)
end end
it 'runner paused state' do
active = shared_runner.active
update_runner(shared_runner.id, admin, paused: active)
expect(response).to have_gitlab_http_status(:ok)
expect(shared_runner.reload.active).to eq(!active)
end
it 'runner tag list' do it 'runner tag list' do
update_runner(shared_runner.id, admin, tag_list: ['ruby2.1', 'pgsql', 'mysql']) update_runner(shared_runner.id, admin, tag_list: ['ruby2.1', 'pgsql', 'mysql'])
@ -908,9 +918,9 @@ RSpec.describe API::Ci::Runners do
get api("/projects/#{project.id}/runners", user) get api("/projects/#{project.id}/runners", user)
expect(json_response).to match_array [ expect(json_response).to match_array [
a_hash_including('description' => 'Project runner'), a_hash_including('description' => 'Project runner', 'active' => true, 'paused' => false),
a_hash_including('description' => 'Two projects runner'), a_hash_including('description' => 'Two projects runner', 'active' => true, 'paused' => false),
a_hash_including('description' => 'Shared runner') a_hash_including('description' => 'Shared runner', 'active' => true, 'paused' => false)
] ]
end end
@ -986,7 +996,7 @@ RSpec.describe API::Ci::Runners do
get api("/groups/#{group.id}/runners", user) get api("/groups/#{group.id}/runners", user)
expect(json_response).to match_array([ expect(json_response).to match_array([
a_hash_including('description' => 'Group runner A') a_hash_including('description' => 'Group runner A', 'active' => true, 'paused' => false)
]) ])
end end

View file

@ -2,10 +2,6 @@
require 'spec_helper' require 'spec_helper'
# Mock Types
MockGoogleOAuth2Credentials = Struct.new(:app_id, :app_secret)
MockServiceAccount = Struct.new(:project_id, :unique_id)
RSpec.describe Projects::GoogleCloud::ServiceAccountsController do RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
@ -86,10 +82,12 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
context 'and user has successfully completed the google oauth2 flow' do context 'and user has successfully completed the google oauth2 flow' do
before do before do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client| allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
mock_service_account = Struct.new(:project_id, :unique_id, :email).new(123, 456, 'em@ai.l')
allow(client).to receive(:validate_token).and_return(true) allow(client).to receive(:validate_token).and_return(true)
allow(client).to receive(:list_projects).and_return([{}, {}, {}]) allow(client).to receive(:list_projects).and_return([{}, {}, {}])
allow(client).to receive(:create_service_account).and_return(MockServiceAccount.new(123, 456)) allow(client).to receive(:create_service_account).and_return(mock_service_account)
allow(client).to receive(:create_service_account_key).and_return({}) allow(client).to receive(:create_service_account_key).and_return({})
allow(client).to receive(:grant_service_account_roles)
end end
end end
@ -147,7 +145,8 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
context 'but gitlab instance is not configured for google oauth2' do context 'but gitlab instance is not configured for google oauth2' do
before do before do
unconfigured_google_oauth2 = MockGoogleOAuth2Credentials.new('', '') unconfigured_google_oauth2 = Struct.new(:app_id, :app_secret)
.new('', '')
allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for) allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for)
.with('google_oauth2') .with('google_oauth2')
.and_return(unconfigured_google_oauth2) .and_return(unconfigured_google_oauth2)

View file

@ -32,5 +32,22 @@ RSpec.describe Ci::PipelineScheduleService do
expect { subject }.not_to raise_error expect { subject }.not_to raise_error
end end
end end
context 'when the project is missing' do
before do
project.delete
end
it 'does not raise an exception' do
expect { subject }.not_to raise_error
end
it 'does not run RunPipelineScheduleWorker' do
expect(RunPipelineScheduleWorker)
.not_to receive(:perform_async).with(schedule.id, schedule.owner.id)
subject
end
end
end end
end end

View file

@ -23,6 +23,20 @@ RSpec.describe Ci::UpdateRunnerService do
end end
end end
context 'with paused param' do
let(:params) { { paused: true } }
it 'updates the runner and ticking the queue' do
expect(runner.active).to be_truthy
expect(update).to be_truthy
runner.reload
expect(runner).to have_received(:tick_runner_queue)
expect(runner.active).to be_falsey
end
end
context 'with cost factor params' do context 'with cost factor params' do
let(:params) { { public_projects_minutes_cost_factor: 1.1, private_projects_minutes_cost_factor: 2.2 }} let(:params) { { public_projects_minutes_cost_factor: 1.1, private_projects_minutes_cost_factor: 2.2 }}

View file

@ -2,22 +2,26 @@
require 'spec_helper' require 'spec_helper'
# Mock Types
MockGoogleOAuth2Credentials = Struct.new(:app_id, :app_secret)
MockServiceAccount = Struct.new(:project_id, :unique_id)
RSpec.describe GoogleCloud::CreateServiceAccountsService do RSpec.describe GoogleCloud::CreateServiceAccountsService do
describe '#execute' do describe '#execute' do
before do before do
mock_google_oauth2_creds = Struct.new(:app_id, :app_secret)
.new('mock-app-id', 'mock-app-secret')
allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for) allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for)
.with('google_oauth2') .with('google_oauth2')
.and_return(MockGoogleOAuth2Credentials.new('mock-app-id', 'mock-app-secret')) .and_return(mock_google_oauth2_creds)
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client| allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
mock_service_account = Struct.new(:project_id, :unique_id, :email)
.new('mock-project-id', 'mock-unique-id', 'mock-email')
allow(client).to receive(:create_service_account) allow(client).to receive(:create_service_account)
.and_return(MockServiceAccount.new('mock-project-id', 'mock-unique-id')) .and_return(mock_service_account)
allow(client).to receive(:create_service_account_key) allow(client).to receive(:create_service_account_key)
.and_return('mock-key') .and_return('mock-key')
allow(client)
.to receive(:grant_service_account_roles)
end end
end end

View file

@ -6,15 +6,16 @@ RSpec.describe Ci::DeleteObjectsWorker do
let(:worker) { described_class.new } let(:worker) { described_class.new }
it { expect(described_class.idempotent?).to be_truthy } it { expect(described_class.idempotent?).to be_truthy }
it { is_expected.to respond_to(:max_running_jobs) }
it { is_expected.to respond_to(:remaining_work_count) }
it { is_expected.to respond_to(:perform_work) }
describe '#perform' do describe '#perform' do
it 'executes a service' do it 'executes a service' do
allow(worker).to receive(:max_running_jobs).and_return(25)
expect_next_instance_of(Ci::DeleteObjectsService) do |instance| expect_next_instance_of(Ci::DeleteObjectsService) do |instance|
expect(instance).to receive(:execute) expect(instance).to receive(:execute)
expect(instance).to receive(:remaining_batches_count) expect(instance).to receive(:remaining_batches_count)
.with(max_batch_count: 25) .with(max_batch_count: 20)
.once .once
.and_call_original .and_call_original
end end
@ -22,30 +23,4 @@ RSpec.describe Ci::DeleteObjectsWorker do
worker.perform worker.perform
end end
end end
describe '#max_running_jobs' do
using RSpec::Parameterized::TableSyntax
before do
stub_feature_flags(
ci_delete_objects_medium_concurrency: medium,
ci_delete_objects_high_concurrency: high
)
end
subject(:max_running_jobs) { worker.max_running_jobs }
where(:medium, :high, :expected) do
false | false | 2
true | false | 20
true | true | 20
false | true | 50
end
with_them do
it 'sets up concurrency depending on the feature flag' do
expect(max_running_jobs).to eq(expected)
end
end
end
end end

View file

@ -103,4 +103,14 @@ RSpec.describe PipelineScheduleWorker do
expect { subject }.not_to raise_error expect { subject }.not_to raise_error
end end
end end
context 'when the project is missing' do
before do
project.delete
end
it 'does not raise an exception' do
expect { subject }.not_to raise_error
end
end
end end

View file

@ -10,12 +10,25 @@ RSpec.describe RunPipelineScheduleWorker do
let(:worker) { described_class.new } let(:worker) { described_class.new }
context 'when a project not found' do context 'when a schedule not found' do
it 'does not call the Service' do it 'does not call the Service' do
expect(Ci::CreatePipelineService).not_to receive(:new) expect(Ci::CreatePipelineService).not_to receive(:new)
expect(worker).not_to receive(:run_pipeline_schedule) expect(worker).not_to receive(:run_pipeline_schedule)
worker.perform(100000, user.id) worker.perform(non_existing_record_id, user.id)
end
end
context 'when a schedule project is missing' do
before do
project.delete
end
it 'does not call the Service' do
expect(Ci::CreatePipelineService).not_to receive(:new)
expect(worker).not_to receive(:run_pipeline_schedule)
worker.perform(pipeline_schedule.id, user.id)
end end
end end
@ -24,7 +37,7 @@ RSpec.describe RunPipelineScheduleWorker do
expect(Ci::CreatePipelineService).not_to receive(:new) expect(Ci::CreatePipelineService).not_to receive(:new)
expect(worker).not_to receive(:run_pipeline_schedule) expect(worker).not_to receive(:run_pipeline_schedule)
worker.perform(pipeline_schedule.id, 10000) worker.perform(pipeline_schedule.id, non_existing_record_id)
end end
end end