From d6ce16a4070112512d792f7ab66fdca4764cbf2c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 28 Jan 2022 12:17:26 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- GITALY_SERVER_VERSION | 2 +- .../diffs/components/diff_file_header.vue | 6 +- .../framework/contextual_sidebar.scss | 2 + app/assets/stylesheets/framework/files.scss | 5 - .../stylesheets/startup/startup-dark.scss | 2 + .../stylesheets/startup/startup-general.scss | 2 + app/graphql/mutations/ci/runner/update.rb | 7 +- app/services/ci/pipeline_schedule_service.rb | 2 + app/services/ci/update_runner_service.rb | 2 + .../create_service_accounts_service.rb | 5 +- .../projects/blob/_header_content.html.haml | 2 +- app/workers/ci/delete_objects_worker.rb | 8 +- app/workers/pipeline_schedule_worker.rb | 2 + app/workers/run_pipeline_schedule_worker.rb | 2 +- ...ct_import_schedule_worker_job_tracker.yml} | 10 +- ...yml => update_all_mirrors_job_tracker.yml} | 10 +- ...-runner-api-status-does-contain-paused.yml | 4 +- ...d-replaced-with-paused-breaking-change.yml | 33 ++++++ .../20220124130028_dedup_runner_projects.rb | 71 ++++++++++++ ...i_sources_projects_source_project_id_fk.rb | 19 ++++ ...cts_ci_pipeline_schedules_project_id_fk.rb | 19 ++++ db/schema_migrations/20220124130028 | 1 + db/schema_migrations/20220126202654 | 1 + db/schema_migrations/20220126203421 | 1 + db/structure.sql | 10 +- doc/api/graphql/reference/index.md | 3 +- doc/api/runners.md | 103 +++++++++++++----- doc/update/deprecations.md | 34 +++++- lib/api/ci/runner.rb | 16 ++- lib/api/ci/runners.rb | 9 +- lib/api/entities/ci/runner.rb | 5 +- .../database/gitlab_loose_foreign_keys.yml | 7 ++ lib/gitlab/saas.rb | 2 +- lib/google_api/cloud_platform/client.rb | 30 ++++- .../user_creates_image_diff_notes_spec.rb | 2 +- .../diffs/components/diff_file_header_spec.js | 2 +- .../database/no_cross_db_foreign_keys_spec.rb | 2 - spec/lib/gitlab_spec.rb | 7 ++ .../google_api/cloud_platform/client_spec.rb | 52 ++++++++- ...220124130028_dedup_runner_projects_spec.rb | 65 +++++++++++ spec/models/ci/pipeline_schedule_spec.rb | 7 ++ .../api/ci/runner/runners_post_spec.rb | 30 ++++- spec/requests/api/ci/runners_spec.rb | 22 +++- .../service_accounts_controller_spec.rb | 11 +- .../ci/pipeline_schedule_service_spec.rb | 17 +++ .../services/ci/update_runner_service_spec.rb | 14 +++ .../create_service_accounts_service_spec.rb | 16 ++- spec/workers/ci/delete_objects_worker_spec.rb | 33 +----- spec/workers/pipeline_schedule_worker_spec.rb | 10 ++ .../run_pipeline_schedule_worker_spec.rb | 19 +++- 50 files changed, 603 insertions(+), 143 deletions(-) rename config/feature_flags/development/{ci_delete_objects_medium_concurrency.yml => project_import_schedule_worker_job_tracker.yml} (59%) rename config/feature_flags/development/{ci_delete_objects_high_concurrency.yml => update_all_mirrors_job_tracker.yml} (60%) create mode 100644 data/deprecations/14-8-runner-api-active-field-replaced-with-paused-breaking-change.yml create mode 100644 db/post_migrate/20220124130028_dedup_runner_projects.rb create mode 100644 db/post_migrate/20220126202654_remove_projects_ci_sources_projects_source_project_id_fk.rb create mode 100644 db/post_migrate/20220126203421_remove_projects_ci_pipeline_schedules_project_id_fk.rb create mode 100644 db/schema_migrations/20220124130028 create mode 100644 db/schema_migrations/20220126202654 create mode 100644 db/schema_migrations/20220126203421 create mode 100644 spec/migrations/20220124130028_dedup_runner_projects_spec.rb diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 580ba589977..1b322111644 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -b7f0c0462a8f689c8ee9e654f0875157b238158b +69baea2ed2c593ed570b614f1c311d8ee80a1c33 diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index 238f07ac22c..0e8506cd896 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -3,6 +3,7 @@ import { GlTooltipDirective, GlSafeHtmlDirective, GlIcon, + GlBadge, GlButton, GlButtonGroup, GlDropdown, @@ -34,6 +35,7 @@ export default { GlIcon, FileIcon, DiffStats, + GlBadge, GlButton, GlButtonGroup, GlDropdown, @@ -349,7 +351,9 @@ export default { {{ diffFile.a_mode }} → {{ diffFile.b_mode }} - {{ __('LFS') }} + {{ + __('LFS') + }}
a, diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 9209a0c2173..9387500e66f 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -411,11 +411,6 @@ span.idiff { margin-right: 1.5em; } -.label-lfs { - color: $common-gray-light; - border: 1px solid $common-gray-light; -} - .preview-container { overflow: auto; diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index 9acf650af43..574a1652548 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -1095,6 +1095,8 @@ input { } .nav-sidebar li .nav-item-name { flex: 1; + overflow: hidden; + text-overflow: ellipsis; } .nav-sidebar li > a, .nav-sidebar li > .fly-out-top-item-container { diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index 645fa42f832..7eced0f3b07 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -1076,6 +1076,8 @@ input { } .nav-sidebar li .nav-item-name { flex: 1; + overflow: hidden; + text-overflow: ellipsis; } .nav-sidebar li > a, .nav-sidebar li > .fly-out-top-item-container { diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb index e37ab1081f9..e6123b4283a 100644 --- a/app/graphql/mutations/ci/runner/update.rb +++ b/app/graphql/mutations/ci/runner/update.rb @@ -28,7 +28,12 @@ module Mutations argument :active, GraphQL::Types::Boolean, 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, description: 'Indicates the runner is locked.' diff --git a/app/services/ci/pipeline_schedule_service.rb b/app/services/ci/pipeline_schedule_service.rb index 596c3b80bda..536eaa56f9b 100644 --- a/app/services/ci/pipeline_schedule_service.rb +++ b/app/services/ci/pipeline_schedule_service.rb @@ -3,6 +3,8 @@ module Ci class PipelineScheduleService < BaseService def execute(schedule) + return unless project.persisted? + # Ensure `next_run_at` is set properly before creating a pipeline. # Otherwise, multiple pipelines could be created in a short interval. schedule.schedule_next_run! diff --git a/app/services/ci/update_runner_service.rb b/app/services/ci/update_runner_service.rb index e4117a51fe6..4a17e25c0cc 100644 --- a/app/services/ci/update_runner_service.rb +++ b/app/services/ci/update_runner_service.rb @@ -9,6 +9,8 @@ module Ci end def update(params) + params[:active] = !params.delete(:paused) if params.include?(:paused) + runner.update(params).tap do |updated| runner.tick_runner_queue if updated end diff --git a/app/services/google_cloud/create_service_accounts_service.rb b/app/services/google_cloud/create_service_accounts_service.rb index fa025e8f672..e360b3a8e4e 100644 --- a/app/services/google_cloud/create_service_accounts_service.rb +++ b/app/services/google_cloud/create_service_accounts_service.rb @@ -5,6 +5,7 @@ module GoogleCloud def execute 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) + google_api_client.grant_service_account_roles(gcp_project_id, service_account.email) service_accounts_service.add_for_project( environment_name, @@ -35,7 +36,7 @@ module GoogleCloud end 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 def service_accounts_service @@ -50,7 +51,7 @@ module GoogleCloud "GitLab generated service account for project '#{project.name}' and environment '#{environment_name}'" end - # Overriden in EE + # Overridden in EE def environment_protected? false end diff --git a/app/views/projects/blob/_header_content.html.haml b/app/views/projects/blob/_header_content.html.haml index 95a5d63e07f..f5e32e7f589 100644 --- a/app/views/projects/blob/_header_content.html.haml +++ b/app/views/projects/blob/_header_content.html.haml @@ -14,4 +14,4 @@ = number_to_human_size(blob.raw_size) - if blob.stored_externally? && blob.external_storage == :lfs - %span.badge.label-lfs.gl-mr-2 LFS + = gl_badge_tag(_('LFS'), variant: :neutral) diff --git a/app/workers/ci/delete_objects_worker.rb b/app/workers/ci/delete_objects_worker.rb index cbcad3e8838..32c57750076 100644 --- a/app/workers/ci/delete_objects_worker.rb +++ b/app/workers/ci/delete_objects_worker.rb @@ -22,13 +22,7 @@ module Ci end def max_running_jobs - if ::Feature.enabled?(:ci_delete_objects_medium_concurrency) - 20 - elsif ::Feature.enabled?(:ci_delete_objects_high_concurrency) - 50 - else - 2 - end + 20 end private diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb index ebda30f57d8..5a53d53ccf9 100644 --- a/app/workers/pipeline_schedule_worker.rb +++ b/app/workers/pipeline_schedule_worker.rb @@ -13,6 +13,8 @@ class PipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker def perform Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules| schedules.each do |schedule| + next unless schedule.project + with_context(project: schedule.project, user: schedule.owner) do Ci::PipelineScheduleService.new(schedule.project, schedule.owner).execute(schedule) end diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb index f08d8231e43..35e3e633c70 100644 --- a/app/workers/run_pipeline_schedule_worker.rb +++ b/app/workers/run_pipeline_schedule_worker.rb @@ -15,7 +15,7 @@ class RunPipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker schedule = Ci::PipelineSchedule.find_by_id(schedule_id) user = User.find_by_id(user_id) - return unless schedule && user + return unless schedule && schedule.project && user run_pipeline_schedule(schedule, user) end diff --git a/config/feature_flags/development/ci_delete_objects_medium_concurrency.yml b/config/feature_flags/development/project_import_schedule_worker_job_tracker.yml similarity index 59% rename from config/feature_flags/development/ci_delete_objects_medium_concurrency.yml rename to config/feature_flags/development/project_import_schedule_worker_job_tracker.yml index 55fc2d9fc94..5dae4ddc60c 100644 --- a/config/feature_flags/development/ci_delete_objects_medium_concurrency.yml +++ b/config/feature_flags/development/project_import_schedule_worker_job_tracker.yml @@ -1,8 +1,8 @@ --- -name: ci_delete_objects_medium_concurrency -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39464 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247103 -milestone: '13.5' +name: project_import_schedule_worker_job_tracker +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79097 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351408 +milestone: '14.8' type: development -group: group::pipeline execution +group: group::scalability default_enabled: false diff --git a/config/feature_flags/development/ci_delete_objects_high_concurrency.yml b/config/feature_flags/development/update_all_mirrors_job_tracker.yml similarity index 60% rename from config/feature_flags/development/ci_delete_objects_high_concurrency.yml rename to config/feature_flags/development/update_all_mirrors_job_tracker.yml index a14861beb59..507f32550c3 100644 --- a/config/feature_flags/development/ci_delete_objects_high_concurrency.yml +++ b/config/feature_flags/development/update_all_mirrors_job_tracker.yml @@ -1,8 +1,8 @@ --- -name: ci_delete_objects_high_concurrency -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39464 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247103 -milestone: '13.5' +name: update_all_mirrors_job_tracker +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79097 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351420 +milestone: '14.8' type: development -group: group::pipeline execution +group: group::scalability default_enabled: false diff --git a/data/deprecations/14-5-runner-api-status-does-contain-paused.yml b/data/deprecations/14-5-runner-api-status-does-contain-paused.yml index dd2e6e7a6fb..c3fbe553706 100644 --- a/data/deprecations/14-5-runner-api-status-does-contain-paused.yml +++ b/data/deprecations/14-5-runner-api-status-does-contain-paused.yml @@ -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_date: "2021-11-22" 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. 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 tiers: [Core, Premium, Ultimate] issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648 diff --git a/data/deprecations/14-8-runner-api-active-field-replaced-with-paused-breaking-change.yml b/data/deprecations/14-8-runner-api-active-field-replaced-with-paused-breaking-change.yml new file mode 100644 index 00000000000..791c44d19eb --- /dev/null +++ b/data/deprecations/14-8-runner-api-active-field-replaced-with-paused-breaking-change.yml @@ -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 diff --git a/db/post_migrate/20220124130028_dedup_runner_projects.rb b/db/post_migrate/20220124130028_dedup_runner_projects.rb new file mode 100644 index 00000000000..1a4c895f1c4 --- /dev/null +++ b/db/post_migrate/20220124130028_dedup_runner_projects.rb @@ -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 diff --git a/db/post_migrate/20220126202654_remove_projects_ci_sources_projects_source_project_id_fk.rb b/db/post_migrate/20220126202654_remove_projects_ci_sources_projects_source_project_id_fk.rb new file mode 100644 index 00000000000..a69cd43b921 --- /dev/null +++ b/db/post_migrate/20220126202654_remove_projects_ci_sources_projects_source_project_id_fk.rb @@ -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 diff --git a/db/post_migrate/20220126203421_remove_projects_ci_pipeline_schedules_project_id_fk.rb b/db/post_migrate/20220126203421_remove_projects_ci_pipeline_schedules_project_id_fk.rb new file mode 100644 index 00000000000..870127ab168 --- /dev/null +++ b/db/post_migrate/20220126203421_remove_projects_ci_pipeline_schedules_project_id_fk.rb @@ -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 diff --git a/db/schema_migrations/20220124130028 b/db/schema_migrations/20220124130028 new file mode 100644 index 00000000000..5ee8e463a55 --- /dev/null +++ b/db/schema_migrations/20220124130028 @@ -0,0 +1 @@ +7f2b3e70e33273d75f68bd1fa33103f24a4e4cfc3f2e5777dfd258b5a2e7bf4e \ No newline at end of file diff --git a/db/schema_migrations/20220126202654 b/db/schema_migrations/20220126202654 new file mode 100644 index 00000000000..341e2c6b8bf --- /dev/null +++ b/db/schema_migrations/20220126202654 @@ -0,0 +1 @@ +6067e4e22e49548496454b48171f8168f7d5bf626fedab4351e2a37a3f85731b \ No newline at end of file diff --git a/db/schema_migrations/20220126203421 b/db/schema_migrations/20220126203421 new file mode 100644 index 00000000000..32469cd40ee --- /dev/null +++ b/db/schema_migrations/20220126203421 @@ -0,0 +1 @@ +07f837ddde21e36d1ca6a471dd96350d3020bd30204fca0e093983810c94e54d \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index b98c24f6c4a..deb58fe6708 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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_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_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_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 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 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 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 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 ADD CONSTRAINT fk_rails_64bfc19bc5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 11ac51e9726..12ea1e13b57 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -4158,12 +4158,13 @@ Input type: `RunnerUpdateInput` | Name | Type | Description | | ---- | ---- | ----------- | | `accessLevel` | [`CiRunnerAccessLevel`](#cirunneraccesslevel) | Access level of the runner. | -| `active` | [`Boolean`](#boolean) | Indicates the runner is allowed to receive jobs. | +| `active` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated:** This was renamed. Please use `paused`. Deprecated in 14.8. | | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | `description` | [`String`](#string) | Description of the runner. | | `id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner to update. | | `locked` | [`Boolean`](#boolean) | Indicates the runner is locked. | | `maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. | +| `paused` | [`Boolean`](#boolean) | Indicates the runner is not allowed to receive jobs. | | `privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). | | `publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "minutes cost factor" associated with the runner (GitLab.com only). | | `runUntagged` | [`Boolean`](#boolean) | Indicates the runner is able to run untagged jobs. | diff --git a/doc/api/runners.md b/doc/api/runners.md index 163bcdad588..aa676746450 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -54,12 +54,17 @@ GET /runners?tag_list=tag1,tag2 curl --header "PRIVATE-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: ```json [ { "active": true, + "paused": false, "description": "test-1-20150125", "id": 6, "ip_address": "127.0.0.1", @@ -71,6 +76,7 @@ Example response: }, { "active": true, + "paused": false, "description": "test-2-20150125", "id": 8, "ip_address": "127.0.0.1", @@ -107,12 +113,17 @@ GET /runners/all?tag_list=tag1,tag2 curl --header "PRIVATE-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: ```json [ { "active": true, + "paused": false, "description": "shared-runner-1", "id": 1, "ip_address": "127.0.0.1", @@ -124,6 +135,7 @@ Example response: }, { "active": true, + "paused": false, "description": "shared-runner-2", "id": 3, "ip_address": "127.0.0.1", @@ -135,6 +147,7 @@ Example response: }, { "active": true, + "paused": false, "description": "test-1-20150125", "id": 6, "ip_address": "127.0.0.1", @@ -146,6 +159,7 @@ Example response: }, { "active": true, + "paused": false, "description": "test-2-20150125", "id": 8, "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). 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: ```json { "active": true, + "paused": false, "architecture": null, "description": "test-1-20150125", "id": 6, @@ -229,16 +248,17 @@ Update details of a runner. PUT /runners/:id ``` -| Attribute | Type | Required | Description | -|---------------|---------|----------|---------------------| -| `id` | integer | yes | The ID 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` | -| `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner | -| `run_untagged`| boolean | no | Flag indicating the runner can execute untagged jobs | -| `locked` | boolean | no | Flag indicating the runner is locked | -| `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 | +| Attribute | Type | Required | Description | +|-------------------|---------|----------|--------------------------------------------------------------------------------------------------| +| `id` | integer | yes | The ID of a runner | +| `description` | string | no | The description of a runner | +| `active` | boolean | no | Deprecated: Use `:paused` instead. Flag indicating whether the runner is allowed to receive jobs | +| `paused` | boolean | no | Flag indicating whether the runner should ignore new jobs | +| `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner | +| `run_untagged` | boolean | no | Flag indicating the runner can execute untagged jobs | +| `locked` | boolean | no | Flag indicating the runner is locked | +| `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 curl --request PUT --header "PRIVATE-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. 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: ```json @@ -292,7 +316,12 @@ Example response: Pause a specific runner. ```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 | @@ -300,10 +329,20 @@ PUT --form "active=false" /runners/:runner_id | `runner_id` | integer | yes | The ID of a runner | ```shell +curl --request PUT --header "PRIVATE-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: " \ --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 > [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: " "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: ```json [ { "active": true, + "paused": false, "description": "test-2-20150125", "id": 8, "ip_address": "127.0.0.1", @@ -437,6 +481,7 @@ Example response: }, { "active": true, + "paused": false, "description": "development_runner", "id": 5, "ip_address": "127.0.0.1", @@ -444,7 +489,7 @@ Example response: "runner_type": "instance_type", "name": null, "online": true, - "status": "paused" + "status": "online" } ] ``` @@ -525,6 +570,10 @@ GET /groups/:id/runners?tag_list=tag1,tag2 curl --header "PRIVATE-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: ```json @@ -534,6 +583,7 @@ Example response: "description": "Shared", "ip_address": "127.0.0.1", "active": true, + "paused": false, "is_shared": true, "runner_type": "instance_type", "name": "gitlab-runner", @@ -545,6 +595,7 @@ Example response: "description": "Test", "ip_address": "127.0.0.1", "active": true, + "paused": false, "is_shared": true, "runner_type": "instance_type", "name": "gitlab-runner", @@ -556,6 +607,7 @@ Example response: "description": "Test 2", "ip_address": "127.0.0.1", "active": true, + "paused": false, "is_shared": false, "runner_type": "group_type", "name": "gitlab-runner", @@ -573,19 +625,20 @@ Register a new runner for the instance. POST /runners ``` -| Attribute | Type | Required | Description | -|--------------------|--------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `token` | string | yes | [Registration token](#registration-and-authentication-tokens). | -| `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. | -| `active` | boolean | no | Whether the runner is active | -| `locked` | boolean | no | Whether the runner should be locked for current project | -| `run_untagged` | boolean | no | Whether the runner should handle untagged jobs | -| `tag_list` | string array | no | List of runner's tags | -| `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 | -| `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) | +| Attribute | Type | Required | Description | +|--------------------|--------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `token` | string | yes | [Registration token](#registration-and-authentication-tokens) | +| `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 | +| `active` | boolean | no | Deprecated: Use `:paused` instead. Whether the runner is allowed to receive jobs | +| `paused` | boolean | no | Whether the runner should ignore new jobs | +| `locked` | boolean | no | Whether the runner should be locked for current project | +| `run_untagged` | boolean | no | Whether the runner should handle untagged jobs | +| `tag_list` | string array | no | List of runner's tags | +| `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 | +| `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 curl --request POST "https://gitlab.example.com/api/v4/runners" \ diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 8327e42dabf..9dbb2d2e8f2 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -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)** -### REST API Runner will not contain `paused` +### REST and GraphQL API Runner status will not return `paused` WARNING: 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. 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)** @@ -717,6 +717,36 @@ The `merged_by` field in the [merge request API](https://docs.gitlab.com/ee/api/ ## 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 WARNING: diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index aa75a6f2410..153738a8edc 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -18,21 +18,25 @@ module API 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 :info, type: Hash, desc: %q(Runner's metadata) - optional :active, type: Boolean, desc: 'Should Runner be active' - optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' + optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Should runner be active' + 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, - desc: 'The access_level of the runner' - optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' + desc: 'The access_level of the runner; `not_protected` or `ref_protected`' + 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 :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 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) # Pull in deprecated maintainer_note if that's the only note value available deprecated_note = attributes.delete(:maintainer_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) forbidden! unless @runner diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index f21782a698f..9b3b9ca3f48 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -77,18 +77,21 @@ module API params do requires :id, type: Integer, desc: 'The ID 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 :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 :access_level, type: String, values: ::Ci::Runner.access_levels.keys, desc: 'The access_level of the runner' 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 put ':id' do runner = get_runner(params.delete(:id)) authenticate_update_runner!(runner) + params[:active] = !params.delete(:paused) if params.include?(:paused) update_service = ::Ci::UpdateRunnerService.new(runner) if update_service.update(declared_params(include_missing: false)) diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb index c17ff513479..a6944b8c925 100644 --- a/lib/api/entities/ci/runner.rb +++ b/lib/api/entities/ci/runner.rb @@ -7,7 +7,10 @@ module API expose :id expose :description 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 :runner_type expose :name diff --git a/lib/gitlab/database/gitlab_loose_foreign_keys.yml b/lib/gitlab/database/gitlab_loose_foreign_keys.yml index 4a796b6c265..c80163290ec 100644 --- a/lib/gitlab/database/gitlab_loose_foreign_keys.yml +++ b/lib/gitlab/database/gitlab_loose_foreign_keys.yml @@ -83,6 +83,10 @@ ci_namespace_mirrors: - table: namespaces column: namespace_id on_delete: async_delete +ci_sources_projects: + - table: projects + column: source_project_id + on_delete: async_delete ci_build_report_results: - table: projects column: project_id @@ -167,6 +171,9 @@ ci_pipeline_schedules: - table: users column: owner_id on_delete: async_nullify + - table: projects + column: project_id + on_delete: async_delete merge_trains: - table: ci_pipelines column: pipeline_id diff --git a/lib/gitlab/saas.rb b/lib/gitlab/saas.rb index 577e33fd700..0a4f2ba64a8 100644 --- a/lib/gitlab/saas.rb +++ b/lib/gitlab/saas.rb @@ -18,7 +18,7 @@ module Gitlab end def self.subdomain_regex - %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze + %r{\Ahttps://[a-z0-9-]+\.gitlab\.com\z}.freeze end def self.dev_url diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index 9bd2309d2b7..defcb33c5b6 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -20,6 +20,7 @@ module GoogleApi "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/monitoring" ].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 def session_key_for_token @@ -88,11 +89,8 @@ module GoogleApi def list_projects result = [] - service = Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new - service.authorization = access_token - - response = service.fetch_all(items: :projects) do |token| - service.list_projects + response = cloud_resource_manager_service.fetch_all(items: :projects) do |token| + cloud_resource_manager_service.list_projects end # 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) 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 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;)" } 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 diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb index cc0d7a279dd..47529518ba4 100644 --- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb +++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb @@ -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) 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 it_behaves_like 'creates image diff note' diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js index 320fb395c3b..f22bd312a6d 100644 --- a/spec/frontend/diffs/components/diff_file_header_spec.js +++ b/spec/frontend/diffs/components/diff_file_header_spec.js @@ -82,7 +82,7 @@ describe('DiffFileHeader component', () => { const findExpandButton = () => wrapper.find({ ref: 'expandDiffToFullFileButton' }); const findFileActions = () => wrapper.find('.file-actions'); 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 findExternalLink = () => wrapper.find({ ref: 'externalLink' }); const findReplacedFileButton = () => wrapper.find({ ref: 'replacedFileButton' }); diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb index c5ebd762a79..84dd2e98c18 100644 --- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb @@ -19,12 +19,10 @@ RSpec.describe 'cross-database foreign keys' do ci_pending_builds.namespace_id ci_pending_builds.project_id ci_pipeline_schedules.owner_id - ci_pipeline_schedules.project_id ci_pipelines.project_id ci_resource_groups.project_id ci_runner_namespaces.namespace_id ci_running_builds.project_id - ci_sources_projects.source_project_id ci_stages.project_id ci_unit_tests.project_id ).freeze diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 49ba4debe31..57a4bdc9bb5 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -99,6 +99,13 @@ RSpec.describe Gitlab do expect(described_class.com?).to eq true 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 stub_config_setting(url: 'http://example.com') diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index 3284c9cd0d1..ab45a648555 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -60,7 +60,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do before do allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) .to receive(:get_zone_cluster).with(any_args, options: user_agent_options) - .and_return(gke_cluster) + .and_return(gke_cluster) end it { is_expected.to eq(gke_cluster) } @@ -122,7 +122,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do before do allow_any_instance_of(Google::Apis::ContainerV1beta1::ContainerService) .to receive(:create_cluster).with(any_args) - .and_return(operation) + .and_return(operation) end it 'sets corresponded parameters' do @@ -172,7 +172,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do before do allow_any_instance_of(Google::Apis::ContainerV1::ContainerService) .to receive(:get_zone_operation).with(any_args, options: user_agent_options) - .and_return(operation) + .and_return(operation) end it { is_expected.to eq(operation) } @@ -244,7 +244,7 @@ RSpec.describe GoogleApi::CloudPlatform::Client do 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) .to receive(:create_service_account_key) .with(any_args) @@ -252,4 +252,48 @@ RSpec.describe GoogleApi::CloudPlatform::Client do is_expected.to eq(operation) 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 diff --git a/spec/migrations/20220124130028_dedup_runner_projects_spec.rb b/spec/migrations/20220124130028_dedup_runner_projects_spec.rb new file mode 100644 index 00000000000..2698af6f6f5 --- /dev/null +++ b/spec/migrations/20220124130028_dedup_runner_projects_spec.rb @@ -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 diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 0f1cb721e95..0f4f148775e 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -227,4 +227,11 @@ RSpec.describe Ci::PipelineSchedule do it { is_expected.to eq(144) } 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 diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb index 6409c08c278..27917933c68 100644 --- a/spec/requests/api/ci/runner/runners_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_post_spec.rb @@ -34,7 +34,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do run_untagged: false, tag_list: 'tag1, tag2', locked: true, - active: true, + paused: false, access_level: 'ref_protected', maximum_timeout: 9000 } @@ -55,7 +55,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do maximum_timeout: 9000 }.stringify_keys - allow(service).to receive(:execute) + expect(service).to receive(:execute) .once .with('valid token', a_hash_including(expected_params)) .and_return(new_runner) @@ -108,6 +108,32 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do 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 include StubGitlabCalls diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index 305c0bd9df0..7cf436969db 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -109,7 +109,7 @@ RSpec.describe API::Ci::Runners do get api('/runners?tag_list=tag1,tag2', user) 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 @@ -137,7 +137,7 @@ RSpec.describe API::Ci::Runners do get api('/runners/all', admin) 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' => 'Group runner A', '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['maximum_timeout']).to be_nil expect(json_response['status']).to eq("not_connected") + expect(json_response['active']).to eq(true) + expect(json_response['paused']).to eq(false) end end @@ -359,6 +361,14 @@ RSpec.describe API::Ci::Runners do expect(shared_runner.reload.active).to eq(!active) 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 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) expect(json_response).to match_array [ - a_hash_including('description' => 'Project runner'), - a_hash_including('description' => 'Two projects runner'), - a_hash_including('description' => 'Shared runner') + a_hash_including('description' => 'Project runner', 'active' => true, 'paused' => false), + a_hash_including('description' => 'Two projects runner', 'active' => true, 'paused' => false), + a_hash_including('description' => 'Shared runner', 'active' => true, 'paused' => false) ] end @@ -986,7 +996,7 @@ RSpec.describe API::Ci::Runners do get api("/groups/#{group.id}/runners", user) 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 diff --git a/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb b/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb index 6b4d1c490e2..0f243a6a7a9 100644 --- a/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb +++ b/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb @@ -2,10 +2,6 @@ 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 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 before do 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(: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(:grant_service_account_roles) end end @@ -147,7 +145,8 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do context 'but gitlab instance is not configured for google oauth2' 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) .with('google_oauth2') .and_return(unconfigured_google_oauth2) diff --git a/spec/services/ci/pipeline_schedule_service_spec.rb b/spec/services/ci/pipeline_schedule_service_spec.rb index 65bbd13c5e7..b8e4fb19f5d 100644 --- a/spec/services/ci/pipeline_schedule_service_spec.rb +++ b/spec/services/ci/pipeline_schedule_service_spec.rb @@ -32,5 +32,22 @@ RSpec.describe Ci::PipelineScheduleService do expect { subject }.not_to raise_error 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 diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb index 1c875b2f54a..eee80bfef47 100644 --- a/spec/services/ci/update_runner_service_spec.rb +++ b/spec/services/ci/update_runner_service_spec.rb @@ -23,6 +23,20 @@ RSpec.describe Ci::UpdateRunnerService do 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 let(:params) { { public_projects_minutes_cost_factor: 1.1, private_projects_minutes_cost_factor: 2.2 }} diff --git a/spec/services/google_cloud/create_service_accounts_service_spec.rb b/spec/services/google_cloud/create_service_accounts_service_spec.rb index 190e1a8098c..53d21df713a 100644 --- a/spec/services/google_cloud/create_service_accounts_service_spec.rb +++ b/spec/services/google_cloud/create_service_accounts_service_spec.rb @@ -2,22 +2,26 @@ require 'spec_helper' -# Mock Types -MockGoogleOAuth2Credentials = Struct.new(:app_id, :app_secret) -MockServiceAccount = Struct.new(:project_id, :unique_id) - RSpec.describe GoogleCloud::CreateServiceAccountsService do describe '#execute' 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) .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| + 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) - .and_return(MockServiceAccount.new('mock-project-id', 'mock-unique-id')) + .and_return(mock_service_account) + allow(client).to receive(:create_service_account_key) .and_return('mock-key') + + allow(client) + .to receive(:grant_service_account_roles) end end diff --git a/spec/workers/ci/delete_objects_worker_spec.rb b/spec/workers/ci/delete_objects_worker_spec.rb index 52d90d7667a..3d985dffdc5 100644 --- a/spec/workers/ci/delete_objects_worker_spec.rb +++ b/spec/workers/ci/delete_objects_worker_spec.rb @@ -6,15 +6,16 @@ RSpec.describe Ci::DeleteObjectsWorker do let(:worker) { described_class.new } 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 it 'executes a service' do - allow(worker).to receive(:max_running_jobs).and_return(25) - expect_next_instance_of(Ci::DeleteObjectsService) do |instance| expect(instance).to receive(:execute) expect(instance).to receive(:remaining_batches_count) - .with(max_batch_count: 25) + .with(max_batch_count: 20) .once .and_call_original end @@ -22,30 +23,4 @@ RSpec.describe Ci::DeleteObjectsWorker do worker.perform 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 diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb index f59d8ad4615..4a7db0eca56 100644 --- a/spec/workers/pipeline_schedule_worker_spec.rb +++ b/spec/workers/pipeline_schedule_worker_spec.rb @@ -103,4 +103,14 @@ RSpec.describe PipelineScheduleWorker do expect { subject }.not_to raise_error 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 diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb index bb11d1dbb58..846b4455bf9 100644 --- a/spec/workers/run_pipeline_schedule_worker_spec.rb +++ b/spec/workers/run_pipeline_schedule_worker_spec.rb @@ -10,12 +10,25 @@ RSpec.describe RunPipelineScheduleWorker do 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 expect(Ci::CreatePipelineService).not_to receive(:new) 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 @@ -24,7 +37,7 @@ RSpec.describe RunPipelineScheduleWorker do expect(Ci::CreatePipelineService).not_to receive(:new) 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